PageSourceSearch

https://assets.cgtrader.com/assets/application-2b6b37222f76dfac012…ead54c036dc1ec6f24538ae05aa1e386f522a5fe.js

js cgtrader.com collected 2026-09-24 08:26:56 UTC 3,804,033 bytes, 66,258 lines download raw bytes

vendor: 4,491 bytes, lines 1-181
1/*!
2 * jQuery JavaScript Library v1.11.0
3 * http://jquery.com/
4 *
5 * Includes Sizzle.js
6 * http://sizzlejs.com/
7 *
8 * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
9 * Released under the MIT license
10 * http://jquery.org/license
11 *
12 * Date: 2014-01-23T21:02Z
13 */
14
15
16(function( global, factory ) {
17
18	if ( typeof module === "object" && typeof module.exports === "object" ) {
19		// For CommonJS and CommonJS-like environments where a proper window is present,
20		// execute the factory and get jQuery
21		// For environments that do not inherently posses a window with a document
22		// (such as Node.js), expose a jQuery-making factory as module.exports
23		// This accentuates the need for the creation of a real window
24		// e.g. var jQuery = require("jquery")(window);
25		// See ticket #14549 for more info
26		module.exports = global.document ?
27			factory( global, true ) :
28			function( w ) {
29				if ( !w.document ) {
30					throw new Error( "jQuery requires a window with a document" );
31				}
32				return factory( w );
33			};
34	} else {
35		factory( global );
36	}
37
38// Pass this if window is not defined yet
39}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
40
41// Can't do this because several apps including ASP.NET trace
42// the stack via arguments.caller.callee and Firefox dies if
43// you try to trace through "use strict" call chains. (#13335)
44// Support: Firefox 18+
45//
46
47var deletedIds = [];
48
49var slice = deletedIds.slice;
50
51var concat = deletedIds.concat;
52
53var push = deletedIds.push;
54
55var indexOf = deletedIds.indexOf;
56
57var class2type = {};
58
59var toString = class2type.toString;
60
61var hasOwn = class2type.hasOwnProperty;
62
63var trim = "".trim;
64
65var support = {};
66
67
68
69var
70	version = "1.11.0",
71
72	// Define a local copy of jQuery
73	jQuery = function( selector, context ) {
74		// The jQuery object is actually just the init constructor 'enhanced'
75		// Need init if jQuery is called (just allow error to be thrown if not included)
76		return new jQuery.fn.init( selector, context );
77	},
78
79	// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
80	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
81
82	// Matches dashed string for camelizing
83	rmsPrefix = /^-ms-/,
84	rdashAlpha = /-([\da-z])/gi,
85
86	// Used by jQuery.camelCase as callback to replace()
87	fcamelCase = function( all, letter ) {
88		return letter.toUpperCase();
89	};
90
91jQuery.fn = jQuery.prototype = {
92	// The current version of jQuery being used
93	jquery: version,
94
95	constructor: jQuery,
96
97	// Start with an empty selector
98	selector: "",
99
100	// The default length of a jQuery object is 0
101	length: 0,
102
103	toArray: function() {
104		return slice.call( this );
105	},
106
107	// Get the Nth element in the matched element set OR
108	// Get the whole matched element set as a clean array
109	get: function( num ) {
110		return num != null ?
111
112			// Return a 'clean' array
113			( num < 0 ? this[ num + this.length ] : this[ num ] ) :
114
115			// Return just the object
116			slice.call( this );
117	},
118
119	// Take an array of elements and push it onto the stack
120	// (returning the new matched element set)
121	pushStack: function( elems ) {
122
123		// Build a new jQuery matched element set
124		var ret = jQuery.merge( this.constructor(), elems );
125
126		// Add the old object onto the stack (as a reference)
127		ret.prevObject = this;
128		ret.context = this.context;
129
130		// Return the newly-formed element set
131		return ret;
132	},
133
134	// Execute a callback for every element in the matched set.
135	// (You can seed the arguments with an array of args, but this is
136	// only used internally.)
137	each: function( callback, args ) {
138		return jQuery.each( this, callback, args );
139	},
140
141	map: function( callback ) {
142		return this.pushStack( jQuery.map(this, function( elem, i ) {
143			return callback.call( elem, i, elem );
144		}));
145	},
146
147	slice: function() {
148		return this.pushStack( slice.apply( this, arguments ) );
149	},
150
151	first: function() {
152		return this.eq( 0 );
153	},
154
155	last: function() {
156		return this.eq( -1 );
157	},
158
159	eq: function( i ) {
160		var len = this.length,
161			j = +i + ( i < 0 ? len : 0 );
162		return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
163	},
164
165	end: function() {
166		return this.prevObject || this.constructor(null);
167	},
168
169	// For internal use only.
170	// Behaves like an Array's method, not like a jQuery method.
171	push: push,
172	sort: deletedIds.sort,
173	splice: deletedIds.splice
174};
175
176jQuery.extend = jQuery.fn.extend = function() {
177	var src, copyIsArray, copy, name, options, clone,
178		target = arguments[0] || {},
179		i = 1,
180		length = arguments.length,
181		deep = false;
182
183	// Handle a deep copy situation
184	if ( typeof target === "boolean" ) {
185		deep = target;
186
187		// skip the boolean and the target
188		target = arguments[ i ] || {};
189		i++;
190	}
191
192	// Handle case when target is a string or something (possible in deep copy)
193	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
194		target = {};
195	}
196
197	// extend jQuery itself if only one argument is passed
198	if ( i === length ) {
199		target = this;
200		i--;
201	}
202
203	for ( ; i < length; i++ ) {
204		// Only deal with non-null/undefined values
205		if ( (options = arguments[ i ]) != null ) {
206			// Extend the base object
207			for ( name in options ) {
208				src = target[ name ];
209				copy = options[ name ];
210
211				// Prevent never-ending loop
212				if ( target === copy ) {
213					continue;
214				}
215
216				// Recurse if we're merging plain objects or arrays
217				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
218					if ( copyIsArray ) {
219						copyIsArray = false;
220						clone = src && jQuery.isArray(src) ? src : [];
221
222					} else {
223						clone = src && jQuery.isPlainObject(src) ? src : {};
224					}
225
226					// Never move original objects, clone them
227					target[ name ] = jQuery.extend( deep, clone, copy );
228
229				// Don't bring in undefined values
230				} else if ( copy !== undefined ) {
231					target[ name ] = copy;
232				}
233			}
234		}
235	}
236
237	// Return the modified object
238	return target;
239};
240
241jQuery.extend({
242	// Unique for each copy of jQuery on the page
243	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
244
245	// Assume jQuery is ready without the ready module
246	isReady: true,
247
248	error: function( msg ) {
249		throw new Error( msg );
250	},
251
252	noop: function() {},
253
254	// See test/unit/core.js for details concerning isFunction.
255	// Since version 1.3, DOM methods and functions like alert
256	// aren't supported. They return false on IE (#2968).
257	isFunction: function( obj ) {
258		return jQuery.type(obj) === "function";
259	},
260
261	isArray: Array.isArray || function( obj ) {
262		return jQuery.type(obj) === "array";
263	},
264
265	isWindow: function( obj ) {
266		/* jshint eqeqeq: false */
267		return obj != null && obj == obj.window;
268	},
269
270	isNumeric: function( obj ) {
271		// parseFloat NaNs numeric-cast false positives (null|true|false|"")
272		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
273		// subtraction forces infinities to NaN
274		return obj - parseFloat( obj ) >= 0;
275	},
276
277	isEmptyObject: function( obj ) {
278		var name;
279		for ( name in obj ) {
280			return false;
281		}
282		return true;
283	},
284
285	isPlainObject: function( obj ) {
286		var key;
287
288		// Must be an Object.
289		// Because of IE, we also have to check the presence of the constructor property.
290		// Make sure that DOM nodes and window objects don't pass through, as well
291		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
292			return false;
293		}
294
295		try {
296			// Not own constructor property must be Object
297			if ( obj.constructor &&
298				!hasOwn.call(obj, "constructor") &&
299				!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
300				return false;
301			}
302		} catch ( e ) {
303			// IE8,9 Will throw exceptions on certain host objects #9897
304			return false;
305		}
306
307		// Support: IE<9
308		// Handle iteration over inherited properties before own properties.
309		if ( support.ownLast ) {
310			for ( key in obj ) {
311				return hasOwn.call( obj, key );
312			}
313		}
314
315		// Own properties are enumerated firstly, so to speed up,
316		// if last one is own, then all properties are own.
317		for ( key in obj ) {}
318
319		return key === undefined || hasOwn.call( obj, key );
320	},
321
322	type: function( obj ) {
323		if ( obj == null ) {
324			return obj + "";
325		}
326		return typeof obj === "object" || typeof obj === "function" ?
327			class2type[ toString.call(obj) ] || "object" :
328			typeof obj;
329	},
330
331	// Evaluates a script in a global context
332	// Workarounds based on findings by Jim Driscoll
333	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
334	globalEval: function( data ) {
335		if ( data && jQuery.trim( data ) ) {
336			// We use execScript on Internet Explorer
337			// We use an anonymous function so that context is window
338			// rather than jQuery in Firefox
339			( window.execScript || function( data ) {
340				window[ "eval" ].call( window, data );
341			} )( data );
342		}
343	},
344
345	// Convert dashed to camelCase; used by the css and data modules
346	// Microsoft forgot to hump their vendor prefix (#9572)
347	camelCase: function( string ) {
348		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
349	},
350
351	nodeName: function( elem, name ) {
352		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
353	},
354
355	// args is for internal usage only
356	each: function( obj, callback, args ) {
357		var value,
358			i = 0,
359			length = obj.length,
360			isArray = isArraylike( obj );
361
362		if ( args ) {
363			if ( isArray ) {
364				for ( ; i < length; i++ ) {
365					value = callback.apply( obj[ i ], args );
366
367					if ( value === false ) {
368						break;
369					}
370				}
371			} else {
372				for ( i in obj ) {
373					value = callback.apply( obj[ i ], args );
374
375					if ( value === false ) {
376						break;
377					}
378				}
379			}
380
381		// A special, fast, case for the most common use of each
382		} else {
383			if ( isArray ) {
384				for ( ; i < length; i++ ) {
385					value = callback.call( obj[ i ], i, obj[ i ] );
386
387					if ( value === false ) {
388						break;
389					}
390				}
391			} else {
392				for ( i in obj ) {
393					value = callback.call( obj[ i ], i, obj[ i ] );
394
395					if ( value === false ) {
396						break;
397					}
398				}
399			}
400		}
401
402		return obj;
403	},
404
405	// Use native String.trim function wherever possible
406	trim: trim && !trim.call("\uFEFF\xA0") ?
407		function( text ) {
408			return text == null ?
409				"" :
410				trim.call( text );
411		} :
412
413		// Otherwise use our own trimming functionality
414		function( text ) {
415			return text == null ?
416				"" :
417				( text + "" ).replace( rtrim, "" );
418		},
419
420	// results is for internal usage only
421	makeArray: function( arr, results ) {
422		var ret = results || [];
423
424		if ( arr != null ) {
425			if ( isArraylike( Object(arr) ) ) {
426				jQuery.merge( ret,
427					typeof arr === "string" ?
428					[ arr ] : arr
429				);
430			} else {
431				push.call( ret, arr );
432			}
433		}
434
435		return ret;
436	},
437
438	inArray: function( elem, arr, i ) {
439		var len;
440
441		if ( arr ) {
442			if ( indexOf ) {
443				return indexOf.call( arr, elem, i );
444			}
445
446			len = arr.length;
447			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
448
449			for ( ; i < len; i++ ) {
450				// Skip accessing in sparse arrays
451				if ( i in arr && arr[ i ] === elem ) {
452					return i;
453				}
454			}
455		}
456
457		return -1;
458	},
459
460	merge: function( first, second ) {
461		var len = +second.length,
462			j = 0,
463			i = first.length;
464
465		while ( j < len ) {
466			first[ i++ ] = second[ j++ ];
467		}
468
469		// Support: IE<9
470		// Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
471		if ( len !== len ) {
472			while ( second[j] !== undefined ) {
473				first[ i++ ] = second[ j++ ];
474			}
475		}
476
477		first.length = i;
478
479		return first;
480	},
481
482	grep: function( elems, callback, invert ) {
483		var callbackInverse,
484			matches = [],
485			i = 0,
486			length = elems.length,
487			callbackExpect = !invert;
488
489		// Go through the array, only saving the items
490		// that pass the validator function
491		for ( ; i < length; i++ ) {
492			callbackInverse = !callback( elems[ i ], i );
493			if ( callbackInverse !== callbackExpect ) {
494				matches.push( elems[ i ] );
495			}
496		}
497
498		return matches;
499	},
500
501	// arg is for internal usage only
502	map: function( elems, callback, arg ) {
503		var value,
504			i = 0,
505			length = elems.length,
506			isArray = isArraylike( elems ),
507			ret = [];
508
509		// Go through the array, translating each of the items to their new values
510		if ( isArray ) {
511			for ( ; i < length; i++ ) {
512				value = callback( elems[ i ], i, arg );
513
514				if ( value != null ) {
515					ret.push( value );
516				}
517			}
518
519		// Go through every key on the object,
520		} else {
521			for ( i in elems ) {
522				value = callback( elems[ i ], i, arg );
523
524				if ( value != null ) {
525					ret.push( value );
526				}
527			}
528		}
529
530		// Flatten any nested arrays
531		return concat.apply( [], ret );
532	},
533
534	// A global GUID counter for objects
535	guid: 1,
536
537	// Bind a function to a context, optionally partially applying any
538	// arguments.
539	proxy: function( fn, context ) {
540		var args, proxy, tmp;
541
542		if ( typeof context === "string" ) {
543			tmp = fn[ context ];
544			context = fn;
545			fn = tmp;
546		}
547
548		// Quick check to determine if target is callable, in the spec
549		// this throws a TypeError, but we will just return undefined.
550		if ( !jQuery.isFunction( fn ) ) {
551			return undefined;
552		}
553
554		// Simulated bind
555		args = slice.call( arguments, 2 );
556		proxy = function() {
557			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
558		};
559
560		// Set the guid of unique handler to the same of original handler, so it can be removed
561		proxy.guid = fn.guid = fn.guid || jQuery.guid++;
562
563		return proxy;
564	},
565
566	now: function() {
567		return +( new Date() );
568	},
569
570	// jQuery.support is not used in Core but other projects attach their
571	// properties to it so it needs to exist.
572	support: support
573});
574
575// Populate the class2type map
576jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
577	class2type[ "[object " + name + "]" ] = name.toLowerCase();
578});
579
580function isArraylike( obj ) {
581	var length = obj.length,
582		type = jQuery.type( obj );
583
584	if ( type === "function" || jQuery.isWindow( obj ) ) {
585		return false;
586	}
587
588	if ( obj.nodeType === 1 && length ) {
589		return true;
590	}
591
592	return type === "array" || length === 0 ||
593		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
594}
595var Sizzle =
596/*!
597 * Sizzle CSS Selector Engine v1.10.16
598 * http://sizzlejs.com/
599 *
600 * Copyright 2013 jQuery Foundation, Inc. and other contributors
601 * Released under the MIT license
602 * http://jquery.org/license
603 *
604 * Date: 2014-01-13
605 */
606(function( window ) {
607
608var i,
609	support,
610	Expr,
611	getText,
612	isXML,
613	compile,
614	outermostContext,
615	sortInput,
616	hasDuplicate,
617
618	// Local document vars
619	setDocument,
620	document,
621	docElem,
622	documentIsHTML,
623	rbuggyQSA,
624	rbuggyMatches,
625	matches,
626	contains,
627
628	// Instance-specific data
629	expando = "sizzle" + -(new Date()),
630	preferredDoc = window.document,
631	dirruns = 0,
632	done = 0,
633	classCache = createCache(),
634	tokenCache = createCache(),
635	compilerCache = createCache(),
636	sortOrder = function( a, b ) {
637		if ( a === b ) {
638			hasDuplicate = true;
639		}
640		return 0;
641	},
642
643	// General-purpose constants
644	strundefined = typeof undefined,
645	MAX_NEGATIVE = 1 << 31,
646
647	// Instance methods
648	hasOwn = ({}).hasOwnProperty,
649	arr = [],
650	pop = arr.pop,
651	push_native = arr.push,
652	push = arr.push,
653	slice = arr.slice,
654	// Use a stripped-down indexOf if we can't use a native one
655	indexOf = arr.indexOf || function( elem ) {
656		var i = 0,
657			len = this.length;
658		for ( ; i < len; i++ ) {
659			if ( this[i] === elem ) {
660				return i;
661			}
662		}
663		return -1;
664	},
665
666	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
667
668	// Regular expressions
669
670	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
671	whitespace = "[\\x20\\t\\r\\n\\f]",
672	// http://www.w3.org/TR/css3-syntax/#characters
673	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
674
675	// Loosely modeled on CSS identifier characters
676	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
677	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
678	identifier = characterEncoding.replace( "w", "w#" ),
679
680	// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
681	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
682		"*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
683
684	// Prefer arguments quoted,
685	//   then not containing pseudos/brackets,
686	//   then attribute selectors/non-parenthetical expressions,
687	//   then anything else
688	// These preferences are here to reduce the number of selectors
689	//   needing tokenize in the PSEUDO preFilter
690	pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
691
692	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
693	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
694
695	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
696	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
697
698	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
699
700	rpseudo = new RegExp( pseudos ),
701	ridentifier = new RegExp( "^" + identifier + "$" ),
702
703	matchExpr = {
704		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
705		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
706		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
707		"ATTR": new RegExp( "^" + attributes ),
708		"PSEUDO": new RegExp( "^" + pseudos ),
709		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
710			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
711			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
712		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
713		// For use in libraries implementing .is()
714		// We use this for POS matching in `select`
715		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
716			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
717	},
718
719	rinputs = /^(?:input|select|textarea|button)$/i,
720	rheader = /^h\d$/i,
721
722	rnative = /^[^{]+\{\s*\[native \w/,
723
724	// Easily-parseable/retrievable ID or TAG or CLASS selectors
725	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
726
727	rsibling = /[+~]/,
728	rescape = /'|\\/g,
729
730	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
731	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
732	funescape = function( _, escaped, escapedWhitespace ) {
733		var high = "0x" + escaped - 0x10000;
734		// NaN means non-codepoint
735		// Support: Firefox
736		// Workaround erroneous numeric interpretation of +"0x"
737		return high !== high || escapedWhitespace ?
738			escaped :
739			high < 0 ?
740				// BMP codepoint
741				String.fromCharCode( high + 0x10000 ) :
742				// Supplemental Plane codepoint (surrogate pair)
743				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
744	};
745
746// Optimize for push.apply( _, NodeList )
747try {
748	push.apply(
749		(arr = slice.call( preferredDoc.childNodes )),
750		preferredDoc.childNodes
751	);
752	// Support: Android<4.0
753	// Detect silently failing push.apply
754	arr[ preferredDoc.childNodes.length ].nodeType;
755} catch ( e ) {
756	push = { apply: arr.length ?
757
758		// Leverage slice if possible
759		function( target, els ) {
760			push_native.apply( target, slice.call(els) );
761		} :
762
763		// Support: IE<9
764		// Otherwise append directly
765		function( target, els ) {
766			var j = target.length,
767				i = 0;
768			// Can't trust NodeList.length
769			while ( (target[j++] = els[i++]) ) {}
770			target.length = j - 1;
771		}
772	};
773}
774
775function Sizzle( selector, context, results, seed ) {
776	var match, elem, m, nodeType,
777		// QSA vars
778		i, groups, old, nid, newContext, newSelector;
779
780	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
781		setDocument( context );
782	}
783
784	context = context || document;
785	results = results || [];
786
787	if ( !selector || typeof selector !== "string" ) {
788		return results;
789	}
790
791	if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
792		return [];
793	}
794
795	if ( documentIsHTML && !seed ) {
796
797		// Shortcuts
798		if ( (match = rquickExpr.exec( selector )) ) {
799			// Speed-up: Sizzle("#ID")
800			if ( (m = match[1]) ) {
801				if ( nodeType === 9 ) {
802					elem = context.getElementById( m );
803					// Check parentNode to catch when Blackberry 4.6 returns
804					// nodes that are no longer in the document (jQuery #6963)
805					if ( elem && elem.parentNode ) {
806						// Handle the case where IE, Opera, and Webkit return items
807						// by name instead of ID
808						if ( elem.id === m ) {
809							results.push( elem );
810							return results;
811						}
812					} else {
813						return results;
814					}
815				} else {
816					// Context is not a document
817					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
818						contains( context, elem ) && elem.id === m ) {
819						results.push( elem );
820						return results;
821					}
822				}
823
824			// Speed-up: Sizzle("TAG")
825			} else if ( match[2] ) {
826				push.apply( results, context.getElementsByTagName( selector ) );
827				return results;
828
829			// Speed-up: Sizzle(".CLASS")
830			} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
831				push.apply( results, context.getElementsByClassName( m ) );
832				return results;
833			}
834		}
835
836		// QSA path
837		if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
838			nid = old = expando;
839			newContext = context;
840			newSelector = nodeType === 9 && selector;
841
842			// qSA works strangely on Element-rooted queries
843			// We can work around this by specifying an extra ID on the root
844			// and working up from there (Thanks to Andrew Dupont for the technique)
845			// IE 8 doesn't work on object elements
846			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
847				groups = tokenize( selector );
848
849				if ( (old = context.getAttribute("id")) ) {
850					nid = old.replace( rescape, "\\$&" );
851				} else {
852					context.setAttribute( "id", nid );
853				}
854				nid = "[id='" + nid + "'] ";
855
856				i = groups.length;
857				while ( i-- ) {
858					groups[i] = nid + toSelector( groups[i] );
859				}
vendor: 4,490 bytes, lines 860-1042
860				newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
861				newSelector = groups.join(",");
862			}
863
864			if ( newSelector ) {
865				try {
866					push.apply( results,
867						newContext.querySelectorAll( newSelector )
868					);
869					return results;
870				} catch(qsaError) {
871				} finally {
872					if ( !old ) {
873						context.removeAttribute("id");
874					}
875				}
876			}
877		}
878	}
879
880	// All others
881	return select( selector.replace( rtrim, "$1" ), context, results, seed );
882}
883
884/**
885 * Create key-value caches of limited size
886 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
887 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
888 *	deleting the oldest entry
889 */
890function createCache() {
891	var keys = [];
892
893	function cache( key, value ) {
894		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
895		if ( keys.push( key + " " ) > Expr.cacheLength ) {
896			// Only keep the most recent entries
897			delete cache[ keys.shift() ];
898		}
899		return (cache[ key + " " ] = value);
900	}
901	return cache;
902}
903
904/**
905 * Mark a function for special use by Sizzle
906 * @param {Function} fn The function to mark
907 */
908function markFunction( fn ) {
909	fn[ expando ] = true;
910	return fn;
911}
912
913/**
914 * Support testing using an element
915 * @param {Function} fn Passed the created div and expects a boolean result
916 */
917function assert( fn ) {
918	var div = document.createElement("div");
919
920	try {
921		return !!fn( div );
922	} catch (e) {
923		return false;
924	} finally {
925		// Remove from its parent by default
926		if ( div.parentNode ) {
927			div.parentNode.removeChild( div );
928		}
929		// release memory in IE
930		div = null;
931	}
932}
933
934/**
935 * Adds the same handler for all of the specified attrs
936 * @param {String} attrs Pipe-separated list of attributes
937 * @param {Function} handler The method that will be applied
938 */
939function addHandle( attrs, handler ) {
940	var arr = attrs.split("|"),
941		i = attrs.length;
942
943	while ( i-- ) {
944		Expr.attrHandle[ arr[i] ] = handler;
945	}
946}
947
948/**
949 * Checks document order of two siblings
950 * @param {Element} a
951 * @param {Element} b
952 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
953 */
954function siblingCheck( a, b ) {
955	var cur = b && a,
956		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
957			( ~b.sourceIndex || MAX_NEGATIVE ) -
958			( ~a.sourceIndex || MAX_NEGATIVE );
959
960	// Use IE sourceIndex if available on both nodes
961	if ( diff ) {
962		return diff;
963	}
964
965	// Check if b follows a
966	if ( cur ) {
967		while ( (cur = cur.nextSibling) ) {
968			if ( cur === b ) {
969				return -1;
970			}
971		}
972	}
973
974	return a ? 1 : -1;
975}
976
977/**
978 * Returns a function to use in pseudos for input types
979 * @param {String} type
980 */
981function createInputPseudo( type ) {
982	return function( elem ) {
983		var name = elem.nodeName.toLowerCase();
984		return name === "input" && elem.type === type;
985	};
986}
987
988/**
989 * Returns a function to use in pseudos for buttons
990 * @param {String} type
991 */
992function createButtonPseudo( type ) {
993	return function( elem ) {
994		var name = elem.nodeName.toLowerCase();
995		return (name === "input" || name === "button") && elem.type === type;
996	};
997}
998
999/**
1000 * Returns a function to use in pseudos for positionals
1001 * @param {Function} fn
1002 */
1003function createPositionalPseudo( fn ) {
1004	return markFunction(function( argument ) {
1005		argument = +argument;
1006		return markFunction(function( seed, matches ) {
1007			var j,
1008				matchIndexes = fn( [], seed.length, argument ),
1009				i = matchIndexes.length;
1010
1011			// Match elements found at the specified indexes
1012			while ( i-- ) {
1013				if ( seed[ (j = matchIndexes[i]) ] ) {
1014					seed[j] = !(matches[j] = seed[j]);
1015				}
1016			}
1017		});
1018	});
1019}
1020
1021/**
1022 * Checks a node for validity as a Sizzle context
1023 * @param {Element|Object=} context
1024 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
1025 */
1026function testContext( context ) {
1027	return context && typeof context.getElementsByTagName !== strundefined && context;
1028}
1029
1030// Expose support vars for convenience
1031support = Sizzle.support = {};
1032
1033/**
1034 * Detects XML nodes
1035 * @param {Element|Object} elem An element or a document
1036 * @returns {Boolean} True iff elem is a non-HTML XML node
1037 */
1038isXML = Sizzle.isXML = function( elem ) {
1039	// documentElement is verified for cases where it doesn't yet exist
1040	// (such as loading iframes in IE - #4833)
1041	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
1042	return documentElement ? documentElement.nodeName !== "HTML" : false;
1043};
1044
1045/**
1046 * Sets document-related variables once based on the current document
1047 * @param {Element|Object} [doc] An element or document object to use to set the document
1048 * @returns {Object} Returns the current document
1049 */
1050setDocument = Sizzle.setDocument = function( node ) {
1051	var hasCompare,
1052		doc = node ? node.ownerDocument || node : preferredDoc,
1053		parent = doc.defaultView;
1054
1055	// If no document and documentElement is available, return
1056	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
1057		return document;
1058	}
1059
1060	// Set our document
1061	document = doc;
1062	docElem = doc.documentElement;
1063
1064	// Support tests
1065	documentIsHTML = !isXML( doc );
1066
1067	// Support: IE>8
1068	// If iframe document is assigned to "document" variable and if iframe has been reloaded,
1069	// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
1070	// IE6-8 do not support the defaultView property so parent will be undefined
1071	if ( parent && parent !== parent.top ) {
1072		// IE11 does not have attachEvent, so all must suffer
1073		if ( parent.addEventListener ) {
1074			parent.addEventListener( "unload", function() {
1075				setDocument();
1076			}, false );
1077		} else if ( parent.attachEvent ) {
1078			parent.attachEvent( "onunload", function() {
1079				setDocument();
1080			});
1081		}
1082	}
1083
1084	/* Attributes
1085	---------------------------------------------------------------------- */
1086
1087	// Support: IE<8
1088	// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
1089	support.attributes = assert(function( div ) {
1090		div.className = "i";
1091		return !div.getAttribute("className");
1092	});
1093
1094	/* getElement(s)By*
1095	---------------------------------------------------------------------- */
1096
1097	// Check if getElementsByTagName("*") returns only elements
1098	support.getElementsByTagName = assert(function( div ) {
1099		div.appendChild( doc.createComment("") );
1100		return !div.getElementsByTagName("*").length;
1101	});
1102
1103	// Check if getElementsByClassName can be trusted
1104	support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {
1105		div.innerHTML = "<div class='a'></div><div class='a i'></div>";
1106
1107		// Support: Safari<4
1108		// Catch class over-caching
1109		div.firstChild.className = "i";
1110		// Support: Opera<10
1111		// Catch gEBCN failure to find non-leading classes
1112		return div.getElementsByClassName("i").length === 2;
1113	});
1114
1115	// Support: IE<10
1116	// Check if getElementById returns elements by name
1117	// The broken getElementById methods don't pick up programatically-set names,
1118	// so use a roundabout getElementsByName test
1119	support.getById = assert(function( div ) {
1120		docElem.appendChild( div ).id = expando;
1121		return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
1122	});
1123
1124	// ID find and filter
1125	if ( support.getById ) {
1126		Expr.find["ID"] = function( id, context ) {
1127			if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
1128				var m = context.getElementById( id );
1129				// Check parentNode to catch when Blackberry 4.6 returns
1130				// nodes that are no longer in the document #6963
1131				return m && m.parentNode ? [m] : [];
1132			}
1133		};
1134		Expr.filter["ID"] = function( id ) {
1135			var attrId = id.replace( runescape, funescape );
1136			return function( elem ) {
1137				return elem.getAttribute("id") === attrId;
1138			};
1139		};
1140	} else {
vendor: 42,934 bytes, lines 1141-2674
1141		// Support: IE6/7
1142		// getElementById is not reliable as a find shortcut
1143		delete Expr.find["ID"];
1144
1145		Expr.filter["ID"] =  function( id ) {
1146			var attrId = id.replace( runescape, funescape );
1147			return function( elem ) {
1148				var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
1149				return node && node.value === attrId;
1150			};
1151		};
1152	}
1153
1154	// Tag
1155	Expr.find["TAG"] = support.getElementsByTagName ?
1156		function( tag, context ) {
1157			if ( typeof context.getElementsByTagName !== strundefined ) {
1158				return context.getElementsByTagName( tag );
1159			}
1160		} :
1161		function( tag, context ) {
1162			var elem,
1163				tmp = [],
1164				i = 0,
1165				results = context.getElementsByTagName( tag );
1166
1167			// Filter out possible comments
1168			if ( tag === "*" ) {
1169				while ( (elem = results[i++]) ) {
1170					if ( elem.nodeType === 1 ) {
1171						tmp.push( elem );
1172					}
1173				}
1174
1175				return tmp;
1176			}
1177			return results;
1178		};
1179
1180	// Class
1181	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
1182		if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
1183			return context.getElementsByClassName( className );
1184		}
1185	};
1186
1187	/* QSA/matchesSelector
1188	---------------------------------------------------------------------- */
1189
1190	// QSA and matchesSelector support
1191
1192	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
1193	rbuggyMatches = [];
1194
1195	// qSa(:focus) reports false when true (Chrome 21)
1196	// We allow this because of a bug in IE8/9 that throws an error
1197	// whenever `document.activeElement` is accessed on an iframe
1198	// So, we allow :focus to pass through QSA all the time to avoid the IE error
1199	// See http://bugs.jquery.com/ticket/13378
1200	rbuggyQSA = [];
1201
1202	if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
1203		// Build QSA regex
1204		// Regex strategy adopted from Diego Perini
1205		assert(function( div ) {
1206			// Select is set to empty string on purpose
1207			// This is to test IE's treatment of not explicitly
1208			// setting a boolean content attribute,
1209			// since its presence should be enough
1210			// http://bugs.jquery.com/ticket/12359
1211			div.innerHTML = "<select t=''><option selected=''></option></select>";
1212
1213			// Support: IE8, Opera 10-12
1214			// Nothing should be selected when empty strings follow ^= or $= or *=
1215			if ( div.querySelectorAll("[t^='']").length ) {
1216				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
1217			}
1218
1219			// Support: IE8
1220			// Boolean attributes and "value" are not treated correctly
1221			if ( !div.querySelectorAll("[selected]").length ) {
1222				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
1223			}
1224
1225			// Webkit/Opera - :checked should return selected option elements
1226			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
1227			// IE8 throws error here and will not see later tests
1228			if ( !div.querySelectorAll(":checked").length ) {
1229				rbuggyQSA.push(":checked");
1230			}
1231		});
1232
1233		assert(function( div ) {
1234			// Support: Windows 8 Native Apps
1235			// The type and name attributes are restricted during .innerHTML assignment
1236			var input = doc.createElement("input");
1237			input.setAttribute( "type", "hidden" );
1238			div.appendChild( input ).setAttribute( "name", "D" );
1239
1240			// Support: IE8
1241			// Enforce case-sensitivity of name attribute
1242			if ( div.querySelectorAll("[name=d]").length ) {
1243				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
1244			}
1245
1246			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
1247			// IE8 throws error here and will not see later tests
1248			if ( !div.querySelectorAll(":enabled").length ) {
1249				rbuggyQSA.push( ":enabled", ":disabled" );
1250			}
1251
1252			// Opera 10-11 does not throw on post-comma invalid pseudos
1253			div.querySelectorAll("*,:x");
1254			rbuggyQSA.push(",.*:");
1255		});
1256	}
1257
1258	if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||
1259		docElem.mozMatchesSelector ||
1260		docElem.oMatchesSelector ||
1261		docElem.msMatchesSelector) )) ) {
1262
1263		assert(function( div ) {
1264			// Check to see if it's possible to do matchesSelector
1265			// on a disconnected node (IE 9)
1266			support.disconnectedMatch = matches.call( div, "div" );
1267
1268			// This should fail with an exception
1269			// Gecko does not error, returns false instead
1270			matches.call( div, "[s!='']:x" );
1271			rbuggyMatches.push( "!=", pseudos );
1272		});
1273	}
1274
1275	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
1276	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
1277
1278	/* Contains
1279	---------------------------------------------------------------------- */
1280	hasCompare = rnative.test( docElem.compareDocumentPosition );
1281
1282	// Element contains another
1283	// Purposefully does not implement inclusive descendent
1284	// As in, an element does not contain itself
1285	contains = hasCompare || rnative.test( docElem.contains ) ?
1286		function( a, b ) {
1287			var adown = a.nodeType === 9 ? a.documentElement : a,
1288				bup = b && b.parentNode;
1289			return a === bup || !!( bup && bup.nodeType === 1 && (
1290				adown.contains ?
1291					adown.contains( bup ) :
1292					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
1293			));
1294		} :
1295		function( a, b ) {
1296			if ( b ) {
1297				while ( (b = b.parentNode) ) {
1298					if ( b === a ) {
1299						return true;
1300					}
1301				}
1302			}
1303			return false;
1304		};
1305
1306	/* Sorting
1307	---------------------------------------------------------------------- */
1308
1309	// Document order sorting
1310	sortOrder = hasCompare ?
1311	function( a, b ) {
1312
1313		// Flag for duplicate removal
1314		if ( a === b ) {
1315			hasDuplicate = true;
1316			return 0;
1317		}
1318
1319		// Sort on method existence if only one input has compareDocumentPosition
1320		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
1321		if ( compare ) {
1322			return compare;
1323		}
1324
1325		// Calculate position if both inputs belong to the same document
1326		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
1327			a.compareDocumentPosition( b ) :
1328
1329			// Otherwise we know they are disconnected
1330			1;
1331
1332		// Disconnected nodes
1333		if ( compare & 1 ||
1334			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
1335
1336			// Choose the first element that is related to our preferred document
1337			if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
1338				return -1;
1339			}
1340			if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
1341				return 1;
1342			}
1343
1344			// Maintain original order
1345			return sortInput ?
1346				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
1347				0;
1348		}
1349
1350		return compare & 4 ? -1 : 1;
1351	} :
1352	function( a, b ) {
1353		// Exit early if the nodes are identical
1354		if ( a === b ) {
1355			hasDuplicate = true;
1356			return 0;
1357		}
1358
1359		var cur,
1360			i = 0,
1361			aup = a.parentNode,
1362			bup = b.parentNode,
1363			ap = [ a ],
1364			bp = [ b ];
1365
1366		// Parentless nodes are either documents or disconnected
1367		if ( !aup || !bup ) {
1368			return a === doc ? -1 :
1369				b === doc ? 1 :
1370				aup ? -1 :
1371				bup ? 1 :
1372				sortInput ?
1373				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
1374				0;
1375
1376		// If the nodes are siblings, we can do a quick check
1377		} else if ( aup === bup ) {
1378			return siblingCheck( a, b );
1379		}
1380
1381		// Otherwise we need full lists of their ancestors for comparison
1382		cur = a;
1383		while ( (cur = cur.parentNode) ) {
1384			ap.unshift( cur );
1385		}
1386		cur = b;
1387		while ( (cur = cur.parentNode) ) {
1388			bp.unshift( cur );
1389		}
1390
1391		// Walk down the tree looking for a discrepancy
1392		while ( ap[i] === bp[i] ) {
1393			i++;
1394		}
1395
1396		return i ?
1397			// Do a sibling check if the nodes have a common ancestor
1398			siblingCheck( ap[i], bp[i] ) :
1399
1400			// Otherwise nodes in our document sort first
1401			ap[i] === preferredDoc ? -1 :
1402			bp[i] === preferredDoc ? 1 :
1403			0;
1404	};
1405
1406	return doc;
1407};
1408
1409Sizzle.matches = function( expr, elements ) {
1410	return Sizzle( expr, null, null, elements );
1411};
1412
1413Sizzle.matchesSelector = function( elem, expr ) {
1414	// Set document vars if needed
1415	if ( ( elem.ownerDocument || elem ) !== document ) {
1416		setDocument( elem );
1417	}
1418
1419	// Make sure that attribute selectors are quoted
1420	expr = expr.replace( rattributeQuotes, "='$1']" );
1421
1422	if ( support.matchesSelector && documentIsHTML &&
1423		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
1424		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
1425
1426		try {
1427			var ret = matches.call( elem, expr );
1428
1429			// IE 9's matchesSelector returns false on disconnected nodes
1430			if ( ret || support.disconnectedMatch ||
1431					// As well, disconnected nodes are said to be in a document
1432					// fragment in IE 9
1433					elem.document && elem.document.nodeType !== 11 ) {
1434				return ret;
1435			}
1436		} catch(e) {}
1437	}
1438
1439	return Sizzle( expr, document, null, [elem] ).length > 0;
1440};
1441
1442Sizzle.contains = function( context, elem ) {
1443	// Set document vars if needed
1444	if ( ( context.ownerDocument || context ) !== document ) {
1445		setDocument( context );
1446	}
1447	return contains( context, elem );
1448};
1449
1450Sizzle.attr = function( elem, name ) {
1451	// Set document vars if needed
1452	if ( ( elem.ownerDocument || elem ) !== document ) {
1453		setDocument( elem );
1454	}
1455
1456	var fn = Expr.attrHandle[ name.toLowerCase() ],
1457		// Don't get fooled by Object.prototype properties (jQuery #13807)
1458		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
1459			fn( elem, name, !documentIsHTML ) :
1460			undefined;
1461
1462	return val !== undefined ?
1463		val :
1464		support.attributes || !documentIsHTML ?
1465			elem.getAttribute( name ) :
1466			(val = elem.getAttributeNode(name)) && val.specified ?
1467				val.value :
1468				null;
1469};
1470
1471Sizzle.error = function( msg ) {
1472	throw new Error( "Syntax error, unrecognized expression: " + msg );
1473};
1474
1475/**
1476 * Document sorting and removing duplicates
1477 * @param {ArrayLike} results
1478 */
1479Sizzle.uniqueSort = function( results ) {
1480	var elem,
1481		duplicates = [],
1482		j = 0,
1483		i = 0;
1484
1485	// Unless we *know* we can detect duplicates, assume their presence
1486	hasDuplicate = !support.detectDuplicates;
1487	sortInput = !support.sortStable && results.slice( 0 );
1488	results.sort( sortOrder );
1489
1490	if ( hasDuplicate ) {
1491		while ( (elem = results[i++]) ) {
1492			if ( elem === results[ i ] ) {
1493				j = duplicates.push( i );
1494			}
1495		}
1496		while ( j-- ) {
1497			results.splice( duplicates[ j ], 1 );
1498		}
1499	}
1500
1501	// Clear input after sorting to release objects
1502	// See https://github.com/jquery/sizzle/pull/225
1503	sortInput = null;
1504
1505	return results;
1506};
1507
1508/**
1509 * Utility function for retrieving the text value of an array of DOM nodes
1510 * @param {Array|Element} elem
1511 */
1512getText = Sizzle.getText = function( elem ) {
1513	var node,
1514		ret = "",
1515		i = 0,
1516		nodeType = elem.nodeType;
1517
1518	if ( !nodeType ) {
1519		// If no nodeType, this is expected to be an array
1520		while ( (node = elem[i++]) ) {
1521			// Do not traverse comment nodes
1522			ret += getText( node );
1523		}
1524	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
1525		// Use textContent for elements
1526		// innerText usage removed for consistency of new lines (jQuery #11153)
1527		if ( typeof elem.textContent === "string" ) {
1528			return elem.textContent;
1529		} else {
1530			// Traverse its children
1531			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
1532				ret += getText( elem );
1533			}
1534		}
1535	} else if ( nodeType === 3 || nodeType === 4 ) {
1536		return elem.nodeValue;
1537	}
1538	// Do not include comment or processing instruction nodes
1539
1540	return ret;
1541};
1542
1543Expr = Sizzle.selectors = {
1544
1545	// Can be adjusted by the user
1546	cacheLength: 50,
1547
1548	createPseudo: markFunction,
1549
1550	match: matchExpr,
1551
1552	attrHandle: {},
1553
1554	find: {},
1555
1556	relative: {
1557		">": { dir: "parentNode", first: true },
1558		" ": { dir: "parentNode" },
1559		"+": { dir: "previousSibling", first: true },
1560		"~": { dir: "previousSibling" }
1561	},
1562
1563	preFilter: {
1564		"ATTR": function( match ) {
1565			match[1] = match[1].replace( runescape, funescape );
1566
1567			// Move the given value to match[3] whether quoted or unquoted
1568			match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
1569
1570			if ( match[2] === "~=" ) {
1571				match[3] = " " + match[3] + " ";
1572			}
1573
1574			return match.slice( 0, 4 );
1575		},
1576
1577		"CHILD": function( match ) {
1578			/* matches from matchExpr["CHILD"]
1579				1 type (only|nth|...)
1580				2 what (child|of-type)
1581				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
1582				4 xn-component of xn+y argument ([+-]?\d*n|)
1583				5 sign of xn-component
1584				6 x of xn-component
1585				7 sign of y-component
1586				8 y of y-component
1587			*/
1588			match[1] = match[1].toLowerCase();
1589
1590			if ( match[1].slice( 0, 3 ) === "nth" ) {
1591				// nth-* requires argument
1592				if ( !match[3] ) {
1593					Sizzle.error( match[0] );
1594				}
1595
1596				// numeric x and y parameters for Expr.filter.CHILD
1597				// remember that false/true cast respectively to 0/1
1598				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
1599				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
1600
1601			// other types prohibit arguments
1602			} else if ( match[3] ) {
1603				Sizzle.error( match[0] );
1604			}
1605
1606			return match;
1607		},
1608
1609		"PSEUDO": function( match ) {
1610			var excess,
1611				unquoted = !match[5] && match[2];
1612
1613			if ( matchExpr["CHILD"].test( match[0] ) ) {
1614				return null;
1615			}
1616
1617			// Accept quoted arguments as-is
1618			if ( match[3] && match[4] !== undefined ) {
1619				match[2] = match[4];
1620
1621			// Strip excess characters from unquoted arguments
1622			} else if ( unquoted && rpseudo.test( unquoted ) &&
1623				// Get excess from tokenize (recursively)
1624				(excess = tokenize( unquoted, true )) &&
1625				// advance to the next closing parenthesis
1626				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
1627
1628				// excess is a negative index
1629				match[0] = match[0].slice( 0, excess );
1630				match[2] = unquoted.slice( 0, excess );
1631			}
1632
1633			// Return only captures needed by the pseudo filter method (type and argument)
1634			return match.slice( 0, 3 );
1635		}
1636	},
1637
1638	filter: {
1639
1640		"TAG": function( nodeNameSelector ) {
1641			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
1642			return nodeNameSelector === "*" ?
1643				function() { return true; } :
1644				function( elem ) {
1645					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
1646				};
1647		},
1648
1649		"CLASS": function( className ) {
1650			var pattern = classCache[ className + " " ];
1651
1652			return pattern ||
1653				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
1654				classCache( className, function( elem ) {
1655					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
1656				});
1657		},
1658
1659		"ATTR": function( name, operator, check ) {
1660			return function( elem ) {
1661				var result = Sizzle.attr( elem, name );
1662
1663				if ( result == null ) {
1664					return operator === "!=";
1665				}
1666				if ( !operator ) {
1667					return true;
1668				}
1669
1670				result += "";
1671
1672				return operator === "=" ? result === check :
1673					operator === "!=" ? result !== check :
1674					operator === "^=" ? check && result.indexOf( check ) === 0 :
1675					operator === "*=" ? check && result.indexOf( check ) > -1 :
1676					operator === "$=" ? check && result.slice( -check.length ) === check :
1677					operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
1678					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
1679					false;
1680			};
1681		},
1682
1683		"CHILD": function( type, what, argument, first, last ) {
1684			var simple = type.slice( 0, 3 ) !== "nth",
1685				forward = type.slice( -4 ) !== "last",
1686				ofType = what === "of-type";
1687
1688			return first === 1 && last === 0 ?
1689
1690				// Shortcut for :nth-*(n)
1691				function( elem ) {
1692					return !!elem.parentNode;
1693				} :
1694
1695				function( elem, context, xml ) {
1696					var cache, outerCache, node, diff, nodeIndex, start,
1697						dir = simple !== forward ? "nextSibling" : "previousSibling",
1698						parent = elem.parentNode,
1699						name = ofType && elem.nodeName.toLowerCase(),
1700						useCache = !xml && !ofType;
1701
1702					if ( parent ) {
1703
1704						// :(first|last|only)-(child|of-type)
1705						if ( simple ) {
1706							while ( dir ) {
1707								node = elem;
1708								while ( (node = node[ dir ]) ) {
1709									if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
1710										return false;
1711									}
1712								}
1713								// Reverse direction for :only-* (if we haven't yet done so)
1714								start = dir = type === "only" && !start && "nextSibling";
1715							}
1716							return true;
1717						}
1718
1719						start = [ forward ? parent.firstChild : parent.lastChild ];
1720
1721						// non-xml :nth-child(...) stores cache data on `parent`
1722						if ( forward && useCache ) {
1723							// Seek `elem` from a previously-cached index
1724							outerCache = parent[ expando ] || (parent[ expando ] = {});
1725							cache = outerCache[ type ] || [];
1726							nodeIndex = cache[0] === dirruns && cache[1];
1727							diff = cache[0] === dirruns && cache[2];
1728							node = nodeIndex && parent.childNodes[ nodeIndex ];
1729
1730							while ( (node = ++nodeIndex && node && node[ dir ] ||
1731
1732								// Fallback to seeking `elem` from the start
1733								(diff = nodeIndex = 0) || start.pop()) ) {
1734
1735								// When found, cache indexes on `parent` and break
1736								if ( node.nodeType === 1 && ++diff && node === elem ) {
1737									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
1738									break;
1739								}
1740							}
1741
1742						// Use previously-cached element index if available
1743						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
1744							diff = cache[1];
1745
1746						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
1747						} else {
1748							// Use the same loop as above to seek `elem` from the start
1749							while ( (node = ++nodeIndex && node && node[ dir ] ||
1750								(diff = nodeIndex = 0) || start.pop()) ) {
1751
1752								if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
1753									// Cache the index of each encountered element
1754									if ( useCache ) {
1755										(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
1756									}
1757
1758									if ( node === elem ) {
1759										break;
1760									}
1761								}
1762							}
1763						}
1764
1765						// Incorporate the offset, then check against cycle size
1766						diff -= last;
1767						return diff === first || ( diff % first === 0 && diff / first >= 0 );
1768					}
1769				};
1770		},
1771
1772		"PSEUDO": function( pseudo, argument ) {
1773			// pseudo-class names are case-insensitive
1774			// http://www.w3.org/TR/selectors/#pseudo-classes
1775			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
1776			// Remember that setFilters inherits from pseudos
1777			var args,
1778				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
1779					Sizzle.error( "unsupported pseudo: " + pseudo );
1780
1781			// The user may use createPseudo to indicate that
1782			// arguments are needed to create the filter function
1783			// just as Sizzle does
1784			if ( fn[ expando ] ) {
1785				return fn( argument );
1786			}
1787
1788			// But maintain support for old signatures
1789			if ( fn.length > 1 ) {
1790				args = [ pseudo, pseudo, "", argument ];
1791				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
1792					markFunction(function( seed, matches ) {
1793						var idx,
1794							matched = fn( seed, argument ),
1795							i = matched.length;
1796						while ( i-- ) {
1797							idx = indexOf.call( seed, matched[i] );
1798							seed[ idx ] = !( matches[ idx ] = matched[i] );
1799						}
1800					}) :
1801					function( elem ) {
1802						return fn( elem, 0, args );
1803					};
1804			}
1805
1806			return fn;
1807		}
1808	},
1809
1810	pseudos: {
1811		// Potentially complex pseudos
1812		"not": markFunction(function( selector ) {
1813			// Trim the selector passed to compile
1814			// to avoid treating leading and trailing
1815			// spaces as combinators
1816			var input = [],
1817				results = [],
1818				matcher = compile( selector.replace( rtrim, "$1" ) );
1819
1820			return matcher[ expando ] ?
1821				markFunction(function( seed, matches, context, xml ) {
1822					var elem,
1823						unmatched = matcher( seed, null, xml, [] ),
1824						i = seed.length;
1825
1826					// Match elements unmatched by `matcher`
1827					while ( i-- ) {
1828						if ( (elem = unmatched[i]) ) {
1829							seed[i] = !(matches[i] = elem);
1830						}
1831					}
1832				}) :
1833				function( elem, context, xml ) {
1834					input[0] = elem;
1835					matcher( input, null, xml, results );
1836					return !results.pop();
1837				};
1838		}),
1839
1840		"has": markFunction(function( selector ) {
1841			return function( elem ) {
1842				return Sizzle( selector, elem ).length > 0;
1843			};
1844		}),
1845
1846		"contains": markFunction(function( text ) {
1847			return function( elem ) {
1848				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
1849			};
1850		}),
1851
1852		// "Whether an element is represented by a :lang() selector
1853		// is based solely on the element's language value
1854		// being equal to the identifier C,
1855		// or beginning with the identifier C immediately followed by "-".
1856		// The matching of C against the element's language value is performed case-insensitively.
1857		// The identifier C does not have to be a valid language name."
1858		// http://www.w3.org/TR/selectors/#lang-pseudo
1859		"lang": markFunction( function( lang ) {
1860			// lang value must be a valid identifier
1861			if ( !ridentifier.test(lang || "") ) {
1862				Sizzle.error( "unsupported lang: " + lang );
1863			}
1864			lang = lang.replace( runescape, funescape ).toLowerCase();
1865			return function( elem ) {
1866				var elemLang;
1867				do {
1868					if ( (elemLang = documentIsHTML ?
1869						elem.lang :
1870						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
1871
1872						elemLang = elemLang.toLowerCase();
1873						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
1874					}
1875				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
1876				return false;
1877			};
1878		}),
1879
1880		// Miscellaneous
1881		"target": function( elem ) {
1882			var hash = window.location && window.location.hash;
1883			return hash && hash.slice( 1 ) === elem.id;
1884		},
1885
1886		"root": function( elem ) {
1887			return elem === docElem;
1888		},
1889
1890		"focus": function( elem ) {
1891			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
1892		},
1893
1894		// Boolean properties
1895		"enabled": function( elem ) {
1896			return elem.disabled === false;
1897		},
1898
1899		"disabled": function( elem ) {
1900			return elem.disabled === true;
1901		},
1902
1903		"checked": function( elem ) {
1904			// In CSS3, :checked should return both checked and selected elements
1905			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
1906			var nodeName = elem.nodeName.toLowerCase();
1907			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
1908		},
1909
1910		"selected": function( elem ) {
1911			// Accessing this property makes selected-by-default
1912			// options in Safari work properly
1913			if ( elem.parentNode ) {
1914				elem.parentNode.selectedIndex;
1915			}
1916
1917			return elem.selected === true;
1918		},
1919
1920		// Contents
1921		"empty": function( elem ) {
1922			// http://www.w3.org/TR/selectors/#empty-pseudo
1923			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
1924			//   but not by others (comment: 8; processing instruction: 7; etc.)
1925			// nodeType < 6 works because attributes (2) do not appear as children
1926			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
1927				if ( elem.nodeType < 6 ) {
1928					return false;
1929				}
1930			}
1931			return true;
1932		},
1933
1934		"parent": function( elem ) {
1935			return !Expr.pseudos["empty"]( elem );
1936		},
1937
1938		// Element/input types
1939		"header": function( elem ) {
1940			return rheader.test( elem.nodeName );
1941		},
1942
1943		"input": function( elem ) {
1944			return rinputs.test( elem.nodeName );
1945		},
1946
1947		"button": function( elem ) {
1948			var name = elem.nodeName.toLowerCase();
1949			return name === "input" && elem.type === "button" || name === "button";
1950		},
1951
1952		"text": function( elem ) {
1953			var attr;
1954			return elem.nodeName.toLowerCase() === "input" &&
1955				elem.type === "text" &&
1956
1957				// Support: IE<8
1958				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
1959				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
1960		},
1961
1962		// Position-in-collection
1963		"first": createPositionalPseudo(function() {
1964			return [ 0 ];
1965		}),
1966
1967		"last": createPositionalPseudo(function( matchIndexes, length ) {
1968			return [ length - 1 ];
1969		}),
1970
1971		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
1972			return [ argument < 0 ? argument + length : argument ];
1973		}),
1974
1975		"even": createPositionalPseudo(function( matchIndexes, length ) {
1976			var i = 0;
1977			for ( ; i < length; i += 2 ) {
1978				matchIndexes.push( i );
1979			}
1980			return matchIndexes;
1981		}),
1982
1983		"odd": createPositionalPseudo(function( matchIndexes, length ) {
1984			var i = 1;
1985			for ( ; i < length; i += 2 ) {
1986				matchIndexes.push( i );
1987			}
1988			return matchIndexes;
1989		}),
1990
1991		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
1992			var i = argument < 0 ? argument + length : argument;
1993			for ( ; --i >= 0; ) {
1994				matchIndexes.push( i );
1995			}
1996			return matchIndexes;
1997		}),
1998
1999		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
2000			var i = argument < 0 ? argument + length : argument;
2001			for ( ; ++i < length; ) {
2002				matchIndexes.push( i );
2003			}
2004			return matchIndexes;
2005		})
2006	}
2007};
2008
2009Expr.pseudos["nth"] = Expr.pseudos["eq"];
2010
2011// Add button/input type pseudos
2012for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
2013	Expr.pseudos[ i ] = createInputPseudo( i );
2014}
2015for ( i in { submit: true, reset: true } ) {
2016	Expr.pseudos[ i ] = createButtonPseudo( i );
2017}
2018
2019// Easy API for creating new setFilters
2020function setFilters() {}
2021setFilters.prototype = Expr.filters = Expr.pseudos;
2022Expr.setFilters = new setFilters();
2023
2024function tokenize( selector, parseOnly ) {
2025	var matched, match, tokens, type,
2026		soFar, groups, preFilters,
2027		cached = tokenCache[ selector + " " ];
2028
2029	if ( cached ) {
2030		return parseOnly ? 0 : cached.slice( 0 );
2031	}
2032
2033	soFar = selector;
2034	groups = [];
2035	preFilters = Expr.preFilter;
2036
2037	while ( soFar ) {
2038
2039		// Comma and first run
2040		if ( !matched || (match = rcomma.exec( soFar )) ) {
2041			if ( match ) {
2042				// Don't consume trailing commas as valid
2043				soFar = soFar.slice( match[0].length ) || soFar;
2044			}
2045			groups.push( (tokens = []) );
2046		}
2047
2048		matched = false;
2049
2050		// Combinators
2051		if ( (match = rcombinators.exec( soFar )) ) {
2052			matched = match.shift();
2053			tokens.push({
2054				value: matched,
2055				// Cast descendant combinators to space
2056				type: match[0].replace( rtrim, " " )
2057			});
2058			soFar = soFar.slice( matched.length );
2059		}
2060
2061		// Filters
2062		for ( type in Expr.filter ) {
2063			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
2064				(match = preFilters[ type ]( match ))) ) {
2065				matched = match.shift();
2066				tokens.push({
2067					value: matched,
2068					type: type,
2069					matches: match
2070				});
2071				soFar = soFar.slice( matched.length );
2072			}
2073		}
2074
2075		if ( !matched ) {
2076			break;
2077		}
2078	}
2079
2080	// Return the length of the invalid excess
2081	// if we're just parsing
2082	// Otherwise, throw an error or return tokens
2083	return parseOnly ?
2084		soFar.length :
2085		soFar ?
2086			Sizzle.error( selector ) :
2087			// Cache the tokens
2088			tokenCache( selector, groups ).slice( 0 );
2089}
2090
2091function toSelector( tokens ) {
2092	var i = 0,
2093		len = tokens.length,
2094		selector = "";
2095	for ( ; i < len; i++ ) {
2096		selector += tokens[i].value;
2097	}
2098	return selector;
2099}
2100
2101function addCombinator( matcher, combinator, base ) {
2102	var dir = combinator.dir,
2103		checkNonElements = base && dir === "parentNode",
2104		doneName = done++;
2105
2106	return combinator.first ?
2107		// Check against closest ancestor/preceding element
2108		function( elem, context, xml ) {
2109			while ( (elem = elem[ dir ]) ) {
2110				if ( elem.nodeType === 1 || checkNonElements ) {
2111					return matcher( elem, context, xml );
2112				}
2113			}
2114		} :
2115
2116		// Check against all ancestor/preceding elements
2117		function( elem, context, xml ) {
2118			var oldCache, outerCache,
2119				newCache = [ dirruns, doneName ];
2120
2121			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
2122			if ( xml ) {
2123				while ( (elem = elem[ dir ]) ) {
2124					if ( elem.nodeType === 1 || checkNonElements ) {
2125						if ( matcher( elem, context, xml ) ) {
2126							return true;
2127						}
2128					}
2129				}
2130			} else {
2131				while ( (elem = elem[ dir ]) ) {
2132					if ( elem.nodeType === 1 || checkNonElements ) {
2133						outerCache = elem[ expando ] || (elem[ expando ] = {});
2134						if ( (oldCache = outerCache[ dir ]) &&
2135							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
2136
2137							// Assign to newCache so results back-propagate to previous elements
2138							return (newCache[ 2 ] = oldCache[ 2 ]);
2139						} else {
2140							// Reuse newcache so results back-propagate to previous elements
2141							outerCache[ dir ] = newCache;
2142
2143							// A match means we're done; a fail means we have to keep checking
2144							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
2145								return true;
2146							}
2147						}
2148					}
2149				}
2150			}
2151		};
2152}
2153
2154function elementMatcher( matchers ) {
2155	return matchers.length > 1 ?
2156		function( elem, context, xml ) {
2157			var i = matchers.length;
2158			while ( i-- ) {
2159				if ( !matchers[i]( elem, context, xml ) ) {
2160					return false;
2161				}
2162			}
2163			return true;
2164		} :
2165		matchers[0];
2166}
2167
2168function condense( unmatched, map, filter, context, xml ) {
2169	var elem,
2170		newUnmatched = [],
2171		i = 0,
2172		len = unmatched.length,
2173		mapped = map != null;
2174
2175	for ( ; i < len; i++ ) {
2176		if ( (elem = unmatched[i]) ) {
2177			if ( !filter || filter( elem, context, xml ) ) {
2178				newUnmatched.push( elem );
2179				if ( mapped ) {
2180					map.push( i );
2181				}
2182			}
2183		}
2184	}
2185
2186	return newUnmatched;
2187}
2188
2189function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
2190	if ( postFilter && !postFilter[ expando ] ) {
2191		postFilter = setMatcher( postFilter );
2192	}
2193	if ( postFinder && !postFinder[ expando ] ) {
2194		postFinder = setMatcher( postFinder, postSelector );
2195	}
2196	return markFunction(function( seed, results, context, xml ) {
2197		var temp, i, elem,
2198			preMap = [],
2199			postMap = [],
2200			preexisting = results.length,
2201
2202			// Get initial elements from seed or context
2203			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
2204
2205			// Prefilter to get matcher input, preserving a map for seed-results synchronization
2206			matcherIn = preFilter && ( seed || !selector ) ?
2207				condense( elems, preMap, preFilter, context, xml ) :
2208				elems,
2209
2210			matcherOut = matcher ?
2211				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
2212				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
2213
2214					// ...intermediate processing is necessary
2215					[] :
2216
2217					// ...otherwise use results directly
2218					results :
2219				matcherIn;
2220
2221		// Find primary matches
2222		if ( matcher ) {
2223			matcher( matcherIn, matcherOut, context, xml );
2224		}
2225
2226		// Apply postFilter
2227		if ( postFilter ) {
2228			temp = condense( matcherOut, postMap );
2229			postFilter( temp, [], context, xml );
2230
2231			// Un-match failing elements by moving them back to matcherIn
2232			i = temp.length;
2233			while ( i-- ) {
2234				if ( (elem = temp[i]) ) {
2235					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
2236				}
2237			}
2238		}
2239
2240		if ( seed ) {
2241			if ( postFinder || preFilter ) {
2242				if ( postFinder ) {
2243					// Get the final matcherOut by condensing this intermediate into postFinder contexts
2244					temp = [];
2245					i = matcherOut.length;
2246					while ( i-- ) {
2247						if ( (elem = matcherOut[i]) ) {
2248							// Restore matcherIn since elem is not yet a final match
2249							temp.push( (matcherIn[i] = elem) );
2250						}
2251					}
2252					postFinder( null, (matcherOut = []), temp, xml );
2253				}
2254
2255				// Move matched elements from seed to results to keep them synchronized
2256				i = matcherOut.length;
2257				while ( i-- ) {
2258					if ( (elem = matcherOut[i]) &&
2259						(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
2260
2261						seed[temp] = !(results[temp] = elem);
2262					}
2263				}
2264			}
2265
2266		// Add elements to results, through postFinder if defined
2267		} else {
2268			matcherOut = condense(
2269				matcherOut === results ?
2270					matcherOut.splice( preexisting, matcherOut.length ) :
2271					matcherOut
2272			);
2273			if ( postFinder ) {
2274				postFinder( null, results, matcherOut, xml );
2275			} else {
2276				push.apply( results, matcherOut );
2277			}
2278		}
2279	});
2280}
2281
2282function matcherFromTokens( tokens ) {
2283	var checkContext, matcher, j,
2284		len = tokens.length,
2285		leadingRelative = Expr.relative[ tokens[0].type ],
2286		implicitRelative = leadingRelative || Expr.relative[" "],
2287		i = leadingRelative ? 1 : 0,
2288
2289		// The foundational matcher ensures that elements are reachable from top-level context(s)
2290		matchContext = addCombinator( function( elem ) {
2291			return elem === checkContext;
2292		}, implicitRelative, true ),
2293		matchAnyContext = addCombinator( function( elem ) {
2294			return indexOf.call( checkContext, elem ) > -1;
2295		}, implicitRelative, true ),
2296		matchers = [ function( elem, context, xml ) {
2297			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
2298				(checkContext = context).nodeType ?
2299					matchContext( elem, context, xml ) :
2300					matchAnyContext( elem, context, xml ) );
2301		} ];
2302
2303	for ( ; i < len; i++ ) {
2304		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
2305			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
2306		} else {
2307			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
2308
2309			// Return special upon seeing a positional matcher
2310			if ( matcher[ expando ] ) {
2311				// Find the next relative operator (if any) for proper handling
2312				j = ++i;
2313				for ( ; j < len; j++ ) {
2314					if ( Expr.relative[ tokens[j].type ] ) {
2315						break;
2316					}
2317				}
2318				return setMatcher(
2319					i > 1 && elementMatcher( matchers ),
2320					i > 1 && toSelector(
2321						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
2322						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
2323					).replace( rtrim, "$1" ),
2324					matcher,
2325					i < j && matcherFromTokens( tokens.slice( i, j ) ),
2326					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
2327					j < len && toSelector( tokens )
2328				);
2329			}
2330			matchers.push( matcher );
2331		}
2332	}
2333
2334	return elementMatcher( matchers );
2335}
2336
2337function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
2338	var bySet = setMatchers.length > 0,
2339		byElement = elementMatchers.length > 0,
2340		superMatcher = function( seed, context, xml, results, outermost ) {
2341			var elem, j, matcher,
2342				matchedCount = 0,
2343				i = "0",
2344				unmatched = seed && [],
2345				setMatched = [],
2346				contextBackup = outermostContext,
2347				// We must always have either seed elements or outermost context
2348				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
2349				// Use integer dirruns iff this is the outermost matcher
2350				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
2351				len = elems.length;
2352
2353			if ( outermost ) {
2354				outermostContext = context !== document && context;
2355			}
2356
2357			// Add elements passing elementMatchers directly to results
2358			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
2359			// Support: IE<9, Safari
2360			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
2361			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
2362				if ( byElement && elem ) {
2363					j = 0;
2364					while ( (matcher = elementMatchers[j++]) ) {
2365						if ( matcher( elem, context, xml ) ) {
2366							results.push( elem );
2367							break;
2368						}
2369					}
2370					if ( outermost ) {
2371						dirruns = dirrunsUnique;
2372					}
2373				}
2374
2375				// Track unmatched elements for set filters
2376				if ( bySet ) {
2377					// They will have gone through all possible matchers
2378					if ( (elem = !matcher && elem) ) {
2379						matchedCount--;
2380					}
2381
2382					// Lengthen the array for every element, matched or not
2383					if ( seed ) {
2384						unmatched.push( elem );
2385					}
2386				}
2387			}
2388
2389			// Apply set filters to unmatched elements
2390			matchedCount += i;
2391			if ( bySet && i !== matchedCount ) {
2392				j = 0;
2393				while ( (matcher = setMatchers[j++]) ) {
2394					matcher( unmatched, setMatched, context, xml );
2395				}
2396
2397				if ( seed ) {
2398					// Reintegrate element matches to eliminate the need for sorting
2399					if ( matchedCount > 0 ) {
2400						while ( i-- ) {
2401							if ( !(unmatched[i] || setMatched[i]) ) {
2402								setMatched[i] = pop.call( results );
2403							}
2404						}
2405					}
2406
2407					// Discard index placeholder values to get only actual matches
2408					setMatched = condense( setMatched );
2409				}
2410
2411				// Add matches to results
2412				push.apply( results, setMatched );
2413
2414				// Seedless set matches succeeding multiple successful matchers stipulate sorting
2415				if ( outermost && !seed && setMatched.length > 0 &&
2416					( matchedCount + setMatchers.length ) > 1 ) {
2417
2418					Sizzle.uniqueSort( results );
2419				}
2420			}
2421
2422			// Override manipulation of globals by nested matchers
2423			if ( outermost ) {
2424				dirruns = dirrunsUnique;
2425				outermostContext = contextBackup;
2426			}
2427
2428			return unmatched;
2429		};
2430
2431	return bySet ?
2432		markFunction( superMatcher ) :
2433		superMatcher;
2434}
2435
2436compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
2437	var i,
2438		setMatchers = [],
2439		elementMatchers = [],
2440		cached = compilerCache[ selector + " " ];
2441
2442	if ( !cached ) {
2443		// Generate a function of recursive functions that can be used to check each element
2444		if ( !group ) {
2445			group = tokenize( selector );
2446		}
2447		i = group.length;
2448		while ( i-- ) {
2449			cached = matcherFromTokens( group[i] );
2450			if ( cached[ expando ] ) {
2451				setMatchers.push( cached );
2452			} else {
2453				elementMatchers.push( cached );
2454			}
2455		}
2456
2457		// Cache the compiled function
2458		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
2459	}
2460	return cached;
2461};
2462
2463function multipleContexts( selector, contexts, results ) {
2464	var i = 0,
2465		len = contexts.length;
2466	for ( ; i < len; i++ ) {
2467		Sizzle( selector, contexts[i], results );
2468	}
2469	return results;
2470}
2471
2472function select( selector, context, results, seed ) {
2473	var i, tokens, token, type, find,
2474		match = tokenize( selector );
2475
2476	if ( !seed ) {
2477		// Try to minimize operations if there is only one group
2478		if ( match.length === 1 ) {
2479
2480			// Take a shortcut and set the context if the root selector is an ID
2481			tokens = match[0] = match[0].slice( 0 );
2482			if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
2483					support.getById && context.nodeType === 9 && documentIsHTML &&
2484					Expr.relative[ tokens[1].type ] ) {
2485
2486				context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
2487				if ( !context ) {
2488					return results;
2489				}
2490				selector = selector.slice( tokens.shift().value.length );
2491			}
2492
2493			// Fetch a seed set for right-to-left matching
2494			i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
2495			while ( i-- ) {
2496				token = tokens[i];
2497
2498				// Abort if we hit a combinator
2499				if ( Expr.relative[ (type = token.type) ] ) {
2500					break;
2501				}
2502				if ( (find = Expr.find[ type ]) ) {
2503					// Search, expanding context for leading sibling combinators
2504					if ( (seed = find(
2505						token.matches[0].replace( runescape, funescape ),
2506						rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
2507					)) ) {
2508
2509						// If seed is empty or no tokens remain, we can return early
2510						tokens.splice( i, 1 );
2511						selector = seed.length && toSelector( tokens );
2512						if ( !selector ) {
2513							push.apply( results, seed );
2514							return results;
2515						}
2516
2517						break;
2518					}
2519				}
2520			}
2521		}
2522	}
2523
2524	// Compile and execute a filtering function
2525	// Provide `match` to avoid retokenization if we modified the selector above
2526	compile( selector, match )(
2527		seed,
2528		context,
2529		!documentIsHTML,
2530		results,
2531		rsibling.test( selector ) && testContext( context.parentNode ) || context
2532	);
2533	return results;
2534}
2535
2536// One-time assignments
2537
2538// Sort stability
2539support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
2540
2541// Support: Chrome<14
2542// Always assume duplicates if they aren't passed to the comparison function
2543support.detectDuplicates = !!hasDuplicate;
2544
2545// Initialize against the default document
2546setDocument();
2547
2548// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
2549// Detached nodes confoundingly follow *each other*
2550support.sortDetached = assert(function( div1 ) {
2551	// Should return 1, but returns 4 (following)
2552	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
2553});
2554
2555// Support: IE<8
2556// Prevent attribute/property "interpolation"
2557// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
2558if ( !assert(function( div ) {
2559	div.innerHTML = "<a href='#'></a>";
2560	return div.firstChild.getAttribute("href") === "#" ;
2561}) ) {
2562	addHandle( "type|href|height|width", function( elem, name, isXML ) {
2563		if ( !isXML ) {
2564			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
2565		}
2566	});
2567}
2568
2569// Support: IE<9
2570// Use defaultValue in place of getAttribute("value")
2571if ( !support.attributes || !assert(function( div ) {
2572	div.innerHTML = "<input/>";
2573	div.firstChild.setAttribute( "value", "" );
2574	return div.firstChild.getAttribute( "value" ) === "";
2575}) ) {
2576	addHandle( "value", function( elem, name, isXML ) {
2577		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
2578			return elem.defaultValue;
2579		}
2580	});
2581}
2582
2583// Support: IE<9
2584// Use getAttributeNode to fetch booleans when getAttribute lies
2585if ( !assert(function( div ) {
2586	return div.getAttribute("disabled") == null;
2587}) ) {
2588	addHandle( booleans, function( elem, name, isXML ) {
2589		var val;
2590		if ( !isXML ) {
2591			return elem[ name ] === true ? name.toLowerCase() :
2592					(val = elem.getAttributeNode( name )) && val.specified ?
2593					val.value :
2594				null;
2595		}
2596	});
2597}
2598
2599return Sizzle;
2600
2601})( window );
2602
2603
2604
2605jQuery.find = Sizzle;
2606jQuery.expr = Sizzle.selectors;
2607jQuery.expr[":"] = jQuery.expr.pseudos;
2608jQuery.unique = Sizzle.uniqueSort;
2609jQuery.text = Sizzle.getText;
2610jQuery.isXMLDoc = Sizzle.isXML;
2611jQuery.contains = Sizzle.contains;
2612
2613
2614
2615var rneedsContext = jQuery.expr.match.needsContext;
2616
2617var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
2618
2619
2620
2621var risSimple = /^.[^:#\[\.,]*$/;
2622
2623// Implement the identical functionality for filter and not
2624function winnow( elements, qualifier, not ) {
2625	if ( jQuery.isFunction( qualifier ) ) {
2626		return jQuery.grep( elements, function( elem, i ) {
2627			/* jshint -W018 */
2628			return !!qualifier.call( elem, i, elem ) !== not;
2629		});
2630
2631	}
2632
2633	if ( qualifier.nodeType ) {
2634		return jQuery.grep( elements, function( elem ) {
2635			return ( elem === qualifier ) !== not;
2636		});
2637
2638	}
2639
2640	if ( typeof qualifier === "string" ) {
2641		if ( risSimple.test( qualifier ) ) {
2642			return jQuery.filter( qualifier, elements, not );
2643		}
2644
2645		qualifier = jQuery.filter( qualifier, elements );
2646	}
2647
2648	return jQuery.grep( elements, function( elem ) {
2649		return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
2650	});
2651}
2652
2653jQuery.filter = function( expr, elems, not ) {
2654	var elem = elems[ 0 ];
2655
2656	if ( not ) {
2657		expr = ":not(" + expr + ")";
2658	}
2659
2660	return elems.length === 1 && elem.nodeType === 1 ?
2661		jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
2662		jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
2663			return elem.nodeType === 1;
2664		}));
2665};
2666
2667jQuery.fn.extend({
2668	find: function( selector ) {
2669		var i,
2670			ret = [],
2671			self = this,
2672			len = self.length;
2673
2674		if ( typeof selector !== "string" ) {
2675			return this.pushStack( jQuery( selector ).filter(function() {
2676				for ( i = 0; i < len; i++ ) {
2677					if ( jQuery.contains( self[ i ], this ) ) {
2678						return true;
2679					}
2680				}
2681			}) );
2682		}
2683
2684		for ( i = 0; i < len; i++ ) {
2685			jQuery.find( selector, self[ i ], ret );
2686		}
2687
2688		// Needed because $( selector, context ) becomes $( context ).find( selector )
2689		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
2690		ret.selector = this.selector ? this.selector + " " + selector : selector;
2691		return ret;
2692	},
2693	filter: function( selector ) {
2694		return this.pushStack( winnow(this, selector || [], false) );
2695	},
2696	not: function( selector ) {
2697		return this.pushStack( winnow(this, selector || [], true) );
2698	},
2699	is: function( selector ) {
2700		return !!winnow(
2701			this,
2702
2703			// If this is a positional/relative selector, check membership in the returned set
2704			// so $("p:first").is("p:last") won't return true for a doc with two "p".
2705			typeof selector === "string" && rneedsContext.test( selector ) ?
2706				jQuery( selector ) :
2707				selector || [],
2708			false
2709		).length;
2710	}
2711});
2712
2713
2714// Initialize a jQuery object
2715
2716
2717// A central reference to the root jQuery(document)
2718var rootjQuery,
2719
2720	// Use the correct document accordingly with window argument (sandbox)
2721	document = window.document,
2722
2723	// A simple way to check for HTML strings
2724	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
2725	// Strict HTML recognition (#11290: must start with <)
2726	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
2727
2728	init = jQuery.fn.init = function( selector, context ) {
2729		var match, elem;
2730
2731		// HANDLE: $(""), $(null), $(undefined), $(false)
2732		if ( !selector ) {
2733			return this;
2734		}
2735
2736		// Handle HTML strings
2737		if ( typeof selector === "string" ) {
2738			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
2739				// Assume that strings that start and end with <> are HTML and skip the regex check
2740				match = [ null, selector, null ];
2741
2742			} else {
2743				match = rquickExpr.exec( selector );
2744			}
2745
2746			// Match html or make sure no context is specified for #id
2747			if ( match && (match[1] || !context) ) {
2748
2749				// HANDLE: $(html) -> $(array)
2750				if ( match[1] ) {
2751					context = context instanceof jQuery ? context[0] : context;
2752
2753					// scripts is true for back-compat
2754					// Intentionally let the error be thrown if parseHTML is not present
2755					jQuery.merge( this, jQuery.parseHTML(
2756						match[1],
2757						context && context.nodeType ? context.ownerDocument || context : document,
2758						true
2759					) );
2760
2761					// HANDLE: $(html, props)
2762					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
2763						for ( match in context ) {
2764							// Properties of context are called as methods if possible
2765							if ( jQuery.isFunction( this[ match ] ) ) {
2766								this[ match ]( context[ match ] );
2767
2768							// ...and otherwise set as attributes
2769							} else {
2770								this.attr( match, context[ match ] );
2771							}
2772						}
2773					}
2774
2775					return this;
2776
2777				// HANDLE: $(#id)
2778				} else {
2779					elem = document.getElementById( match[2] );
2780
2781					// Check parentNode to catch when Blackberry 4.6 returns
2782					// nodes that are no longer in the document #6963
2783					if ( elem && elem.parentNode ) {
2784						// Handle the case where IE and Opera return items
2785						// by name instead of ID
2786						if ( elem.id !== match[2] ) {
2787							return rootjQuery.find( selector );
2788						}
2789
2790						// Otherwise, we inject the element directly into the jQuery object
2791						this.length = 1;
2792						this[0] = elem;
2793					}
2794
2795					this.context = document;
2796					this.selector = selector;
2797					return this;
2798				}
2799
2800			// HANDLE: $(expr, $(...))
2801			} else if ( !context || context.jquery ) {
2802				return ( context || rootjQuery ).find( selector );
2803
2804			// HANDLE: $(expr, context)
2805			// (which is just equivalent to: $(context).find(expr)
2806			} else {
2807				return this.constructor( context ).find( selector );
2808			}
2809
2810		// HANDLE: $(DOMElement)
2811		} else if ( selector.nodeType ) {
2812			this.context = this[0] = selector;
2813			this.length = 1;
2814			return this;
2815
2816		// HANDLE: $(function)
2817		// Shortcut for document ready
2818		} else if ( jQuery.isFunction( selector ) ) {
2819			return typeof rootjQuery.ready !== "undefined" ?
2820				rootjQuery.ready( selector ) :
2821				// Execute immediately if ready is not present
2822				selector( jQuery );
2823		}
2824
2825		if ( selector.selector !== undefined ) {
2826			this.selector = selector.selector;
2827			this.context = selector.context;
2828		}
2829
2830		return jQuery.makeArray( selector, this );
2831	};
2832
2833// Give the init function the jQuery prototype for later instantiation
2834init.prototype = jQuery.fn;
2835
2836// Initialize central reference
2837rootjQuery = jQuery( document );
2838
2839
2840var rparentsprev = /^(?:parents|prev(?:Until|All))/,
2841	// methods guaranteed to produce a unique set when starting from a unique set
2842	guaranteedUnique = {
2843		children: true,
2844		contents: true,
2845		next: true,
2846		prev: true
2847	};
2848
2849jQuery.extend({
2850	dir: function( elem, dir, until ) {
2851		var matched = [],
2852			cur = elem[ dir ];
2853
2854		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
2855			if ( cur.nodeType === 1 ) {
2856				matched.push( cur );
2857			}
2858			cur = cur[dir];
2859		}
2860		return matched;
2861	},
2862
2863	sibling: function( n, elem ) {
2864		var r = [];
2865
2866		for ( ; n; n = n.nextSibling ) {
2867			if ( n.nodeType === 1 && n !== elem ) {
2868				r.push( n );
2869			}
2870		}
2871
2872		return r;
2873	}
2874});
2875
2876jQuery.fn.extend({
2877	has: function( target ) {
2878		var i,
2879			targets = jQuery( target, this ),
2880			len = targets.length;
2881
2882		return this.filter(function() {
2883			for ( i = 0; i < len; i++ ) {
2884				if ( jQuery.contains( this, targets[i] ) ) {
2885					return true;
2886				}
2887			}
2888		});
2889	},
2890
2891	closest: function( selectors, context ) {
2892		var cur,
2893			i = 0,
2894			l = this.length,
2895			matched = [],
2896			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
2897				jQuery( selectors, context || this.context ) :
2898				0;
2899
2900		for ( ; i < l; i++ ) {
2901			for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
2902				// Always skip document fragments
2903				if ( cur.nodeType < 11 && (pos ?
2904					pos.index(cur) > -1 :
2905
2906					// Don't pass non-elements to Sizzle
2907					cur.nodeType === 1 &&
2908						jQuery.find.matchesSelector(cur, selectors)) ) {
2909
2910					matched.push( cur );
2911					break;
2912				}
2913			}
2914		}
2915
2916		return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
2917	},
2918
2919	// Determine the position of an element within
2920	// the matched set of elements
2921	index: function( elem ) {
2922
2923		// No argument, return index in parent
2924		if ( !elem ) {
2925			return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
2926		}
2927
2928		// index in selector
2929		if ( typeof elem === "string" ) {
2930			return jQuery.inArray( this[0], jQuery( elem ) );
2931		}
2932
2933		// Locate the position of the desired element
2934		return jQuery.inArray(
2935			// If it receives a jQuery object, the first element is used
2936			elem.jquery ? elem[0] : elem, this );
2937	},
2938
2939	add: function( selector, context ) {
2940		return this.pushStack(
2941			jQuery.unique(
2942				jQuery.merge( this.get(), jQuery( selector, context ) )
2943			)
2944		);
2945	},
2946
2947	addBack: function( selector ) {
2948		return this.add( selector == null ?
2949			this.prevObject : this.prevObject.filter(selector)
2950		);
2951	}
2952});
2953
2954function sibling( cur, dir ) {
2955	do {
2956		cur = cur[ dir ];
2957	} while ( cur && cur.nodeType !== 1 );
2958
2959	return cur;
2960}
2961
2962jQuery.each({
2963	parent: function( elem ) {
2964		var parent = elem.parentNode;
2965		return parent && parent.nodeType !== 11 ? parent : null;
2966	},
2967	parents: function( elem ) {
2968		return jQuery.dir( elem, "parentNode" );
2969	},
2970	parentsUntil: function( elem, i, until ) {
2971		return jQuery.dir( elem, "parentNode", until );
2972	},
2973	next: function( elem ) {
2974		return sibling( elem, "nextSibling" );
2975	},
2976	prev: function( elem ) {
2977		return sibling( elem, "previousSibling" );
2978	},
2979	nextAll: function( elem ) {
2980		return jQuery.dir( elem, "nextSibling" );
2981	},
2982	prevAll: function( elem ) {
2983		return jQuery.dir( elem, "previousSibling" );
2984	},
2985	nextUntil: function( elem, i, until ) {
2986		return jQuery.dir( elem, "nextSibling", until );
2987	},
2988	prevUntil: function( elem, i, until ) {
2989		return jQuery.dir( elem, "previousSibling", until );
2990	},
2991	siblings: function( elem ) {
2992		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
2993	},
2994	children: function( elem ) {
2995		return jQuery.sibling( elem.firstChild );
2996	},
2997	contents: function( elem ) {
2998		return jQuery.nodeName( elem, "iframe" ) ?
2999			elem.contentDocument || elem.contentWindow.document :
3000			jQuery.merge( [], elem.childNodes );
3001	}
3002}, function( name, fn ) {
3003	jQuery.fn[ name ] = function( until, selector ) {
3004		var ret = jQuery.map( this, fn, until );
3005
3006		if ( name.slice( -5 ) !== "Until" ) {
3007			selector = until;
3008		}
3009
3010		if ( selector && typeof selector === "string" ) {
3011			ret = jQuery.filter( selector, ret );
3012		}
3013
3014		if ( this.length > 1 ) {
3015			// Remove duplicates
3016			if ( !guaranteedUnique[ name ] ) {
3017				ret = jQuery.unique( ret );
3018			}
3019
3020			// Reverse order for parents* and prev-derivatives
3021			if ( rparentsprev.test( name ) ) {
3022				ret = ret.reverse();
3023			}
3024		}
3025
3026		return this.pushStack( ret );
3027	};
3028});
3029var rnotwhite = (/\S+/g);
3030
3031
3032
3033// String to Object options format cache
3034var optionsCache = {};
3035
3036// Convert String-formatted options into Object-formatted ones and store in cache
3037function createOptions( options ) {
3038	var object = optionsCache[ options ] = {};
3039	jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
3040		object[ flag ] = true;
3041	});
3042	return object;
3043}
3044
3045/*
3046 * Create a callback list using the following parameters:
3047 *
3048 *	options: an optional list of space-separated options that will change how
3049 *			the callback list behaves or a more traditional option object
3050 *
3051 * By default a callback list will act like an event callback list and can be
3052 * "fired" multiple times.
3053 *
3054 * Possible options:
3055 *
3056 *	once:			will ensure the callback list can only be fired once (like a Deferred)
3057 *
3058 *	memory:			will keep track of previous values and will call any callback added
3059 *					after the list has been fired right away with the latest "memorized"
3060 *					values (like a Deferred)
3061 *
3062 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
3063 *
3064 *	stopOnFalse:	interrupt callings when a callback returns false
3065 *
3066 */
3067jQuery.Callbacks = function( options ) {
3068
3069	// Convert options from String-formatted to Object-formatted if needed
3070	// (we check in cache first)
3071	options = typeof options === "string" ?
3072		( optionsCache[ options ] || createOptions( options ) ) :
3073		jQuery.extend( {}, options );
3074
3075	var // Flag to know if list is currently firing
3076		firing,
3077		// Last fire value (for non-forgettable lists)
3078		memory,
3079		// Flag to know if list was already fired
3080		fired,
3081		// End of the loop when firing
3082		firingLength,
3083		// Index of currently firing callback (modified by remove if needed)
3084		firingIndex,
3085		// First callback to fire (used internally by add and fireWith)
3086		firingStart,
3087		// Actual callback list
3088		list = [],
3089		// Stack of fire calls for repeatable lists
3090		stack = !options.once && [],
3091		// Fire callbacks
3092		fire = function( data ) {
3093			memory = options.memory && data;
3094			fired = true;
3095			firingIndex = firingStart || 0;
3096			firingStart = 0;
3097			firingLength = list.length;
3098			firing = true;
3099			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
3100				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
3101					memory = false; // To prevent further calls using add
3102					break;
3103				}
3104			}
3105			firing = false;
3106			if ( list ) {
3107				if ( stack ) {
3108					if ( stack.length ) {
3109						fire( stack.shift() );
3110					}
3111				} else if ( memory ) {
3112					list = [];
3113				} else {
3114					self.disable();
3115				}
3116			}
3117		},
3118		// Actual Callbacks object
3119		self = {
3120			// Add a callback or a collection of callbacks to the list
3121			add: function() {
3122				if ( list ) {
3123					// First, we save the current length
3124					var start = list.length;
3125					(function add( args ) {
3126						jQuery.each( args, function( _, arg ) {
3127							var type = jQuery.type( arg );
3128							if ( type === "function" ) {
3129								if ( !options.unique || !self.has( arg ) ) {
3130									list.push( arg );
3131								}
3132							} else if ( arg && arg.length && type !== "string" ) {
3133								// Inspect recursively
3134								add( arg );
3135							}
3136						});
3137					})( arguments );
3138					// Do we need to add the callbacks to the
3139					// current firing batch?
3140					if ( firing ) {
3141						firingLength = list.length;
3142					// With memory, if we're not firing then
3143					// we should call right away
3144					} else if ( memory ) {
3145						firingStart = start;
3146						fire( memory );
3147					}
3148				}
3149				return this;
3150			},
3151			// Remove a callback from the list
3152			remove: function() {
3153				if ( list ) {
3154					jQuery.each( arguments, function( _, arg ) {
3155						var index;
3156						while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
3157							list.splice( index, 1 );
3158							// Handle firing indexes
3159							if ( firing ) {
3160								if ( index <= firingLength ) {
3161									firingLength--;
3162								}
3163								if ( index <= firingIndex ) {
3164									firingIndex--;
3165								}
3166							}
3167						}
3168					});
3169				}
3170				return this;
3171			},
3172			// Check if a given callback is in the list.
3173			// If no argument is given, return whether or not list has callbacks attached.
3174			has: function( fn ) {
3175				return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
3176			},
3177			// Remove all callbacks from the list
3178			empty: function() {
3179				list = [];
3180				firingLength = 0;
3181				return this;
3182			},
3183			// Have the list do nothing anymore
3184			disable: function() {
3185				list = stack = memory = undefined;
3186				return this;
3187			},
3188			// Is it disabled?
3189			disabled: function() {
3190				return !list;
3191			},
3192			// Lock the list in its current state
3193			lock: function() {
3194				stack = undefined;
3195				if ( !memory ) {
3196					self.disable();
3197				}
3198				return this;
3199			},
3200			// Is it locked?
3201			locked: function() {
3202				return !stack;
3203			},
3204			// Call all callbacks with the given context and arguments
3205			fireWith: function( context, args ) {
3206				if ( list && ( !fired || stack ) ) {
3207					args = args || [];
3208					args = [ context, args.slice ? args.slice() : args ];
3209					if ( firing ) {
3210						stack.push( args );
3211					} else {
3212						fire( args );
3213					}
3214				}
3215				return this;
3216			},
3217			// Call all the callbacks with the given arguments
3218			fire: function() {
3219				self.fireWith( this, arguments );
3220				return this;
3221			},
3222			// To know if the callbacks have already been called at least once
3223			fired: function() {
3224				return !!fired;
3225			}
3226		};
3227
3228	return self;
3229};
3230
3231
3232jQuery.extend({
3233
3234	Deferred: function( func ) {
3235		var tuples = [
3236				// action, add listener, listener list, final state
3237				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
3238				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
3239				[ "notify", "progress", jQuery.Callbacks("memory") ]
3240			],
3241			state = "pending",
3242			promise = {
3243				state: function() {
3244					return state;
3245				},
3246				always: function() {
3247					deferred.done( arguments ).fail( arguments );
3248					return this;
3249				},
3250				then: function( /* fnDone, fnFail, fnProgress */ ) {
3251					var fns = arguments;
3252					return jQuery.Deferred(function( newDefer ) {
3253						jQuery.each( tuples, function( i, tuple ) {
3254							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
3255							// deferred[ done | fail | progress ] for forwarding actions to newDefer
3256							deferred[ tuple[1] ](function() {
3257								var returned = fn && fn.apply( this, arguments );
3258								if ( returned && jQuery.isFunction( returned.promise ) ) {
3259									returned.promise()
3260										.done( newDefer.resolve )
3261										.fail( newDefer.reject )
3262										.progress( newDefer.notify );
3263								} else {
3264									newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
3265								}
3266							});
3267						});
3268						fns = null;
3269					}).promise();
3270				},
3271				// Get a promise for this deferred
3272				// If obj is provided, the promise aspect is added to the object
3273				promise: function( obj ) {
3274					return obj != null ? jQuery.extend( obj, promise ) : promise;
3275				}
3276			},
3277			deferred = {};
3278
3279		// Keep pipe for back-compat
3280		promise.pipe = promise.then;
3281
3282		// Add list-specific methods
3283		jQuery.each( tuples, function( i, tuple ) {
3284			var list = tuple[ 2 ],
3285				stateString = tuple[ 3 ];
3286
3287			// promise[ done | fail | progress ] = list.add
3288			promise[ tuple[1] ] = list.add;
3289
3290			// Handle state
3291			if ( stateString ) {
3292				list.add(function() {
3293					// state = [ resolved | rejected ]
3294					state = stateString;
3295
3296				// [ reject_list | resolve_list ].disable; progress_list.lock
3297				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
3298			}
3299
3300			// deferred[ resolve | reject | notify ]
3301			deferred[ tuple[0] ] = function() {
3302				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
3303				return this;
3304			};
3305			deferred[ tuple[0] + "With" ] = list.fireWith;
3306		});
3307
3308		// Make the deferred a promise
3309		promise.promise( deferred );
3310
3311		// Call given func if any
3312		if ( func ) {
3313			func.call( deferred, deferred );
3314		}
3315
3316		// All done!
3317		return deferred;
3318	},
3319
3320	// Deferred helper
3321	when: function( subordinate /* , ..., subordinateN */ ) {
3322		var i = 0,
3323			resolveValues = slice.call( arguments ),
3324			length = resolveValues.length,
3325
3326			// the count of uncompleted subordinates
3327			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
3328
3329			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
3330			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
3331
3332			// Update function for both resolve and progress values
3333			updateFunc = function( i, contexts, values ) {
3334				return function( value ) {
3335					contexts[ i ] = this;
3336					values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
3337					if ( values === progressValues ) {
3338						deferred.notifyWith( contexts, values );
3339
3340					} else if ( !(--remaining) ) {
3341						deferred.resolveWith( contexts, values );
3342					}
3343				};
3344			},
3345
3346			progressValues, progressContexts, resolveContexts;
3347
3348		// add listeners to Deferred subordinates; treat others as resolved
3349		if ( length > 1 ) {
3350			progressValues = new Array( length );
3351			progressContexts = new Array( length );
3352			resolveContexts = new Array( length );
3353			for ( ; i < length; i++ ) {
3354				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
3355					resolveValues[ i ].promise()
3356						.done( updateFunc( i, resolveContexts, resolveValues ) )
3357						.fail( deferred.reject )
3358						.progress( updateFunc( i, progressContexts, progressValues ) );
3359				} else {
3360					--remaining;
3361				}
3362			}
3363		}
3364
3365		// if we're not waiting on anything, resolve the master
3366		if ( !remaining ) {
3367			deferred.resolveWith( resolveContexts, resolveValues );
3368		}
3369
3370		return deferred.promise();
3371	}
3372});
3373
3374
3375// The deferred used on DOM ready
3376var readyList;
3377
3378jQuery.fn.ready = function( fn ) {
3379	// Add the callback
3380	jQuery.ready.promise().done( fn );
3381
3382	return this;
3383};
3384
3385jQuery.extend({
3386	// Is the DOM ready to be used? Set to true once it occurs.
3387	isReady: false,
3388
3389	// A counter to track how many items to wait for before
3390	// the ready event fires. See #6781
3391	readyWait: 1,
3392
3393	// Hold (or release) the ready event
3394	holdReady: function( hold ) {
3395		if ( hold ) {
3396			jQuery.readyWait++;
3397		} else {
3398			jQuery.ready( true );
3399		}
3400	},
3401
3402	// Handle when the DOM is ready
3403	ready: function( wait ) {
3404
3405		// Abort if there are pending holds or we're already ready
3406		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
3407			return;
3408		}
3409
3410		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
3411		if ( !document.body ) {
3412			return setTimeout( jQuery.ready );
3413		}
3414
3415		// Remember that the DOM is ready
3416		jQuery.isReady = true;
3417
3418		// If a normal DOM Ready event fired, decrement, and wait if need be
3419		if ( wait !== true && --jQuery.readyWait > 0 ) {
3420			return;
3421		}
3422
3423		// If there are functions bound, to execute
3424		readyList.resolveWith( document, [ jQuery ] );
3425
3426		// Trigger any bound ready events
3427		if ( jQuery.fn.trigger ) {
3428			jQuery( document ).trigger("ready").off("ready");
3429		}
3430	}
3431});
3432
3433/**
3434 * Clean-up method for dom ready events
3435 */
3436function detach() {
3437	if ( document.addEventListener ) {
3438		document.removeEventListener( "DOMContentLoaded", completed, false );
3439		window.removeEventListener( "load", completed, false );
3440
3441	} else {
3442		document.detachEvent( "onreadystatechange", completed );
3443		window.detachEvent( "onload", completed );
3444	}
3445}
3446
3447/**
3448 * The ready event handler and self cleanup method
3449 */
3450function completed() {
3451	// readyState === "complete" is good enough for us to call the dom ready in oldIE
3452	if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
3453		detach();
3454		jQuery.ready();
3455	}
3456}
3457
3458jQuery.ready.promise = function( obj ) {
3459	if ( !readyList ) {
3460
3461		readyList = jQuery.Deferred();
3462
3463		// Catch cases where $(document).ready() is called after the browser event has already occurred.
3464		// we once tried to use readyState "interactive" here, but it caused issues like the one
3465		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
3466		if ( document.readyState === "complete" ) {
3467			// Handle it asynchronously to allow scripts the opportunity to delay ready
3468			setTimeout( jQuery.ready );
3469
3470		// Standards-based browsers support DOMContentLoaded
3471		} else if ( document.addEventListener ) {
3472			// Use the handy event callback
3473			document.addEventListener( "DOMContentLoaded", completed, false );
3474
3475			// A fallback to window.onload, that will always work
3476			window.addEventListener( "load", completed, false );
3477
3478		// If IE event model is used
3479		} else {
3480			// Ensure firing before onload, maybe late but safe also for iframes
3481			document.attachEvent( "onreadystatechange", completed );
3482
3483			// A fallback to window.onload, that will always work
3484			window.attachEvent( "onload", completed );
3485
3486			// If IE and not a frame
3487			// continually check to see if the document is ready
3488			var top = false;
3489
3490			try {
3491				top = window.frameElement == null && document.documentElement;
3492			} catch(e) {}
3493
3494			if ( top && top.doScroll ) {
3495				(function doScrollCheck() {
3496					if ( !jQuery.isReady ) {
3497
3498						try {
3499							// Use the trick by Diego Perini
3500							// http://javascript.nwbox.com/IEContentLoaded/
3501							top.doScroll("left");
3502						} catch(e) {
3503							return setTimeout( doScrollCheck, 50 );
3504						}
3505
3506						// detach all dom ready events
3507						detach();
3508
3509						// and execute any waiting functions
3510						jQuery.ready();
3511					}
3512				})();
3513			}
3514		}
3515	}
3516	return readyList.promise( obj );
3517};
3518
3519
3520var strundefined = typeof undefined;
3521
3522
3523
3524// Support: IE<9
3525// Iteration over object's inherited properties before its own
3526var i;
3527for ( i in jQuery( support ) ) {
3528	break;
3529}
3530support.ownLast = i !== "0";
3531
3532// Note: most support tests are defined in their respective modules.
3533// false until the test is run
3534support.inlineBlockNeedsLayout = false;
3535
3536jQuery(function() {
3537	// We need to execute this one support test ASAP because we need to know
3538	// if body.style.zoom needs to be set.
3539
3540	var container, div,
3541		body = document.getElementsByTagName("body")[0];
3542
3543	if ( !body ) {
3544		// Return for frameset docs that don't have a body
3545		return;
3546	}
3547
3548	// Setup
3549	container = document.createElement( "div" );
3550	container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
3551
3552	div = document.createElement( "div" );
3553	body.appendChild( container ).appendChild( div );
3554
3555	if ( typeof div.style.zoom !== strundefined ) {
3556		// Support: IE<8
3557		// Check if natively block-level elements act like inline-block
3558		// elements when setting their display to 'inline' and giving
3559		// them layout
3560		div.style.cssText = "border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1";
3561
3562		if ( (support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 )) ) {
3563			// Prevent IE 6 from affecting layout for positioned elements #11048
3564			// Prevent IE from shrinking the body in IE 7 mode #12869
3565			// Support: IE<8
3566			body.style.zoom = 1;
3567		}
3568	}
3569
3570	body.removeChild( container );
3571
3572	// Null elements to avoid leaks in IE
3573	container = div = null;
3574});
3575
3576
3577
3578
3579(function() {
3580	var div = document.createElement( "div" );
3581
3582	// Execute the test only if not already executed in another module.
3583	if (support.deleteExpando == null) {
3584		// Support: IE<9
3585		support.deleteExpando = true;
3586		try {
3587			delete div.test;
3588		} catch( e ) {
3589			support.deleteExpando = false;
3590		}
3591	}
3592
3593	// Null elements to avoid leaks in IE.
3594	div = null;
3595})();
3596
3597
3598/**
3599 * Determines whether an object can have data
3600 */
3601jQuery.acceptData = function( elem ) {
3602	var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ],
3603		nodeType = +elem.nodeType || 1;
3604
3605	// Do not set data on non-element DOM nodes because it will not be cleared (#8335).
3606	return nodeType !== 1 && nodeType !== 9 ?
3607		false :
3608
3609		// Nodes accept data unless otherwise specified; rejection can be conditional
3610		!noData || noData !== true && elem.getAttribute("classid") === noData;
3611};
3612
3613
3614var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
3615	rmultiDash = /([A-Z])/g;
3616
3617function dataAttr( elem, key, data ) {
3618	// If nothing was found internally, try to fetch any
3619	// data from the HTML5 data-* attribute
3620	if ( data === undefined && elem.nodeType === 1 ) {
3621
3622		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
3623
3624		data = elem.getAttribute( name );
3625
3626		if ( typeof data === "string" ) {
3627			try {
3628				data = data === "true" ? true :
3629					data === "false" ? false :
3630					data === "null" ? null :
3631					// Only convert to a number if it doesn't change the string
3632					+data + "" === data ? +data :
3633					rbrace.test( data ) ? jQuery.parseJSON( data ) :
3634					data;
3635			} catch( e ) {}
3636
3637			// Make sure we set the data so it isn't changed later
3638			jQuery.data( elem, key, data );
3639
3640		} else {
3641			data = undefined;
3642		}
3643	}
3644
3645	return data;
3646}
3647
3648// checks a cache object for emptiness
3649function isEmptyDataObject( obj ) {
3650	var name;
3651	for ( name in obj ) {
3652
3653		// if the public data object is empty, the private is still empty
3654		if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
3655			continue;
3656		}
3657		if ( name !== "toJSON" ) {
3658			return false;
3659		}
3660	}
3661
3662	return true;
3663}
3664
3665function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
3666	if ( !jQuery.acceptData( elem ) ) {
3667		return;
3668	}
3669
3670	var ret, thisCache,
3671		internalKey = jQuery.expando,
3672
3673		// We have to handle DOM nodes and JS objects differently because IE6-7
3674		// can't GC object references properly across the DOM-JS boundary
3675		isNode = elem.nodeType,
3676
3677		// Only DOM nodes need the global jQuery cache; JS object data is
3678		// attached directly to the object so GC can occur automatically
3679		cache = isNode ? jQuery.cache : elem,
3680
3681		// Only defining an ID for JS objects if its cache already exists allows
3682		// the code to shortcut on the same path as a DOM node with no cache
3683		id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
3684
3685	// Avoid doing any more work than we need to when trying to get data on an
3686	// object that has no data at all
3687	if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
3688		return;
3689	}
3690
3691	if ( !id ) {
3692		// Only DOM nodes need a new unique ID for each element since their data
3693		// ends up in the global cache
3694		if ( isNode ) {
3695			id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
3696		} else {
3697			id = internalKey;
3698		}
3699	}
3700
3701	if ( !cache[ id ] ) {
3702		// Avoid exposing jQuery metadata on plain JS objects when the object
3703		// is serialized using JSON.stringify
3704		cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
3705	}
3706
3707	// An object can be passed to jQuery.data instead of a key/value pair; this gets
3708	// shallow copied over onto the existing cache
3709	if ( typeof name === "object" || typeof name === "function" ) {
3710		if ( pvt ) {
3711			cache[ id ] = jQuery.extend( cache[ id ], name );
3712		} else {
3713			cache[ id ].data = jQuery.extend( cache[ id ].data, name );
3714		}
3715	}
3716
3717	thisCache = cache[ id ];
3718
3719	// jQuery data() is stored in a separate object inside the object's internal data
3720	// cache in order to avoid key collisions between internal data and user-defined
3721	// data.
3722	if ( !pvt ) {
3723		if ( !thisCache.data ) {
3724			thisCache.data = {};
3725		}
3726
3727		thisCache = thisCache.data;
3728	}
3729
3730	if ( data !== undefined ) {
3731		thisCache[ jQuery.camelCase( name ) ] = data;
3732	}
3733
3734	// Check for both converted-to-camel and non-converted data property names
3735	// If a data property was specified
3736	if ( typeof name === "string" ) {
3737
3738		// First Try to find as-is property data
3739		ret = thisCache[ name ];
3740
3741		// Test for null|undefined property data
3742		if ( ret == null ) {
3743
3744			// Try to find the camelCased property
3745			ret = thisCache[ jQuery.camelCase( name ) ];
3746		}
3747	} else {
3748		ret = thisCache;
3749	}
3750
3751	return ret;
3752}
3753
3754function internalRemoveData( elem, name, pvt ) {
3755	if ( !jQuery.acceptData( elem ) ) {
3756		return;
3757	}
3758
3759	var thisCache, i,
3760		isNode = elem.nodeType,
3761
3762		// See jQuery.data for more information
3763		cache = isNode ? jQuery.cache : elem,
3764		id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
3765
3766	// If there is already no cache entry for this object, there is no
3767	// purpose in continuing
3768	if ( !cache[ id ] ) {
3769		return;
3770	}
3771
3772	if ( name ) {
3773
3774		thisCache = pvt ? cache[ id ] : cache[ id ].data;
3775
3776		if ( thisCache ) {
3777
3778			// Support array or space separated string names for data keys
3779			if ( !jQuery.isArray( name ) ) {
3780
3781				// try the string as a key before any manipulation
3782				if ( name in thisCache ) {
3783					name = [ name ];
3784				} else {
3785
3786					// split the camel cased version by spaces unless a key with the spaces exists
3787					name = jQuery.camelCase( name );
3788					if ( name in thisCache ) {
3789						name = [ name ];
3790					} else {
3791						name = name.split(" ");
3792					}
3793				}
3794			} else {
3795				// If "name" is an array of keys...
3796				// When data is initially created, via ("key", "val") signature,
3797				// keys will be converted to camelCase.
3798				// Since there is no way to tell _how_ a key was added, remove
3799				// both plain key and camelCase key. #12786
3800				// This will only penalize the array argument path.
3801				name = name.concat( jQuery.map( name, jQuery.camelCase ) );
3802			}
3803
3804			i = name.length;
3805			while ( i-- ) {
3806				delete thisCache[ name[i] ];
3807			}
3808
3809			// If there is no data left in the cache, we want to continue
3810			// and let the cache object itself get destroyed
3811			if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
3812				return;
3813			}
3814		}
3815	}
3816
3817	// See jQuery.data for more information
3818	if ( !pvt ) {
3819		delete cache[ id ].data;
3820
3821		// Don't destroy the parent cache unless the internal data object
3822		// had been the only thing left in it
3823		if ( !isEmptyDataObject( cache[ id ] ) ) {
3824			return;
3825		}
3826	}
3827
3828	// Destroy the cache
3829	if ( isNode ) {
3830		jQuery.cleanData( [ elem ], true );
3831
3832	// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
3833	/* jshint eqeqeq: false */
3834	} else if ( support.deleteExpando || cache != cache.window ) {
3835		/* jshint eqeqeq: true */
3836		delete cache[ id ];
3837
3838	// When all else fails, null
3839	} else {
3840		cache[ id ] = null;
3841	}
3842}
3843
3844jQuery.extend({
3845	cache: {},
3846
3847	// The following elements (space-suffixed to avoid Object.prototype collisions)
3848	// throw uncatchable exceptions if you attempt to set expando properties
3849	noData: {
3850		"applet ": true,
3851		"embed ": true,
3852		// ...but Flash objects (which have this classid) *can* handle expandos
3853		"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
3854	},
3855
3856	hasData: function( elem ) {
3857		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
3858		return !!elem && !isEmptyDataObject( elem );
3859	},
3860
3861	data: function( elem, name, data ) {
3862		return internalData( elem, name, data );
3863	},
3864
3865	removeData: function( elem, name ) {
3866		return internalRemoveData( elem, name );
3867	},
3868
3869	// For internal use only.
3870	_data: function( elem, name, data ) {
3871		return internalData( elem, name, data, true );
3872	},
3873
3874	_removeData: function( elem, name ) {
3875		return internalRemoveData( elem, name, true );
3876	}
3877});
3878
3879jQuery.fn.extend({
3880	data: function( key, value ) {
3881		var i, name, data,
3882			elem = this[0],
3883			attrs = elem && elem.attributes;
3884
3885		// Special expections of .data basically thwart jQuery.access,
3886		// so implement the relevant behavior ourselves
3887
3888		// Gets all values
3889		if ( key === undefined ) {
3890			if ( this.length ) {
3891				data = jQuery.data( elem );
3892
3893				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
3894					i = attrs.length;
3895					while ( i-- ) {
3896						name = attrs[i].name;
3897
3898						if ( name.indexOf("data-") === 0 ) {
3899							name = jQuery.camelCase( name.slice(5) );
3900
3901							dataAttr( elem, name, data[ name ] );
3902						}
3903					}
3904					jQuery._data( elem, "parsedAttrs", true );
3905				}
3906			}
3907
3908			return data;
3909		}
3910
3911		// Sets multiple values
3912		if ( typeof key === "object" ) {
3913			return this.each(function() {
3914				jQuery.data( this, key );
3915			});
3916		}
3917
3918		return arguments.length > 1 ?
3919
3920			// Sets one value
3921			this.each(function() {
3922				jQuery.data( this, key, value );
3923			}) :
3924
3925			// Gets one value
3926			// Try to fetch any internally stored data first
3927			elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
3928	},
3929
3930	removeData: function( key ) {
3931		return this.each(function() {
3932			jQuery.removeData( this, key );
3933		});
3934	}
3935});
3936
3937
3938jQuery.extend({
3939	queue: function( elem, type, data ) {
3940		var queue;
3941
3942		if ( elem ) {
3943			type = ( type || "fx" ) + "queue";
3944			queue = jQuery._data( elem, type );
3945
3946			// Speed up dequeue by getting out quickly if this is just a lookup
3947			if ( data ) {
3948				if ( !queue || jQuery.isArray(data) ) {
3949					queue = jQuery._data( elem, type, jQuery.makeArray(data) );
3950				} else {
3951					queue.push( data );
3952				}
3953			}
3954			return queue || [];
3955		}
3956	},
3957
3958	dequeue: function( elem, type ) {
3959		type = type || "fx";
3960
3961		var queue = jQuery.queue( elem, type ),
3962			startLength = queue.length,
3963			fn = queue.shift(),
3964			hooks = jQuery._queueHooks( elem, type ),
3965			next = function() {
3966				jQuery.dequeue( elem, type );
3967			};
3968
3969		// If the fx queue is dequeued, always remove the progress sentinel
3970		if ( fn === "inprogress" ) {
3971			fn = queue.shift();
3972			startLength--;
3973		}
3974
3975		if ( fn ) {
3976
3977			// Add a progress sentinel to prevent the fx queue from being
3978			// automatically dequeued
3979			if ( type === "fx" ) {
3980				queue.unshift( "inprogress" );
3981			}
3982
3983			// clear up the last queue stop function
3984			delete hooks.stop;
3985			fn.call( elem, next, hooks );
3986		}
3987
3988		if ( !startLength && hooks ) {
3989			hooks.empty.fire();
3990		}
3991	},
3992
3993	// not intended for public consumption - generates a queueHooks object, or returns the current one
3994	_queueHooks: function( elem, type ) {
3995		var key = type + "queueHooks";
3996		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
3997			empty: jQuery.Callbacks("once memory").add(function() {
3998				jQuery._removeData( elem, type + "queue" );
3999				jQuery._removeData( elem, key );
4000			})
4001		});
4002	}
4003});
4004
4005jQuery.fn.extend({
4006	queue: function( type, data ) {
4007		var setter = 2;
4008
4009		if ( typeof type !== "string" ) {
4010			data = type;
4011			type = "fx";
4012			setter--;
4013		}
4014
4015		if ( arguments.length < setter ) {
4016			return jQuery.queue( this[0], type );
4017		}
4018
4019		return data === undefined ?
4020			this :
4021			this.each(function() {
4022				var queue = jQuery.queue( this, type, data );
4023
4024				// ensure a hooks for this queue
4025				jQuery._queueHooks( this, type );
4026
4027				if ( type === "fx" && queue[0] !== "inprogress" ) {
4028					jQuery.dequeue( this, type );
4029				}
4030			});
4031	},
4032	dequeue: function( type ) {
4033		return this.each(function() {
4034			jQuery.dequeue( this, type );
4035		});
4036	},
4037	clearQueue: function( type ) {
4038		return this.queue( type || "fx", [] );
4039	},
4040	// Get a promise resolved when queues of a certain type
4041	// are emptied (fx is the type by default)
4042	promise: function( type, obj ) {
4043		var tmp,
4044			count = 1,
4045			defer = jQuery.Deferred(),
4046			elements = this,
4047			i = this.length,
4048			resolve = function() {
4049				if ( !( --count ) ) {
4050					defer.resolveWith( elements, [ elements ] );
4051				}
4052			};
4053
4054		if ( typeof type !== "string" ) {
4055			obj = type;
4056			type = undefined;
4057		}
4058		type = type || "fx";
4059
4060		while ( i-- ) {
4061			tmp = jQuery._data( elements[ i ], type + "queueHooks" );
4062			if ( tmp && tmp.empty ) {
4063				count++;
4064				tmp.empty.add( resolve );
4065			}
4066		}
4067		resolve();
4068		return defer.promise( obj );
4069	}
4070});
4071var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
4072
4073var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
4074
4075var isHidden = function( elem, el ) {
4076		// isHidden might be called from jQuery#filter function;
4077		// in that case, element will be second argument
4078		elem = el || elem;
4079		return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
4080	};
4081
4082
4083
4084// Multifunctional method to get and set values of a collection
4085// The value/s can optionally be executed if it's a function
4086var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
4087	var i = 0,
4088		length = elems.length,
4089		bulk = key == null;
4090
4091	// Sets many values
4092	if ( jQuery.type( key ) === "object" ) {
4093		chainable = true;
4094		for ( i in key ) {
4095			jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
4096		}
4097
4098	// Sets one value
4099	} else if ( value !== undefined ) {
4100		chainable = true;
4101
4102		if ( !jQuery.isFunction( value ) ) {
4103			raw = true;
4104		}
4105
4106		if ( bulk ) {
4107			// Bulk operations run against the entire set
4108			if ( raw ) {
4109				fn.call( elems, value );
4110				fn = null;
4111
4112			// ...except when executing function values
4113			} else {
4114				bulk = fn;
4115				fn = function( elem, key, value ) {
4116					return bulk.call( jQuery( elem ), value );
4117				};
4118			}
4119		}
4120
4121		if ( fn ) {
4122			for ( ; i < length; i++ ) {
4123				fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
4124			}
4125		}
4126	}
4127
4128	return chainable ?
4129		elems :
4130
4131		// Gets
4132		bulk ?
4133			fn.call( elems ) :
4134			length ? fn( elems[0], key ) : emptyGet;
4135};
4136var rcheckableType = (/^(?:checkbox|radio)$/i);
4137
4138
4139
4140(function() {
4141	var fragment = document.createDocumentFragment(),
4142		div = document.createElement("div"),
4143		input = document.createElement("input");
4144
4145	// Setup
4146	div.setAttribute( "className", "t" );
4147	div.innerHTML = "  <link/><table></table><a href='/a'>a</a>";
4148
4149	// IE strips leading whitespace when .innerHTML is used
4150	support.leadingWhitespace = div.firstChild.nodeType === 3;
4151
4152	// Make sure that tbody elements aren't automatically inserted
4153	// IE will insert them into empty tables
4154	support.tbody = !div.getElementsByTagName( "tbody" ).length;
4155
4156	// Make sure that link elements get serialized correctly by innerHTML
4157	// This requires a wrapper element in IE
4158	support.htmlSerialize = !!div.getElementsByTagName( "link" ).length;
4159
4160	// Makes sure cloning an html5 element does not cause problems
4161	// Where outerHTML is undefined, this still works
4162	support.html5Clone =
4163		document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav></:nav>";
4164
4165	// Check if a disconnected checkbox will retain its checked
4166	// value of true after appended to the DOM (IE6/7)
4167	input.type = "checkbox";
4168	input.checked = true;
4169	fragment.appendChild( input );
4170	support.appendChecked = input.checked;
4171
4172	// Make sure textarea (and checkbox) defaultValue is properly cloned
4173	// Support: IE6-IE11+
4174	div.innerHTML = "<textarea>x</textarea>";
4175	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
4176
4177	// #11217 - WebKit loses check when the name is after the checked attribute
4178	fragment.appendChild( div );
4179	div.innerHTML = "<input type='radio' checked='checked' name='t'/>";
4180
4181	// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
4182	// old WebKit doesn't clone checked state correctly in fragments
4183	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
4184
4185	// Support: IE<9
4186	// Opera does not clone events (and typeof div.attachEvent === undefined).
4187	// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
4188	support.noCloneEvent = true;
4189	if ( div.attachEvent ) {
4190		div.attachEvent( "onclick", function() {
4191			support.noCloneEvent = false;
4192		});
4193
4194		div.cloneNode( true ).click();
4195	}
4196
4197	// Execute the test only if not already executed in another module.
4198	if (support.deleteExpando == null) {
4199		// Support: IE<9
4200		support.deleteExpando = true;
4201		try {
4202			delete div.test;
4203		} catch( e ) {
4204			support.deleteExpando = false;
4205		}
4206	}
4207
4208	// Null elements to avoid leaks in IE.
4209	fragment = div = input = null;
4210})();
4211
4212
4213(function() {
4214	var i, eventName,
4215		div = document.createElement( "div" );
4216
4217	// Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event)
4218	for ( i in { submit: true, change: true, focusin: true }) {
4219		eventName = "on" + i;
4220
4221		if ( !(support[ i + "Bubbles" ] = eventName in window) ) {
4222			// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
4223			div.setAttribute( eventName, "t" );
4224			support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false;
4225		}
4226	}
4227
4228	// Null elements to avoid leaks in IE.
4229	div = null;
4230})();
4231
4232
4233var rformElems = /^(?:input|select|textarea)$/i,
4234	rkeyEvent = /^key/,
4235	rmouseEvent = /^(?:mouse|contextmenu)|click/,
4236	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
4237	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
4238
4239function returnTrue() {
4240	return true;
4241}
4242
4243function returnFalse() {
4244	return false;
4245}
4246
4247function safeActiveElement() {
4248	try {
4249		return document.activeElement;
4250	} catch ( err ) { }
4251}
4252
4253/*
4254 * Helper functions for managing events -- not part of the public interface.
4255 * Props to Dean Edwards' addEvent library for many of the ideas.
4256 */
4257jQuery.event = {
4258
4259	global: {},
4260
4261	add: function( elem, types, handler, data, selector ) {
4262		var tmp, events, t, handleObjIn,
4263			special, eventHandle, handleObj,
4264			handlers, type, namespaces, origType,
4265			elemData = jQuery._data( elem );
4266
4267		// Don't attach events to noData or text/comment nodes (but allow plain objects)
4268		if ( !elemData ) {
4269			return;
4270		}
4271
4272		// Caller can pass in an object of custom data in lieu of the handler
4273		if ( handler.handler ) {
4274			handleObjIn = handler;
4275			handler = handleObjIn.handler;
4276			selector = handleObjIn.selector;
4277		}
4278
4279		// Make sure that the handler has a unique ID, used to find/remove it later
4280		if ( !handler.guid ) {
4281			handler.guid = jQuery.guid++;
4282		}
4283
4284		// Init the element's event structure and main handler, if this is the first
4285		if ( !(events = elemData.events) ) {
4286			events = elemData.events = {};
4287		}
4288		if ( !(eventHandle = elemData.handle) ) {
4289			eventHandle = elemData.handle = function( e ) {
4290				// Discard the second event of a jQuery.event.trigger() and
4291				// when an event is called after a page has unloaded
4292				return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ?
4293					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
4294					undefined;
4295			};
4296			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
4297			eventHandle.elem = elem;
4298		}
4299
4300		// Handle multiple events separated by a space
4301		types = ( types || "" ).match( rnotwhite ) || [ "" ];
4302		t = types.length;
4303		while ( t-- ) {
4304			tmp = rtypenamespace.exec( types[t] ) || [];
4305			type = origType = tmp[1];
4306			namespaces = ( tmp[2] || "" ).split( "." ).sort();
4307
4308			// There *must* be a type, no attaching namespace-only handlers
4309			if ( !type ) {
4310				continue;
4311			}
4312
4313			// If event changes its type, use the special event handlers for the changed type
4314			special = jQuery.event.special[ type ] || {};
4315
4316			// If selector defined, determine special event api type, otherwise given type
4317			type = ( selector ? special.delegateType : special.bindType ) || type;
4318
4319			// Update special based on newly reset type
4320			special = jQuery.event.special[ type ] || {};
4321
4322			// handleObj is passed to all event handlers
4323			handleObj = jQuery.extend({
4324				type: type,
4325				origType: origType,
4326				data: data,
4327				handler: handler,
4328				guid: handler.guid,
4329				selector: selector,
4330				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
4331				namespace: namespaces.join(".")
4332			}, handleObjIn );
4333
4334			// Init the event handler queue if we're the first
4335			if ( !(handlers = events[ type ]) ) {
4336				handlers = events[ type ] = [];
4337				handlers.delegateCount = 0;
4338
4339				// Only use addEventListener/attachEvent if the special events handler returns false
4340				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
4341					// Bind the global event handler to the element
4342					if ( elem.addEventListener ) {
4343						elem.addEventListener( type, eventHandle, false );
4344
4345					} else if ( elem.attachEvent ) {
4346						elem.attachEvent( "on" + type, eventHandle );
4347					}
4348				}
4349			}
4350
4351			if ( special.add ) {
4352				special.add.call( elem, handleObj );
4353
4354				if ( !handleObj.handler.guid ) {
4355					handleObj.handler.guid = handler.guid;
4356				}
4357			}
4358
4359			// Add to the element's handler list, delegates in front
4360			if ( selector ) {
4361				handlers.splice( handlers.delegateCount++, 0, handleObj );
4362			} else {
4363				handlers.push( handleObj );
4364			}
4365
4366			// Keep track of which events have ever been used, for event optimization
4367			jQuery.event.global[ type ] = true;
4368		}
4369
4370		// Nullify elem to prevent memory leaks in IE
4371		elem = null;
4372	},
4373
4374	// Detach an event or set of events from an element
4375	remove: function( elem, types, handler, selector, mappedTypes ) {
4376		var j, handleObj, tmp,
4377			origCount, t, events,
4378			special, handlers, type,
4379			namespaces, origType,
4380			elemData = jQuery.hasData( elem ) && jQuery._data( elem );
4381
4382		if ( !elemData || !(events = elemData.events) ) {
4383			return;
4384		}
4385
vendor: 8,441 bytes, lines 4386-4652
4386		// Once for each type.namespace in types; type may be omitted
4387		types = ( types || "" ).match( rnotwhite ) || [ "" ];
4388		t = types.length;
4389		while ( t-- ) {
4390			tmp = rtypenamespace.exec( types[t] ) || [];
4391			type = origType = tmp[1];
4392			namespaces = ( tmp[2] || "" ).split( "." ).sort();
4393
4394			// Unbind all events (on this namespace, if provided) for the element
4395			if ( !type ) {
4396				for ( type in events ) {
4397					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
4398				}
4399				continue;
4400			}
4401
4402			special = jQuery.event.special[ type ] || {};
4403			type = ( selector ? special.delegateType : special.bindType ) || type;
4404			handlers = events[ type ] || [];
4405			tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
4406
4407			// Remove matching events
4408			origCount = j = handlers.length;
4409			while ( j-- ) {
4410				handleObj = handlers[ j ];
4411
4412				if ( ( mappedTypes || origType === handleObj.origType ) &&
4413					( !handler || handler.guid === handleObj.guid ) &&
4414					( !tmp || tmp.test( handleObj.namespace ) ) &&
4415					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
4416					handlers.splice( j, 1 );
4417
4418					if ( handleObj.selector ) {
4419						handlers.delegateCount--;
4420					}
4421					if ( special.remove ) {
4422						special.remove.call( elem, handleObj );
4423					}
4424				}
4425			}
4426
4427			// Remove generic event handler if we removed something and no more handlers exist
4428			// (avoids potential for endless recursion during removal of special event handlers)
4429			if ( origCount && !handlers.length ) {
4430				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
4431					jQuery.removeEvent( elem, type, elemData.handle );
4432				}
4433
4434				delete events[ type ];
4435			}
4436		}
4437
4438		// Remove the expando if it's no longer used
4439		if ( jQuery.isEmptyObject( events ) ) {
4440			delete elemData.handle;
4441
4442			// removeData also checks for emptiness and clears the expando if empty
4443			// so use it instead of delete
4444			jQuery._removeData( elem, "events" );
4445		}
4446	},
4447
4448	trigger: function( event, data, elem, onlyHandlers ) {
4449		var handle, ontype, cur,
4450			bubbleType, special, tmp, i,
4451			eventPath = [ elem || document ],
4452			type = hasOwn.call( event, "type" ) ? event.type : event,
4453			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
4454
4455		cur = tmp = elem = elem || document;
4456
4457		// Don't do events on text and comment nodes
4458		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
4459			return;
4460		}
4461
4462		// focus/blur morphs to focusin/out; ensure we're not firing them right now
4463		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
4464			return;
4465		}
4466
4467		if ( type.indexOf(".") >= 0 ) {
4468			// Namespaced trigger; create a regexp to match event type in handle()
4469			namespaces = type.split(".");
4470			type = namespaces.shift();
4471			namespaces.sort();
4472		}
4473		ontype = type.indexOf(":") < 0 && "on" + type;
4474
4475		// Caller can pass in a jQuery.Event object, Object, or just an event type string
4476		event = event[ jQuery.expando ] ?
4477			event :
4478			new jQuery.Event( type, typeof event === "object" && event );
4479
4480		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
4481		event.isTrigger = onlyHandlers ? 2 : 3;
4482		event.namespace = namespaces.join(".");
4483		event.namespace_re = event.namespace ?
4484			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
4485			null;
4486
4487		// Clean up the event in case it is being reused
4488		event.result = undefined;
4489		if ( !event.target ) {
4490			event.target = elem;
4491		}
4492
4493		// Clone any incoming data and prepend the event, creating the handler arg list
4494		data = data == null ?
4495			[ event ] :
4496			jQuery.makeArray( data, [ event ] );
4497
4498		// Allow special events to draw outside the lines
4499		special = jQuery.event.special[ type ] || {};
4500		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
4501			return;
4502		}
4503
4504		// Determine event propagation path in advance, per W3C events spec (#9951)
4505		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
4506		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
4507
4508			bubbleType = special.delegateType || type;
4509			if ( !rfocusMorph.test( bubbleType + type ) ) {
4510				cur = cur.parentNode;
4511			}
4512			for ( ; cur; cur = cur.parentNode ) {
4513				eventPath.push( cur );
4514				tmp = cur;
4515			}
4516
4517			// Only add window if we got to document (e.g., not plain obj or detached DOM)
4518			if ( tmp === (elem.ownerDocument || document) ) {
4519				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
4520			}
4521		}
4522
4523		// Fire handlers on the event path
4524		i = 0;
4525		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
4526
4527			event.type = i > 1 ?
4528				bubbleType :
4529				special.bindType || type;
4530
4531			// jQuery handler
4532			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
4533			if ( handle ) {
4534				handle.apply( cur, data );
4535			}
4536
4537			// Native handler
4538			handle = ontype && cur[ ontype ];
4539			if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
4540				event.result = handle.apply( cur, data );
4541				if ( event.result === false ) {
4542					event.preventDefault();
4543				}
4544			}
4545		}
4546		event.type = type;
4547
4548		// If nobody prevented the default action, do it now
4549		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
4550
4551			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
4552				jQuery.acceptData( elem ) ) {
4553
4554				// Call a native DOM method on the target with the same name name as the event.
4555				// Can't use an .isFunction() check here because IE6/7 fails that test.
4556				// Don't do default actions on window, that's where global variables be (#6170)
4557				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
4558
4559					// Don't re-trigger an onFOO event when we call its FOO() method
4560					tmp = elem[ ontype ];
4561
4562					if ( tmp ) {
4563						elem[ ontype ] = null;
4564					}
4565
4566					// Prevent re-triggering of the same event, since we already bubbled it above
4567					jQuery.event.triggered = type;
4568					try {
4569						elem[ type ]();
4570					} catch ( e ) {
4571						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
4572						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
4573					}
4574					jQuery.event.triggered = undefined;
4575
4576					if ( tmp ) {
4577						elem[ ontype ] = tmp;
4578					}
4579				}
4580			}
4581		}
4582
4583		return event.result;
4584	},
4585
4586	dispatch: function( event ) {
4587
4588		// Make a writable jQuery.Event from the native event object
4589		event = jQuery.event.fix( event );
4590
4591		var i, ret, handleObj, matched, j,
4592			handlerQueue = [],
4593			args = slice.call( arguments ),
4594			handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
4595			special = jQuery.event.special[ event.type ] || {};
4596
4597		// Use the fix-ed jQuery.Event rather than the (read-only) native event
4598		args[0] = event;
4599		event.delegateTarget = this;
4600
4601		// Call the preDispatch hook for the mapped type, and let it bail if desired
4602		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
4603			return;
4604		}
4605
4606		// Determine handlers
4607		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
4608
4609		// Run delegates first; they may want to stop propagation beneath us
4610		i = 0;
4611		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
4612			event.currentTarget = matched.elem;
4613
4614			j = 0;
4615			while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
4616
4617				// Triggered event must either 1) have no namespace, or
4618				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
4619				if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
4620
4621					event.handleObj = handleObj;
4622					event.data = handleObj.data;
4623
4624					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
4625							.apply( matched.elem, args );
4626
4627					if ( ret !== undefined ) {
4628						if ( (event.result = ret) === false ) {
4629							event.preventDefault();
4630							event.stopPropagation();
4631						}
4632					}
4633				}
4634			}
4635		}
4636
4637		// Call the postDispatch hook for the mapped type
4638		if ( special.postDispatch ) {
4639			special.postDispatch.call( this, event );
4640		}
4641
4642		return event.result;
4643	},
4644
4645	handlers: function( event, handlers ) {
4646		var sel, handleObj, matches, i,
4647			handlerQueue = [],
4648			delegateCount = handlers.delegateCount,
4649			cur = event.target;
4650
4651		// Find delegate handlers
4652		// Black-hole SVG <use> instance trees (#13180)
4653		// Avoid non-left-click bubbling in Firefox (#3861)
4654		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
4655
4656			/* jshint eqeqeq: false */
4657			for ( ; cur != this; cur = cur.parentNode || this ) {
4658				/* jshint eqeqeq: true */
4659
4660				// Don't check non-elements (#13208)
4661				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
4662				if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) {
4663					matches = [];
4664					for ( i = 0; i < delegateCount; i++ ) {
4665						handleObj = handlers[ i ];
4666
4667						// Don't conflict with Object.prototype properties (#13203)
4668						sel = handleObj.selector + " ";
4669
4670						if ( matches[ sel ] === undefined ) {
4671							matches[ sel ] = handleObj.needsContext ?
4672								jQuery( sel, this ).index( cur ) >= 0 :
4673								jQuery.find( sel, this, null, [ cur ] ).length;
4674						}
4675						if ( matches[ sel ] ) {
4676							matches.push( handleObj );
4677						}
4678					}
4679					if ( matches.length ) {
4680						handlerQueue.push({ elem: cur, handlers: matches });
4681					}
4682				}
4683			}
4684		}
4685
4686		// Add the remaining (directly-bound) handlers
4687		if ( delegateCount < handlers.length ) {
4688			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
4689		}
4690
4691		return handlerQueue;
4692	},
4693
4694	fix: function( event ) {
4695		if ( event[ jQuery.expando ] ) {
4696			return event;
4697		}
4698
4699		// Create a writable copy of the event object and normalize some properties
4700		var i, prop, copy,
4701			type = event.type,
4702			originalEvent = event,
4703			fixHook = this.fixHooks[ type ];
4704
4705		if ( !fixHook ) {
4706			this.fixHooks[ type ] = fixHook =
4707				rmouseEvent.test( type ) ? this.mouseHooks :
4708				rkeyEvent.test( type ) ? this.keyHooks :
4709				{};
4710		}
4711		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
4712
4713		event = new jQuery.Event( originalEvent );
4714
4715		i = copy.length;
4716		while ( i-- ) {
4717			prop = copy[ i ];
4718			event[ prop ] = originalEvent[ prop ];
4719		}
4720
4721		// Support: IE<9
4722		// Fix target property (#1925)
4723		if ( !event.target ) {
4724			event.target = originalEvent.srcElement || document;
4725		}
4726
4727		// Support: Chrome 23+, Safari?
4728		// Target should not be a text node (#504, #13143)
4729		if ( event.target.nodeType === 3 ) {
4730			event.target = event.target.parentNode;
4731		}
4732
4733		// Support: IE<9
4734		// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
4735		event.metaKey = !!event.metaKey;
4736
4737		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
4738	},
4739
4740	// Includes some event props shared by KeyEvent and MouseEvent
4741	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
4742
4743	fixHooks: {},
4744
4745	keyHooks: {
4746		props: "char charCode key keyCode".split(" "),
4747		filter: function( event, original ) {
4748
4749			// Add which for key events
4750			if ( event.which == null ) {
4751				event.which = original.charCode != null ? original.charCode : original.keyCode;
4752			}
4753
4754			return event;
4755		}
4756	},
4757
4758	mouseHooks: {
4759		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
4760		filter: function( event, original ) {
4761			var body, eventDoc, doc,
4762				button = original.button,
4763				fromElement = original.fromElement;
4764
4765			// Calculate pageX/Y if missing and clientX/Y available
4766			if ( event.pageX == null && original.clientX != null ) {
4767				eventDoc = event.target.ownerDocument || document;
4768				doc = eventDoc.documentElement;
4769				body = eventDoc.body;
4770
4771				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
4772				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
4773			}
4774
4775			// Add relatedTarget, if necessary
4776			if ( !event.relatedTarget && fromElement ) {
4777				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
4778			}
4779
4780			// Add which for click: 1 === left; 2 === middle; 3 === right
4781			// Note: button is not normalized, so don't use it
4782			if ( !event.which && button !== undefined ) {
4783				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
4784			}
4785
4786			return event;
4787		}
4788	},
4789
4790	special: {
4791		load: {
4792			// Prevent triggered image.load events from bubbling to window.load
4793			noBubble: true
4794		},
4795		focus: {
4796			// Fire native event if possible so blur/focus sequence is correct
4797			trigger: function() {
4798				if ( this !== safeActiveElement() && this.focus ) {
4799					try {
4800						this.focus();
4801						return false;
4802					} catch ( e ) {
4803						// Support: IE<9
4804						// If we error on focus to hidden element (#1486, #12518),
4805						// let .trigger() run the handlers
4806					}
4807				}
4808			},
4809			delegateType: "focusin"
4810		},
4811		blur: {
4812			trigger: function() {
4813				if ( this === safeActiveElement() && this.blur ) {
4814					this.blur();
4815					return false;
4816				}
4817			},
4818			delegateType: "focusout"
4819		},
4820		click: {
4821			// For checkbox, fire native event so checked state will be right
4822			trigger: function() {
4823				if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
4824					this.click();
4825					return false;
4826				}
4827			},
4828
4829			// For cross-browser consistency, don't fire native .click() on links
4830			_default: function( event ) {
4831				return jQuery.nodeName( event.target, "a" );
4832			}
4833		},
4834
4835		beforeunload: {
4836			postDispatch: function( event ) {
4837
4838				// Even when returnValue equals to undefined Firefox will still show alert
4839				if ( event.result !== undefined ) {
4840					event.originalEvent.returnValue = event.result;
4841				}
4842			}
4843		}
4844	},
4845
4846	simulate: function( type, elem, event, bubble ) {
4847		// Piggyback on a donor event to simulate a different one.
4848		// Fake originalEvent to avoid donor's stopPropagation, but if the
4849		// simulated event prevents default then we do the same on the donor.
4850		var e = jQuery.extend(
4851			new jQuery.Event(),
4852			event,
4853			{
4854				type: type,
4855				isSimulated: true,
4856				originalEvent: {}
4857			}
4858		);
4859		if ( bubble ) {
4860			jQuery.event.trigger( e, null, elem );
4861		} else {
4862			jQuery.event.dispatch.call( elem, e );
4863		}
4864		if ( e.isDefaultPrevented() ) {
4865			event.preventDefault();
4866		}
4867	}
4868};
4869
4870jQuery.removeEvent = document.removeEventListener ?
4871	function( elem, type, handle ) {
4872		if ( elem.removeEventListener ) {
4873			elem.removeEventListener( type, handle, false );
4874		}
4875	} :
4876	function( elem, type, handle ) {
4877		var name = "on" + type;
4878
4879		if ( elem.detachEvent ) {
4880
4881			// #8545, #7054, preventing memory leaks for custom events in IE6-8
4882			// detachEvent needed property on element, by name of that event, to properly expose it to GC
4883			if ( typeof elem[ name ] === strundefined ) {
4884				elem[ name ] = null;
4885			}
4886
4887			elem.detachEvent( name, handle );
4888		}
4889	};
4890
4891jQuery.Event = function( src, props ) {
4892	// Allow instantiation without the 'new' keyword
4893	if ( !(this instanceof jQuery.Event) ) {
4894		return new jQuery.Event( src, props );
4895	}
4896
4897	// Event object
4898	if ( src && src.type ) {
4899		this.originalEvent = src;
4900		this.type = src.type;
4901
4902		// Events bubbling up the document may have been marked as prevented
4903		// by a handler lower down the tree; reflect the correct value.
4904		this.isDefaultPrevented = src.defaultPrevented ||
4905				src.defaultPrevented === undefined && (
4906				// Support: IE < 9
4907				src.returnValue === false ||
4908				// Support: Android < 4.0
4909				src.getPreventDefault && src.getPreventDefault() ) ?
4910			returnTrue :
4911			returnFalse;
4912
4913	// Event type
4914	} else {
4915		this.type = src;
4916	}
4917
4918	// Put explicitly provided properties onto the event object
4919	if ( props ) {
4920		jQuery.extend( this, props );
4921	}
4922
4923	// Create a timestamp if incoming event doesn't have one
4924	this.timeStamp = src && src.timeStamp || jQuery.now();
4925
4926	// Mark it as fixed
4927	this[ jQuery.expando ] = true;
4928};
4929
4930// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
4931// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
4932jQuery.Event.prototype = {
4933	isDefaultPrevented: returnFalse,
4934	isPropagationStopped: returnFalse,
4935	isImmediatePropagationStopped: returnFalse,
4936
4937	preventDefault: function() {
4938		var e = this.originalEvent;
4939
4940		this.isDefaultPrevented = returnTrue;
4941		if ( !e ) {
4942			return;
4943		}
4944
4945		// If preventDefault exists, run it on the original event
4946		if ( e.preventDefault ) {
4947			e.preventDefault();
4948
4949		// Support: IE
4950		// Otherwise set the returnValue property of the original event to false
4951		} else {
4952			e.returnValue = false;
4953		}
4954	},
4955	stopPropagation: function() {
4956		var e = this.originalEvent;
4957
4958		this.isPropagationStopped = returnTrue;
4959		if ( !e ) {
4960			return;
4961		}
4962		// If stopPropagation exists, run it on the original event
4963		if ( e.stopPropagation ) {
4964			e.stopPropagation();
4965		}
4966
4967		// Support: IE
4968		// Set the cancelBubble property of the original event to true
4969		e.cancelBubble = true;
4970	},
4971	stopImmediatePropagation: function() {
4972		this.isImmediatePropagationStopped = returnTrue;
4973		this.stopPropagation();
4974	}
4975};
4976
4977// Create mouseenter/leave events using mouseover/out and event-time checks
4978jQuery.each({
4979	mouseenter: "mouseover",
4980	mouseleave: "mouseout"
4981}, function( orig, fix ) {
4982	jQuery.event.special[ orig ] = {
4983		delegateType: fix,
4984		bindType: fix,
4985
4986		handle: function( event ) {
4987			var ret,
4988				target = this,
4989				related = event.relatedTarget,
4990				handleObj = event.handleObj;
4991
4992			// For mousenter/leave call the handler if related is outside the target.
4993			// NB: No relatedTarget if the mouse left/entered the browser window
4994			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
4995				event.type = handleObj.origType;
4996				ret = handleObj.handler.apply( this, arguments );
4997				event.type = fix;
4998			}
4999			return ret;
5000		}
5001	};
5002});
5003
5004// IE submit delegation
5005if ( !support.submitBubbles ) {
5006
5007	jQuery.event.special.submit = {
5008		setup: function() {
5009			// Only need this for delegated form submit events
5010			if ( jQuery.nodeName( this, "form" ) ) {
5011				return false;
5012			}
5013
5014			// Lazy-add a submit handler when a descendant form may potentially be submitted
5015			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
5016				// Node name check avoids a VML-related crash in IE (#9807)
5017				var elem = e.target,
5018					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
5019				if ( form && !jQuery._data( form, "submitBubbles" ) ) {
5020					jQuery.event.add( form, "submit._submit", function( event ) {
5021						event._submit_bubble = true;
5022					});
5023					jQuery._data( form, "submitBubbles", true );
5024				}
5025			});
5026			// return undefined since we don't need an event listener
5027		},
5028
5029		postDispatch: function( event ) {
5030			// If form was submitted by the user, bubble the event up the tree
5031			if ( event._submit_bubble ) {
5032				delete event._submit_bubble;
5033				if ( this.parentNode && !event.isTrigger ) {
5034					jQuery.event.simulate( "submit", this.parentNode, event, true );
5035				}
5036			}
5037		},
5038
5039		teardown: function() {
5040			// Only need this for delegated form submit events
5041			if ( jQuery.nodeName( this, "form" ) ) {
5042				return false;
5043			}
5044
5045			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
5046			jQuery.event.remove( this, "._submit" );
5047		}
5048	};
5049}
5050
5051// IE change delegation and checkbox/radio fix
5052if ( !support.changeBubbles ) {
5053
5054	jQuery.event.special.change = {
5055
5056		setup: function() {
5057
5058			if ( rformElems.test( this.nodeName ) ) {
5059				// IE doesn't fire change on a check/radio until blur; trigger it on click
5060				// after a propertychange. Eat the blur-change in special.change.handle.
5061				// This still fires onchange a second time for check/radio after blur.
5062				if ( this.type === "checkbox" || this.type === "radio" ) {
5063					jQuery.event.add( this, "propertychange._change", function( event ) {
5064						if ( event.originalEvent.propertyName === "checked" ) {
5065							this._just_changed = true;
5066						}
5067					});
5068					jQuery.event.add( this, "click._change", function( event ) {
5069						if ( this._just_changed && !event.isTrigger ) {
5070							this._just_changed = false;
5071						}
5072						// Allow triggered, simulated change events (#11500)
5073						jQuery.event.simulate( "change", this, event, true );
5074					});
5075				}
5076				return false;
5077			}
5078			// Delegated event; lazy-add a change handler on descendant inputs
5079			jQuery.event.add( this, "beforeactivate._change", function( e ) {
5080				var elem = e.target;
5081
5082				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
5083					jQuery.event.add( elem, "change._change", function( event ) {
5084						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
5085							jQuery.event.simulate( "change", this.parentNode, event, true );
5086						}
5087					});
5088					jQuery._data( elem, "changeBubbles", true );
5089				}
5090			});
5091		},
5092
5093		handle: function( event ) {
5094			var elem = event.target;
5095
5096			// Swallow native change events from checkbox/radio, we already triggered them above
5097			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
5098				return event.handleObj.handler.apply( this, arguments );
5099			}
5100		},
5101
5102		teardown: function() {
5103			jQuery.event.remove( this, "._change" );
5104
5105			return !rformElems.test( this.nodeName );
5106		}
5107	};
5108}
5109
5110// Create "bubbling" focus and blur events
5111if ( !support.focusinBubbles ) {
5112	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
5113
5114		// Attach a single capturing handler on the document while someone wants focusin/focusout
5115		var handler = function( event ) {
5116				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
5117			};
5118
5119		jQuery.event.special[ fix ] = {
5120			setup: function() {
5121				var doc = this.ownerDocument || this,
5122					attaches = jQuery._data( doc, fix );
5123
5124				if ( !attaches ) {
5125					doc.addEventListener( orig, handler, true );
5126				}
5127				jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
5128			},
5129			teardown: function() {
5130				var doc = this.ownerDocument || this,
5131					attaches = jQuery._data( doc, fix ) - 1;
5132
5133				if ( !attaches ) {
5134					doc.removeEventListener( orig, handler, true );
5135					jQuery._removeData( doc, fix );
5136				} else {
5137					jQuery._data( doc, fix, attaches );
5138				}
5139			}
5140		};
5141	});
5142}
5143
5144jQuery.fn.extend({
5145
5146	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
5147		var type, origFn;
5148
5149		// Types can be a map of types/handlers
5150		if ( typeof types === "object" ) {
5151			// ( types-Object, selector, data )
5152			if ( typeof selector !== "string" ) {
5153				// ( types-Object, data )
5154				data = data || selector;
5155				selector = undefined;
5156			}
5157			for ( type in types ) {
5158				this.on( type, selector, data, types[ type ], one );
5159			}
5160			return this;
5161		}
5162
5163		if ( data == null && fn == null ) {
5164			// ( types, fn )
5165			fn = selector;
5166			data = selector = undefined;
5167		} else if ( fn == null ) {
5168			if ( typeof selector === "string" ) {
5169				// ( types, selector, fn )
5170				fn = data;
5171				data = undefined;
5172			} else {
5173				// ( types, data, fn )
5174				fn = data;
5175				data = selector;
5176				selector = undefined;
5177			}
5178		}
5179		if ( fn === false ) {
5180			fn = returnFalse;
5181		} else if ( !fn ) {
5182			return this;
5183		}
5184
5185		if ( one === 1 ) {
5186			origFn = fn;
5187			fn = function( event ) {
5188				// Can use an empty set, since event contains the info
5189				jQuery().off( event );
5190				return origFn.apply( this, arguments );
5191			};
5192			// Use same guid so caller can remove using origFn
5193			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
5194		}
5195		return this.each( function() {
5196			jQuery.event.add( this, types, fn, data, selector );
5197		});
5198	},
5199	one: function( types, selector, data, fn ) {
5200		return this.on( types, selector, data, fn, 1 );
5201	},
5202	off: function( types, selector, fn ) {
5203		var handleObj, type;
5204		if ( types && types.preventDefault && types.handleObj ) {
5205			// ( event )  dispatched jQuery.Event
5206			handleObj = types.handleObj;
5207			jQuery( types.delegateTarget ).off(
5208				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
5209				handleObj.selector,
5210				handleObj.handler
5211			);
5212			return this;
5213		}
5214		if ( typeof types === "object" ) {
5215			// ( types-object [, selector] )
5216			for ( type in types ) {
5217				this.off( type, selector, types[ type ] );
5218			}
5219			return this;
5220		}
5221		if ( selector === false || typeof selector === "function" ) {
5222			// ( types [, fn] )
5223			fn = selector;
5224			selector = undefined;
5225		}
5226		if ( fn === false ) {
5227			fn = returnFalse;
5228		}
5229		return this.each(function() {
5230			jQuery.event.remove( this, types, fn, selector );
5231		});
5232	},
5233
5234	trigger: function( type, data ) {
5235		return this.each(function() {
5236			jQuery.event.trigger( type, data, this );
5237		});
5238	},
5239	triggerHandler: function( type, data ) {
5240		var elem = this[0];
5241		if ( elem ) {
5242			return jQuery.event.trigger( type, data, elem, true );
5243		}
5244	}
5245});
5246
5247
5248function createSafeFragment( document ) {
5249	var list = nodeNames.split( "|" ),
5250		safeFrag = document.createDocumentFragment();
5251
5252	if ( safeFrag.createElement ) {
5253		while ( list.length ) {
5254			safeFrag.createElement(
5255				list.pop()
5256			);
5257		}
5258	}
5259	return safeFrag;
5260}
5261
5262var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
5263		"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
5264	rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
5265	rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
5266	rleadingWhitespace = /^\s+/,
5267	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
5268	rtagName = /<([\w:]+)/,
5269	rtbody = /<tbody/i,
5270	rhtml = /<|&#?\w+;/,
5271	rnoInnerhtml = /<(?:script|style|link)/i,
5272	// checked="checked" or checked
5273	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
5274	rscriptType = /^$|\/(?:java|ecma)script/i,
5275	rscriptTypeMasked = /^true\/(.*)/,
5276	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
5277
5278	// We have to close these tags to support XHTML (#13200)
5279	wrapMap = {
5280		option: [ 1, "<select multiple='multiple'>", "</select>" ],
5281		legend: [ 1, "<fieldset>", "</fieldset>" ],
5282		area: [ 1, "<map>", "</map>" ],
5283		param: [ 1, "<object>", "</object>" ],
5284		thead: [ 1, "<table>", "</table>" ],
5285		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
5286		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
5287		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
5288
5289		// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
5290		// unless wrapped in a div with non-breaking characters in front of it.
5291		_default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>"  ]
5292	},
5293	safeFragment = createSafeFragment( document ),
5294	fragmentDiv = safeFragment.appendChild( document.createElement("div") );
5295
5296wrapMap.optgroup = wrapMap.option;
5297wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
5298wrapMap.th = wrapMap.td;
5299
5300function getAll( context, tag ) {
5301	var elems, elem,
5302		i = 0,
5303		found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) :
5304			typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) :
5305			undefined;
5306
5307	if ( !found ) {
5308		for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
5309			if ( !tag || jQuery.nodeName( elem, tag ) ) {
5310				found.push( elem );
5311			} else {
5312				jQuery.merge( found, getAll( elem, tag ) );
5313			}
5314		}
5315	}
5316
5317	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
5318		jQuery.merge( [ context ], found ) :
5319		found;
5320}
5321
5322// Used in buildFragment, fixes the defaultChecked property
5323function fixDefaultChecked( elem ) {
5324	if ( rcheckableType.test( elem.type ) ) {
5325		elem.defaultChecked = elem.checked;
5326	}
5327}
5328
5329// Support: IE<8
5330// Manipulating tables requires a tbody
5331function manipulationTarget( elem, content ) {
5332	return jQuery.nodeName( elem, "table" ) &&
5333		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
5334
5335		elem.getElementsByTagName("tbody")[0] ||
5336			elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
5337		elem;
5338}
5339
5340// Replace/restore the type attribute of script elements for safe DOM manipulation
5341function disableScript( elem ) {
5342	elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type;
5343	return elem;
5344}
5345function restoreScript( elem ) {
5346	var match = rscriptTypeMasked.exec( elem.type );
5347	if ( match ) {
5348		elem.type = match[1];
5349	} else {
5350		elem.removeAttribute("type");
5351	}
5352	return elem;
5353}
5354
5355// Mark scripts as having already been evaluated
5356function setGlobalEval( elems, refElements ) {
5357	var elem,
5358		i = 0;
5359	for ( ; (elem = elems[i]) != null; i++ ) {
5360		jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) );
5361	}
5362}
5363
5364function cloneCopyEvent( src, dest ) {
5365
5366	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
5367		return;
5368	}
5369
5370	var type, i, l,
5371		oldData = jQuery._data( src ),
5372		curData = jQuery._data( dest, oldData ),
5373		events = oldData.events;
5374
5375	if ( events ) {
5376		delete curData.handle;
5377		curData.events = {};
5378
5379		for ( type in events ) {
5380			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
5381				jQuery.event.add( dest, type, events[ type ][ i ] );
5382			}
5383		}
5384	}
5385
5386	// make the cloned public data object a copy from the original
5387	if ( curData.data ) {
5388		curData.data = jQuery.extend( {}, curData.data );
5389	}
5390}
5391
5392function fixCloneNodeIssues( src, dest ) {
5393	var nodeName, e, data;
5394
5395	// We do not need to do anything for non-Elements
5396	if ( dest.nodeType !== 1 ) {
5397		return;
5398	}
5399
5400	nodeName = dest.nodeName.toLowerCase();
5401
5402	// IE6-8 copies events bound via attachEvent when using cloneNode.
5403	if ( !support.noCloneEvent && dest[ jQuery.expando ] ) {
5404		data = jQuery._data( dest );
5405
5406		for ( e in data.events ) {
5407			jQuery.removeEvent( dest, e, data.handle );
5408		}
5409
5410		// Event data gets referenced instead of copied if the expando gets copied too
5411		dest.removeAttribute( jQuery.expando );
5412	}
5413
5414	// IE blanks contents when cloning scripts, and tries to evaluate newly-set text
5415	if ( nodeName === "script" && dest.text !== src.text ) {
5416		disableScript( dest ).text = src.text;
5417		restoreScript( dest );
5418
5419	// IE6-10 improperly clones children of object elements using classid.
5420	// IE10 throws NoModificationAllowedError if parent is null, #12132.
5421	} else if ( nodeName === "object" ) {
5422		if ( dest.parentNode ) {
5423			dest.outerHTML = src.outerHTML;
5424		}
5425
5426		// This path appears unavoidable for IE9. When cloning an object
5427		// element in IE9, the outerHTML strategy above is not sufficient.
5428		// If the src has innerHTML and the destination does not,
5429		// copy the src.innerHTML into the dest.innerHTML. #10324
5430		if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
5431			dest.innerHTML = src.innerHTML;
5432		}
5433
5434	} else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
5435		// IE6-8 fails to persist the checked state of a cloned checkbox
5436		// or radio button. Worse, IE6-7 fail to give the cloned element
5437		// a checked appearance if the defaultChecked value isn't also set
5438
5439		dest.defaultChecked = dest.checked = src.checked;
5440
5441		// IE6-7 get confused and end up setting the value of a cloned
5442		// checkbox/radio button to an empty string instead of "on"
5443		if ( dest.value !== src.value ) {
5444			dest.value = src.value;
5445		}
5446
5447	// IE6-8 fails to return the selected option to the default selected
5448	// state when cloning options
5449	} else if ( nodeName === "option" ) {
5450		dest.defaultSelected = dest.selected = src.defaultSelected;
5451
5452	// IE6-8 fails to set the defaultValue to the correct value when
5453	// cloning other types of input fields
5454	} else if ( nodeName === "input" || nodeName === "textarea" ) {
5455		dest.defaultValue = src.defaultValue;
5456	}
5457}
5458
5459jQuery.extend({
5460	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
5461		var destElements, node, clone, i, srcElements,
5462			inPage = jQuery.contains( elem.ownerDocument, elem );
5463
5464		if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
5465			clone = elem.cloneNode( true );
5466
5467		// IE<=8 does not properly clone detached, unknown element nodes
5468		} else {
5469			fragmentDiv.innerHTML = elem.outerHTML;
5470			fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
5471		}
5472
5473		if ( (!support.noCloneEvent || !support.noCloneChecked) &&
5474				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
5475
5476			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
5477			destElements = getAll( clone );
5478			srcElements = getAll( elem );
5479
5480			// Fix all IE cloning issues
5481			for ( i = 0; (node = srcElements[i]) != null; ++i ) {
5482				// Ensure that the destination node is not null; Fixes #9587
5483				if ( destElements[i] ) {
5484					fixCloneNodeIssues( node, destElements[i] );
5485				}
5486			}
5487		}
5488
5489		// Copy the events from the original to the clone
5490		if ( dataAndEvents ) {
5491			if ( deepDataAndEvents ) {
5492				srcElements = srcElements || getAll( elem );
5493				destElements = destElements || getAll( clone );
5494
5495				for ( i = 0; (node = srcElements[i]) != null; i++ ) {
5496					cloneCopyEvent( node, destElements[i] );
5497				}
5498			} else {
5499				cloneCopyEvent( elem, clone );
5500			}
5501		}
5502
5503		// Preserve script evaluation history
5504		destElements = getAll( clone, "script" );
5505		if ( destElements.length > 0 ) {
5506			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
5507		}
5508
5509		destElements = srcElements = node = null;
5510
5511		// Return the cloned set
5512		return clone;
5513	},
5514
5515	buildFragment: function( elems, context, scripts, selection ) {
5516		var j, elem, contains,
5517			tmp, tag, tbody, wrap,
5518			l = elems.length,
5519
5520			// Ensure a safe fragment
5521			safe = createSafeFragment( context ),
5522
5523			nodes = [],
5524			i = 0;
5525
5526		for ( ; i < l; i++ ) {
5527			elem = elems[ i ];
5528
5529			if ( elem || elem === 0 ) {
5530
5531				// Add nodes directly
5532				if ( jQuery.type( elem ) === "object" ) {
5533					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
5534
5535				// Convert non-html into a text node
5536				} else if ( !rhtml.test( elem ) ) {
5537					nodes.push( context.createTextNode( elem ) );
5538
5539				// Convert html into DOM nodes
5540				} else {
5541					tmp = tmp || safe.appendChild( context.createElement("div") );
5542
5543					// Deserialize a standard representation
5544					tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase();
5545					wrap = wrapMap[ tag ] || wrapMap._default;
5546
5547					tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];
5548
5549					// Descend through wrappers to the right content
5550					j = wrap[0];
5551					while ( j-- ) {
5552						tmp = tmp.lastChild;
5553					}
5554
5555					// Manually add leading whitespace removed by IE
5556					if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
5557						nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
5558					}
5559
5560					// Remove IE's autoinserted <tbody> from table fragments
5561					if ( !support.tbody ) {
5562
5563						// String was a <table>, *may* have spurious <tbody>
5564						elem = tag === "table" && !rtbody.test( elem ) ?
5565							tmp.firstChild :
5566
5567							// String was a bare <thead> or <tfoot>
5568							wrap[1] === "<table>" && !rtbody.test( elem ) ?
5569								tmp :
5570								0;
5571
5572						j = elem && elem.childNodes.length;
5573						while ( j-- ) {
5574							if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
5575								elem.removeChild( tbody );
5576							}
5577						}
5578					}
5579
5580					jQuery.merge( nodes, tmp.childNodes );
5581
5582					// Fix #12392 for WebKit and IE > 9
5583					tmp.textContent = "";
5584
5585					// Fix #12392 for oldIE
5586					while ( tmp.firstChild ) {
5587						tmp.removeChild( tmp.firstChild );
5588					}
5589
5590					// Remember the top-level container for proper cleanup
5591					tmp = safe.lastChild;
5592				}
5593			}
5594		}
5595
5596		// Fix #11356: Clear elements from fragment
5597		if ( tmp ) {
5598			safe.removeChild( tmp );
5599		}
5600
5601		// Reset defaultChecked for any radios and checkboxes
5602		// about to be appended to the DOM in IE 6/7 (#8060)
5603		if ( !support.appendChecked ) {
5604			jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
5605		}
5606
5607		i = 0;
5608		while ( (elem = nodes[ i++ ]) ) {
5609
5610			// #4087 - If origin and destination elements are the same, and this is
5611			// that element, do not do anything
5612			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
5613				continue;
5614			}
5615
5616			contains = jQuery.contains( elem.ownerDocument, elem );
5617
5618			// Append to fragment
5619			tmp = getAll( safe.appendChild( elem ), "script" );
5620
5621			// Preserve script evaluation history
5622			if ( contains ) {
5623				setGlobalEval( tmp );
5624			}
5625
5626			// Capture executables
5627			if ( scripts ) {
5628				j = 0;
5629				while ( (elem = tmp[ j++ ]) ) {
5630					if ( rscriptType.test( elem.type || "" ) ) {
5631						scripts.push( elem );
5632					}
5633				}
5634			}
5635		}
5636
5637		tmp = null;
5638
5639		return safe;
5640	},
5641
5642	cleanData: function( elems, /* internal */ acceptData ) {
5643		var elem, type, id, data,
5644			i = 0,
5645			internalKey = jQuery.expando,
5646			cache = jQuery.cache,
5647			deleteExpando = support.deleteExpando,
5648			special = jQuery.event.special;
5649
5650		for ( ; (elem = elems[i]) != null; i++ ) {
5651			if ( acceptData || jQuery.acceptData( elem ) ) {
5652
5653				id = elem[ internalKey ];
5654				data = id && cache[ id ];
5655
5656				if ( data ) {
5657					if ( data.events ) {
5658						for ( type in data.events ) {
5659							if ( special[ type ] ) {
5660								jQuery.event.remove( elem, type );
5661
5662							// This is a shortcut to avoid jQuery.event.remove's overhead
5663							} else {
5664								jQuery.removeEvent( elem, type, data.handle );
5665							}
5666						}
5667					}
5668
5669					// Remove cache only if it was not already removed by jQuery.event.remove
5670					if ( cache[ id ] ) {
5671
5672						delete cache[ id ];
5673
5674						// IE does not allow us to delete expando properties from nodes,
5675						// nor does it have a removeAttribute function on Document nodes;
5676						// we must handle all of these cases
5677						if ( deleteExpando ) {
5678							delete elem[ internalKey ];
5679
5680						} else if ( typeof elem.removeAttribute !== strundefined ) {
5681							elem.removeAttribute( internalKey );
5682
5683						} else {
5684							elem[ internalKey ] = null;
5685						}
5686
5687						deletedIds.push( id );
5688					}
5689				}
5690			}
5691		}
5692	}
5693});
5694
5695jQuery.fn.extend({
5696	text: function( value ) {
5697		return access( this, function( value ) {
5698			return value === undefined ?
5699				jQuery.text( this ) :
5700				this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
5701		}, null, value, arguments.length );
5702	},
5703
5704	append: function() {
5705		return this.domManip( arguments, function( elem ) {
5706			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
5707				var target = manipulationTarget( this, elem );
5708				target.appendChild( elem );
5709			}
5710		});
5711	},
5712
5713	prepend: function() {
5714		return this.domManip( arguments, function( elem ) {
5715			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
5716				var target = manipulationTarget( this, elem );
5717				target.insertBefore( elem, target.firstChild );
5718			}
5719		});
5720	},
5721
5722	before: function() {
5723		return this.domManip( arguments, function( elem ) {
5724			if ( this.parentNode ) {
5725				this.parentNode.insertBefore( elem, this );
5726			}
5727		});
5728	},
5729
5730	after: function() {
5731		return this.domManip( arguments, function( elem ) {
5732			if ( this.parentNode ) {
5733				this.parentNode.insertBefore( elem, this.nextSibling );
5734			}
5735		});
5736	},
5737
5738	remove: function( selector, keepData /* Internal Use Only */ ) {
5739		var elem,
5740			elems = selector ? jQuery.filter( selector, this ) : this,
5741			i = 0;
5742
5743		for ( ; (elem = elems[i]) != null; i++ ) {
5744
5745			if ( !keepData && elem.nodeType === 1 ) {
5746				jQuery.cleanData( getAll( elem ) );
5747			}
5748
5749			if ( elem.parentNode ) {
5750				if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
5751					setGlobalEval( getAll( elem, "script" ) );
5752				}
5753				elem.parentNode.removeChild( elem );
5754			}
5755		}
5756
5757		return this;
5758	},
5759
5760	empty: function() {
5761		var elem,
5762			i = 0;
5763
5764		for ( ; (elem = this[i]) != null; i++ ) {
5765			// Remove element nodes and prevent memory leaks
5766			if ( elem.nodeType === 1 ) {
5767				jQuery.cleanData( getAll( elem, false ) );
5768			}
5769
5770			// Remove any remaining nodes
5771			while ( elem.firstChild ) {
5772				elem.removeChild( elem.firstChild );
5773			}
5774
5775			// If this is a select, ensure that it displays empty (#12336)
5776			// Support: IE<9
5777			if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
5778				elem.options.length = 0;
5779			}
5780		}
5781
5782		return this;
5783	},
5784
5785	clone: function( dataAndEvents, deepDataAndEvents ) {
5786		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
5787		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
5788
5789		return this.map(function() {
5790			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
5791		});
5792	},
5793
5794	html: function( value ) {
5795		return access( this, function( value ) {
5796			var elem = this[ 0 ] || {},
5797				i = 0,
5798				l = this.length;
5799
5800			if ( value === undefined ) {
5801				return elem.nodeType === 1 ?
5802					elem.innerHTML.replace( rinlinejQuery, "" ) :
5803					undefined;
5804			}
5805
5806			// See if we can take a shortcut and just use innerHTML
5807			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
5808				( support.htmlSerialize || !rnoshimcache.test( value )  ) &&
5809				( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
5810				!wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) {
5811
5812				value = value.replace( rxhtmlTag, "<$1></$2>" );
5813
5814				try {
5815					for (; i < l; i++ ) {
5816						// Remove element nodes and prevent memory leaks
5817						elem = this[i] || {};
5818						if ( elem.nodeType === 1 ) {
5819							jQuery.cleanData( getAll( elem, false ) );
5820							elem.innerHTML = value;
5821						}
5822					}
5823
5824					elem = 0;
5825
5826				// If using innerHTML throws an exception, use the fallback method
5827				} catch(e) {}
5828			}
5829
5830			if ( elem ) {
5831				this.empty().append( value );
5832			}
5833		}, null, value, arguments.length );
5834	},
5835
5836	replaceWith: function() {
5837		var arg = arguments[ 0 ];
5838
5839		// Make the changes, replacing each context element with the new content
5840		this.domManip( arguments, function( elem ) {
5841			arg = this.parentNode;
5842
5843			jQuery.cleanData( getAll( this ) );
5844
5845			if ( arg ) {
5846				arg.replaceChild( elem, this );
5847			}
5848		});
5849
5850		// Force removal if there was no new content (e.g., from empty arguments)
5851		return arg && (arg.length || arg.nodeType) ? this : this.remove();
5852	},
5853
5854	detach: function( selector ) {
5855		return this.remove( selector, true );
5856	},
5857
5858	domManip: function( args, callback ) {
5859
5860		// Flatten any nested arrays
5861		args = concat.apply( [], args );
5862
5863		var first, node, hasScripts,
5864			scripts, doc, fragment,
5865			i = 0,
5866			l = this.length,
5867			set = this,
5868			iNoClone = l - 1,
5869			value = args[0],
5870			isFunction = jQuery.isFunction( value );
5871
5872		// We can't cloneNode fragments that contain checked, in WebKit
5873		if ( isFunction ||
5874				( l > 1 && typeof value === "string" &&
5875					!support.checkClone && rchecked.test( value ) ) ) {
5876			return this.each(function( index ) {
5877				var self = set.eq( index );
5878				if ( isFunction ) {
5879					args[0] = value.call( this, index, self.html() );
5880				}
5881				self.domManip( args, callback );
5882			});
5883		}
5884
5885		if ( l ) {
5886			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
5887			first = fragment.firstChild;
5888
5889			if ( fragment.childNodes.length === 1 ) {
5890				fragment = first;
5891			}
5892
5893			if ( first ) {
5894				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
5895				hasScripts = scripts.length;
5896
5897				// Use the original fragment for the last item instead of the first because it can end up
5898				// being emptied incorrectly in certain situations (#8070).
5899				for ( ; i < l; i++ ) {
5900					node = fragment;
5901
5902					if ( i !== iNoClone ) {
5903						node = jQuery.clone( node, true, true );
5904
5905						// Keep references to cloned scripts for later restoration
5906						if ( hasScripts ) {
5907							jQuery.merge( scripts, getAll( node, "script" ) );
5908						}
5909					}
5910
5911					callback.call( this[i], node, i );
5912				}
5913
5914				if ( hasScripts ) {
5915					doc = scripts[ scripts.length - 1 ].ownerDocument;
5916
5917					// Reenable scripts
5918					jQuery.map( scripts, restoreScript );
5919
5920					// Evaluate executable scripts on first document insertion
5921					for ( i = 0; i < hasScripts; i++ ) {
5922						node = scripts[ i ];
5923						if ( rscriptType.test( node.type || "" ) &&
5924							!jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
5925
5926							if ( node.src ) {
5927								// Optional AJAX dependency, but won't run scripts if not present
5928								if ( jQuery._evalUrl ) {
5929									jQuery._evalUrl( node.src );
5930								}
5931							} else {
5932								jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
5933							}
5934						}
5935					}
5936				}
5937
5938				// Fix #11809: Avoid leaking memory
5939				fragment = first = null;
5940			}
5941		}
5942
5943		return this;
5944	}
5945});
5946
5947jQuery.each({
5948	appendTo: "append",
5949	prependTo: "prepend",
5950	insertBefore: "before",
5951	insertAfter: "after",
5952	replaceAll: "replaceWith"
5953}, function( name, original ) {
5954	jQuery.fn[ name ] = function( selector ) {
5955		var elems,
5956			i = 0,
5957			ret = [],
5958			insert = jQuery( selector ),
5959			last = insert.length - 1;
5960
5961		for ( ; i <= last; i++ ) {
5962			elems = i === last ? this : this.clone(true);
5963			jQuery( insert[i] )[ original ]( elems );
5964
5965			// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
5966			push.apply( ret, elems.get() );
5967		}
5968
5969		return this.pushStack( ret );
5970	};
5971});
5972
5973
5974var iframe,
5975	elemdisplay = {};
5976
5977/**
5978 * Retrieve the actual display of a element
5979 * @param {String} name nodeName of the element
5980 * @param {Object} doc Document object
5981 */
5982// Called only from within defaultDisplay
5983function actualDisplay( name, doc ) {
5984	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
5985
5986		// getDefaultComputedStyle might be reliably used only on attached element
5987		display = window.getDefaultComputedStyle ?
5988
5989			// Use of this method is a temporary fix (more like optmization) until something better comes along,
5990			// since it was removed from specification and supported only in FF
5991			window.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], "display" );
5992
5993	// We don't have any data stored on the element,
5994	// so use "detach" method as fast way to get rid of the element
5995	elem.detach();
5996
5997	return display;
5998}
5999
6000/**
6001 * Try to determine the default display value of an element
6002 * @param {String} nodeName
6003 */
6004function defaultDisplay( nodeName ) {
6005	var doc = document,
6006		display = elemdisplay[ nodeName ];
6007
6008	if ( !display ) {
6009		display = actualDisplay( nodeName, doc );
6010
6011		// If the simple way fails, read from inside an iframe
6012		if ( display === "none" || !display ) {
6013
6014			// Use the already-created iframe if possible
6015			iframe = (iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" )).appendTo( doc.documentElement );
6016
6017			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
6018			doc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;
6019
6020			// Support: IE
6021			doc.write();
6022			doc.close();
6023
6024			display = actualDisplay( nodeName, doc );
6025			iframe.detach();
6026		}
6027
6028		// Store the correct default display
6029		elemdisplay[ nodeName ] = display;
6030	}
6031
6032	return display;
6033}
6034
6035
6036(function() {
6037	var a, shrinkWrapBlocksVal,
6038		div = document.createElement( "div" ),
6039		divReset =
6040			"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;" +
6041			"display:block;padding:0;margin:0;border:0";
6042
6043	// Setup
6044	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
6045	a = div.getElementsByTagName( "a" )[ 0 ];
6046
6047	a.style.cssText = "float:left;opacity:.5";
6048
6049	// Make sure that element opacity exists
6050	// (IE uses filter instead)
6051	// Use a regex to work around a WebKit issue. See #5145
6052	support.opacity = /^0.5/.test( a.style.opacity );
6053
6054	// Verify style float existence
6055	// (IE uses styleFloat instead of cssFloat)
6056	support.cssFloat = !!a.style.cssFloat;
6057
6058	div.style.backgroundClip = "content-box";
6059	div.cloneNode( true ).style.backgroundClip = "";
6060	support.clearCloneStyle = div.style.backgroundClip === "content-box";
6061
6062	// Null elements to avoid leaks in IE.
6063	a = div = null;
6064
6065	support.shrinkWrapBlocks = function() {
6066		var body, container, div, containerStyles;
6067
6068		if ( shrinkWrapBlocksVal == null ) {
6069			body = document.getElementsByTagName( "body" )[ 0 ];
6070			if ( !body ) {
6071				// Test fired too early or in an unsupported environment, exit.
6072				return;
6073			}
6074
6075			containerStyles = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px";
6076			container = document.createElement( "div" );
6077			div = document.createElement( "div" );
6078
6079			body.appendChild( container ).appendChild( div );
6080
6081			// Will be changed later if needed.
6082			shrinkWrapBlocksVal = false;
6083
6084			if ( typeof div.style.zoom !== strundefined ) {
6085				// Support: IE6
6086				// Check if elements with layout shrink-wrap their children
6087				div.style.cssText = divReset + ";width:1px;padding:1px;zoom:1";
6088				div.innerHTML = "<div></div>";
6089				div.firstChild.style.width = "5px";
6090				shrinkWrapBlocksVal = div.offsetWidth !== 3;
6091			}
6092
6093			body.removeChild( container );
6094
6095			// Null elements to avoid leaks in IE.
6096			body = container = div = null;
6097		}
6098
6099		return shrinkWrapBlocksVal;
6100	};
6101
6102})();
6103var rmargin = (/^margin/);
6104
6105var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
6106
6107
6108
6109var getStyles, curCSS,
6110	rposition = /^(top|right|bottom|left)$/;
6111
6112if ( window.getComputedStyle ) {
6113	getStyles = function( elem ) {
6114		return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
6115	};
6116
6117	curCSS = function( elem, name, computed ) {
6118		var width, minWidth, maxWidth, ret,
6119			style = elem.style;
6120
6121		computed = computed || getStyles( elem );
6122
6123		// getPropertyValue is only needed for .css('filter') in IE9, see #12537
6124		ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;
6125
6126		if ( computed ) {
6127
6128			if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
6129				ret = jQuery.style( elem, name );
6130			}
6131
6132			// A tribute to the "awesome hack by Dean Edwards"
6133			// Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
6134			// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
6135			// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
6136			if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
6137
6138				// Remember the original values
6139				width = style.width;
6140				minWidth = style.minWidth;
6141				maxWidth = style.maxWidth;
6142
6143				// Put in the new values to get a computed value out
6144				style.minWidth = style.maxWidth = style.width = ret;
6145				ret = computed.width;
6146
6147				// Revert the changed values
6148				style.width = width;
6149				style.minWidth = minWidth;
6150				style.maxWidth = maxWidth;
6151			}
6152		}
6153
6154		// Support: IE
6155		// IE returns zIndex value as an integer.
6156		return ret === undefined ?
6157			ret :
6158			ret + "";
6159	};
6160} else if ( document.documentElement.currentStyle ) {
6161	getStyles = function( elem ) {
6162		return elem.currentStyle;
6163	};
6164
6165	curCSS = function( elem, name, computed ) {
6166		var left, rs, rsLeft, ret,
6167			style = elem.style;
6168
6169		computed = computed || getStyles( elem );
6170		ret = computed ? computed[ name ] : undefined;
6171
6172		// Avoid setting ret to empty string here
6173		// so we don't default to auto
6174		if ( ret == null && style && style[ name ] ) {
6175			ret = style[ name ];
6176		}
6177
6178		// From the awesome hack by Dean Edwards
6179		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
6180
6181		// If we're not dealing with a regular pixel number
6182		// but a number that has a weird ending, we need to convert it to pixels
6183		// but not position css attributes, as those are proportional to the parent element instead
6184		// and we can't measure the parent instead because it might trigger a "stacking dolls" problem
6185		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
6186
6187			// Remember the original values
6188			left = style.left;
6189			rs = elem.runtimeStyle;
6190			rsLeft = rs && rs.left;
6191
6192			// Put in the new values to get a computed value out
6193			if ( rsLeft ) {
6194				rs.left = elem.currentStyle.left;
6195			}
6196			style.left = name === "fontSize" ? "1em" : ret;
6197			ret = style.pixelLeft + "px";
6198
6199			// Revert the changed values
6200			style.left = left;
6201			if ( rsLeft ) {
6202				rs.left = rsLeft;
6203			}
6204		}
6205
6206		// Support: IE
6207		// IE returns zIndex value as an integer.
6208		return ret === undefined ?
6209			ret :
6210			ret + "" || "auto";
6211	};
6212}
6213
6214
6215
6216
6217function addGetHookIf( conditionFn, hookFn ) {
6218	// Define the hook, we'll check on the first run if it's really needed.
6219	return {
6220		get: function() {
6221			var condition = conditionFn();
6222
6223			if ( condition == null ) {
6224				// The test was not ready at this point; screw the hook this time
6225				// but check again when needed next time.
6226				return;
6227			}
6228
6229			if ( condition ) {
6230				// Hook not needed (or it's not possible to use it due to missing dependency),
6231				// remove it.
6232				// Since there are no other hooks for marginRight, remove the whole object.
6233				delete this.get;
6234				return;
6235			}
6236
6237			// Hook needed; redefine it so that the support test is not executed again.
6238
6239			return (this.get = hookFn).apply( this, arguments );
6240		}
6241	};
6242}
6243
6244
6245(function() {
6246	var a, reliableHiddenOffsetsVal, boxSizingVal, boxSizingReliableVal,
6247		pixelPositionVal, reliableMarginRightVal,
6248		div = document.createElement( "div" ),
6249		containerStyles = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px",
6250		divReset =
6251			"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;" +
6252			"display:block;padding:0;margin:0;border:0";
6253
6254	// Setup
6255	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
6256	a = div.getElementsByTagName( "a" )[ 0 ];
6257
6258	a.style.cssText = "float:left;opacity:.5";
6259
6260	// Make sure that element opacity exists
6261	// (IE uses filter instead)
6262	// Use a regex to work around a WebKit issue. See #5145
6263	support.opacity = /^0.5/.test( a.style.opacity );
6264
6265	// Verify style float existence
6266	// (IE uses styleFloat instead of cssFloat)
6267	support.cssFloat = !!a.style.cssFloat;
6268
6269	div.style.backgroundClip = "content-box";
6270	div.cloneNode( true ).style.backgroundClip = "";
6271	support.clearCloneStyle = div.style.backgroundClip === "content-box";
6272
6273	// Null elements to avoid leaks in IE.
6274	a = div = null;
6275
6276	jQuery.extend(support, {
6277		reliableHiddenOffsets: function() {
6278			if ( reliableHiddenOffsetsVal != null ) {
6279				return reliableHiddenOffsetsVal;
6280			}
6281
6282			var container, tds, isSupported,
6283				div = document.createElement( "div" ),
6284				body = document.getElementsByTagName( "body" )[ 0 ];
6285
6286			if ( !body ) {
6287				// Return for frameset docs that don't have a body
6288				return;
6289			}
6290
6291			// Setup
6292			div.setAttribute( "className", "t" );
6293			div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
6294
6295			container = document.createElement( "div" );
6296			container.style.cssText = containerStyles;
6297
6298			body.appendChild( container ).appendChild( div );
6299
6300			// Support: IE8
6301			// Check if table cells still have offsetWidth/Height when they are set
6302			// to display:none and there are still other visible table cells in a
6303			// table row; if so, offsetWidth/Height are not reliable for use when
6304			// determining if an element has been hidden directly using
6305			// display:none (it is still safe to use offsets if a parent element is
6306			// hidden; don safety goggles and see bug #4512 for more information).
6307			div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
6308			tds = div.getElementsByTagName( "td" );
6309			tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
6310			isSupported = ( tds[ 0 ].offsetHeight === 0 );
6311
6312			tds[ 0 ].style.display = "";
6313			tds[ 1 ].style.display = "none";
6314
6315			// Support: IE8
6316			// Check if empty table cells still have offsetWidth/Height
6317			reliableHiddenOffsetsVal = isSupported && ( tds[ 0 ].offsetHeight === 0 );
6318
6319			body.removeChild( container );
6320
6321			// Null elements to avoid leaks in IE.
6322			div = body = null;
6323
6324			return reliableHiddenOffsetsVal;
6325		},
6326
6327		boxSizing: function() {
6328			if ( boxSizingVal == null ) {
6329				computeStyleTests();
6330			}
6331			return boxSizingVal;
6332		},
6333
6334		boxSizingReliable: function() {
6335			if ( boxSizingReliableVal == null ) {
6336				computeStyleTests();
6337			}
6338			return boxSizingReliableVal;
6339		},
6340
6341		pixelPosition: function() {
6342			if ( pixelPositionVal == null ) {
6343				computeStyleTests();
6344			}
6345			return pixelPositionVal;
6346		},
6347
6348		reliableMarginRight: function() {
6349			var body, container, div, marginDiv;
6350
6351			// Use window.getComputedStyle because jsdom on node.js will break without it.
6352			if ( reliableMarginRightVal == null && window.getComputedStyle ) {
6353				body = document.getElementsByTagName( "body" )[ 0 ];
6354				if ( !body ) {
6355					// Test fired too early or in an unsupported environment, exit.
6356					return;
6357				}
6358
6359				container = document.createElement( "div" );
6360				div = document.createElement( "div" );
6361				container.style.cssText = containerStyles;
6362
6363				body.appendChild( container ).appendChild( div );
6364
6365				// Check if div with explicit width and no margin-right incorrectly
6366				// gets computed margin-right based on width of container. (#3333)
6367				// Fails in WebKit before Feb 2011 nightlies
6368				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
6369				marginDiv = div.appendChild( document.createElement( "div" ) );
6370				marginDiv.style.cssText = div.style.cssText = divReset;
6371				marginDiv.style.marginRight = marginDiv.style.width = "0";
6372				div.style.width = "1px";
6373
6374				reliableMarginRightVal =
6375					!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
6376
6377				body.removeChild( container );
6378			}
6379
6380			return reliableMarginRightVal;
6381		}
6382	});
6383
6384	function computeStyleTests() {
6385		var container, div,
6386			body = document.getElementsByTagName( "body" )[ 0 ];
6387
6388		if ( !body ) {
6389			// Test fired too early or in an unsupported environment, exit.
6390			return;
6391		}
6392
6393		container = document.createElement( "div" );
6394		div = document.createElement( "div" );
6395		container.style.cssText = containerStyles;
6396
6397		body.appendChild( container ).appendChild( div );
6398
6399		div.style.cssText =
6400			"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;" +
6401				"position:absolute;display:block;padding:1px;border:1px;width:4px;" +
6402				"margin-top:1%;top:1%";
6403
6404		// Workaround failing boxSizing test due to offsetWidth returning wrong value
6405		// with some non-1 values of body zoom, ticket #13543
6406		jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
6407			boxSizingVal = div.offsetWidth === 4;
6408		});
6409
6410		// Will be changed later if needed.
6411		boxSizingReliableVal = true;
6412		pixelPositionVal = false;
6413		reliableMarginRightVal = true;
6414
6415		// Use window.getComputedStyle because jsdom on node.js will break without it.
6416		if ( window.getComputedStyle ) {
6417			pixelPositionVal = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
6418			boxSizingReliableVal =
6419				( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
6420		}
6421
6422		body.removeChild( container );
6423
6424		// Null elements to avoid leaks in IE.
6425		div = body = null;
6426	}
6427
6428})();
6429
6430
6431// A method for quickly swapping in/out CSS properties to get correct calculations.
6432jQuery.swap = function( elem, options, callback, args ) {
6433	var ret, name,
6434		old = {};
6435
6436	// Remember the old values, and insert the new ones
6437	for ( name in options ) {
6438		old[ name ] = elem.style[ name ];
6439		elem.style[ name ] = options[ name ];
6440	}
6441
6442	ret = callback.apply( elem, args || [] );
6443
6444	// Revert the old values
6445	for ( name in options ) {
6446		elem.style[ name ] = old[ name ];
6447	}
6448
6449	return ret;
6450};
6451
6452
6453var
6454		ralpha = /alpha\([^)]*\)/i,
6455	ropacity = /opacity\s*=\s*([^)]*)/,
6456
6457	// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
6458	// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
6459	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
6460	rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
6461	rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ),
6462
6463	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
6464	cssNormalTransform = {
6465		letterSpacing: 0,
6466		fontWeight: 400
6467	},
6468
6469	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
6470
6471
6472// return a css property mapped to a potentially vendor prefixed property
6473function vendorPropName( style, name ) {
6474
6475	// shortcut for names that are not vendor prefixed
6476	if ( name in style ) {
6477		return name;
6478	}
6479
6480	// check for vendor prefixed names
6481	var capName = name.charAt(0).toUpperCase() + name.slice(1),
6482		origName = name,
6483		i = cssPrefixes.length;
6484
6485	while ( i-- ) {
6486		name = cssPrefixes[ i ] + capName;
6487		if ( name in style ) {
6488			return name;
6489		}
6490	}
6491
6492	return origName;
6493}
6494
6495function showHide( elements, show ) {
6496	var display, elem, hidden,
6497		values = [],
6498		index = 0,
6499		length = elements.length;
6500
6501	for ( ; index < length; index++ ) {
6502		elem = elements[ index ];
6503		if ( !elem.style ) {
6504			continue;
6505		}
6506
6507		values[ index ] = jQuery._data( elem, "olddisplay" );
6508		display = elem.style.display;
6509		if ( show ) {
6510			// Reset the inline display of this element to learn if it is
6511			// being hidden by cascaded rules or not
6512			if ( !values[ index ] && display === "none" ) {
6513				elem.style.display = "";
6514			}
6515
6516			// Set elements which have been overridden with display: none
6517			// in a stylesheet to whatever the default browser style is
6518			// for such an element
6519			if ( elem.style.display === "" && isHidden( elem ) ) {
6520				values[ index ] = jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
6521			}
6522		} else {
6523
6524			if ( !values[ index ] ) {
6525				hidden = isHidden( elem );
6526
6527				if ( display && display !== "none" || !hidden ) {
6528					jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
6529				}
6530			}
6531		}
6532	}
6533
6534	// Set the display of most of the elements in a second loop
6535	// to avoid the constant reflow
6536	for ( index = 0; index < length; index++ ) {
6537		elem = elements[ index ];
6538		if ( !elem.style ) {
6539			continue;
6540		}
6541		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
6542			elem.style.display = show ? values[ index ] || "" : "none";
6543		}
6544	}
6545
6546	return elements;
6547}
6548
6549function setPositiveNumber( elem, value, subtract ) {
6550	var matches = rnumsplit.exec( value );
6551	return matches ?
6552		// Guard against undefined "subtract", e.g., when used as in cssHooks
6553		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
6554		value;
6555}
6556
6557function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
6558	var i = extra === ( isBorderBox ? "border" : "content" ) ?
6559		// If we already have the right measurement, avoid augmentation
6560		4 :
6561		// Otherwise initialize for horizontal or vertical properties
6562		name === "width" ? 1 : 0,
6563
6564		val = 0;
6565
6566	for ( ; i < 4; i += 2 ) {
6567		// both box models exclude margin, so add it if we want it
6568		if ( extra === "margin" ) {
6569			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
6570		}
6571
6572		if ( isBorderBox ) {
6573			// border-box includes padding, so remove it if we want content
6574			if ( extra === "content" ) {
6575				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
6576			}
6577
6578			// at this point, extra isn't border nor margin, so remove border
6579			if ( extra !== "margin" ) {
6580				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
6581			}
6582		} else {
6583			// at this point, extra isn't content, so add padding
6584			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
6585
6586			// at this point, extra isn't content nor padding, so add border
6587			if ( extra !== "padding" ) {
6588				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
6589			}
6590		}
6591	}
6592
6593	return val;
6594}
6595
6596function getWidthOrHeight( elem, name, extra ) {
6597
6598	// Start with offset property, which is equivalent to the border-box value
6599	var valueIsBorderBox = true,
6600		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
6601		styles = getStyles( elem ),
6602		isBorderBox = support.boxSizing() && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
6603
6604	// some non-html elements return undefined for offsetWidth, so check for null/undefined
6605	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
6606	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
6607	if ( val <= 0 || val == null ) {
6608		// Fall back to computed then uncomputed css if necessary
6609		val = curCSS( elem, name, styles );
6610		if ( val < 0 || val == null ) {
6611			val = elem.style[ name ];
6612		}
6613
6614		// Computed unit is not pixels. Stop here and return.
6615		if ( rnumnonpx.test(val) ) {
6616			return val;
6617		}
6618
6619		// we need the check for style in case a browser which returns unreliable values
6620		// for getComputedStyle silently falls back to the reliable elem.style
6621		valueIsBorderBox = isBorderBox && ( support.boxSizingReliable() || val === elem.style[ name ] );
6622
6623		// Normalize "", auto, and prepare for extra
6624		val = parseFloat( val ) || 0;
6625	}
6626
6627	// use the active box-sizing model to add/subtract irrelevant styles
6628	return ( val +
6629		augmentWidthOrHeight(
6630			elem,
6631			name,
6632			extra || ( isBorderBox ? "border" : "content" ),
6633			valueIsBorderBox,
6634			styles
6635		)
6636	) + "px";
6637}
6638
6639jQuery.extend({
6640	// Add in style property hooks for overriding the default
6641	// behavior of getting and setting a style property
6642	cssHooks: {
6643		opacity: {
6644			get: function( elem, computed ) {
6645				if ( computed ) {
6646					// We should always get a number back from opacity
6647					var ret = curCSS( elem, "opacity" );
6648					return ret === "" ? "1" : ret;
6649				}
6650			}
6651		}
6652	},
6653
6654	// Don't automatically add "px" to these possibly-unitless properties
6655	cssNumber: {
6656		"columnCount": true,
6657		"fillOpacity": true,
6658		"fontWeight": true,
6659		"lineHeight": true,
6660		"opacity": true,
6661		"order": true,
6662		"orphans": true,
6663		"widows": true,
6664		"zIndex": true,
6665		"zoom": true
6666	},
6667
6668	// Add in properties whose names you wish to fix before
6669	// setting or getting the value
6670	cssProps: {
6671		// normalize float css property
6672		"float": support.cssFloat ? "cssFloat" : "styleFloat"
6673	},
6674
6675	// Get and set the style property on a DOM Node
6676	style: function( elem, name, value, extra ) {
6677		// Don't set styles on text and comment nodes
6678		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
6679			return;
6680		}
6681
6682		// Make sure that we're working with the right name
6683		var ret, type, hooks,
6684			origName = jQuery.camelCase( name ),
6685			style = elem.style;
6686
6687		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
6688
6689		// gets hook for the prefixed version
6690		// followed by the unprefixed version
6691		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
6692
6693		// Check if we're setting a value
6694		if ( value !== undefined ) {
6695			type = typeof value;
6696
6697			// convert relative number strings (+= or -=) to relative numbers. #7345
6698			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
6699				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
6700				// Fixes bug #9237
6701				type = "number";
6702			}
6703
6704			// Make sure that null and NaN values aren't set. See: #7116
6705			if ( value == null || value !== value ) {
6706				return;
6707			}
6708
6709			// If a number was passed in, add 'px' to the (except for certain CSS properties)
6710			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
6711				value += "px";
6712			}
6713
6714			// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
6715			// but it would mean to define eight (for every problematic property) identical functions
6716			if ( !support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
6717				style[ name ] = "inherit";
6718			}
6719
6720			// If a hook was provided, use that value, otherwise just set the specified value
6721			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
6722
6723				// Support: IE
6724				// Swallow errors from 'invalid' CSS values (#5509)
6725				try {
6726					// Support: Chrome, Safari
6727					// Setting style to blank string required to delete "style: x !important;"
6728					style[ name ] = "";
6729					style[ name ] = value;
6730				} catch(e) {}
6731			}
6732
6733		} else {
6734			// If a hook was provided get the non-computed value from there
6735			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
6736				return ret;
6737			}
6738
6739			// Otherwise just get the value from the style object
6740			return style[ name ];
6741		}
6742	},
6743
6744	css: function( elem, name, extra, styles ) {
6745		var num, val, hooks,
6746			origName = jQuery.camelCase( name );
6747
6748		// Make sure that we're working with the right name
6749		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
6750
6751		// gets hook for the prefixed version
6752		// followed by the unprefixed version
6753		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
6754
6755		// If a hook was provided get the computed value from there
6756		if ( hooks && "get" in hooks ) {
6757			val = hooks.get( elem, true, extra );
6758		}
6759
6760		// Otherwise, if a way to get the computed value exists, use that
6761		if ( val === undefined ) {
6762			val = curCSS( elem, name, styles );
6763		}
6764
6765		//convert "normal" to computed value
6766		if ( val === "normal" && name in cssNormalTransform ) {
6767			val = cssNormalTransform[ name ];
6768		}
6769
6770		// Return, converting to number if forced or a qualifier was provided and val looks numeric
6771		if ( extra === "" || extra ) {
6772			num = parseFloat( val );
6773			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
6774		}
6775		return val;
6776	}
6777});
6778
6779jQuery.each([ "height", "width" ], function( i, name ) {
6780	jQuery.cssHooks[ name ] = {
6781		get: function( elem, computed, extra ) {
6782			if ( computed ) {
6783				// certain elements can have dimension info if we invisibly show them
6784				// however, it must have a current display style that would benefit from this
6785				return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
6786					jQuery.swap( elem, cssShow, function() {
6787						return getWidthOrHeight( elem, name, extra );
6788					}) :
6789					getWidthOrHeight( elem, name, extra );
6790			}
6791		},
6792
6793		set: function( elem, value, extra ) {
6794			var styles = extra && getStyles( elem );
6795			return setPositiveNumber( elem, value, extra ?
6796				augmentWidthOrHeight(
6797					elem,
6798					name,
6799					extra,
6800					support.boxSizing() && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
6801					styles
6802				) : 0
6803			);
6804		}
6805	};
6806});
6807
6808if ( !support.opacity ) {
6809	jQuery.cssHooks.opacity = {
6810		get: function( elem, computed ) {
6811			// IE uses filters for opacity
6812			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
6813				( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
6814				computed ? "1" : "";
6815		},
6816
6817		set: function( elem, value ) {
6818			var style = elem.style,
6819				currentStyle = elem.currentStyle,
6820				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
6821				filter = currentStyle && currentStyle.filter || style.filter || "";
6822
6823			// IE has trouble with opacity if it does not have layout
6824			// Force it by setting the zoom level
6825			style.zoom = 1;
6826
6827			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
6828			// if value === "", then remove inline opacity #12685
6829			if ( ( value >= 1 || value === "" ) &&
6830					jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
6831					style.removeAttribute ) {
6832
6833				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
6834				// if "filter:" is present at all, clearType is disabled, we want to avoid this
6835				// style.removeAttribute is IE Only, but so apparently is this code path...
6836				style.removeAttribute( "filter" );
6837
6838				// if there is no filter style applied in a css rule or unset inline opacity, we are done
6839				if ( value === "" || currentStyle && !currentStyle.filter ) {
6840					return;
6841				}
6842			}
6843
6844			// otherwise, set new filter values
6845			style.filter = ralpha.test( filter ) ?
6846				filter.replace( ralpha, opacity ) :
6847				filter + " " + opacity;
6848		}
6849	};
6850}
6851
6852jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
6853	function( elem, computed ) {
6854		if ( computed ) {
6855			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
6856			// Work around by temporarily setting element display to inline-block
6857			return jQuery.swap( elem, { "display": "inline-block" },
6858				curCSS, [ elem, "marginRight" ] );
6859		}
6860	}
6861);
6862
6863// These hooks are used by animate to expand properties
6864jQuery.each({
6865	margin: "",
6866	padding: "",
6867	border: "Width"
6868}, function( prefix, suffix ) {
6869	jQuery.cssHooks[ prefix + suffix ] = {
6870		expand: function( value ) {
6871			var i = 0,
6872				expanded = {},
6873
6874				// assumes a single number if not a string
6875				parts = typeof value === "string" ? value.split(" ") : [ value ];
6876
6877			for ( ; i < 4; i++ ) {
6878				expanded[ prefix + cssExpand[ i ] + suffix ] =
6879					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
6880			}
6881
6882			return expanded;
6883		}
6884	};
6885
6886	if ( !rmargin.test( prefix ) ) {
6887		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
6888	}
6889});
6890
6891jQuery.fn.extend({
6892	css: function( name, value ) {
6893		return access( this, function( elem, name, value ) {
6894			var styles, len,
6895				map = {},
6896				i = 0;
6897
6898			if ( jQuery.isArray( name ) ) {
6899				styles = getStyles( elem );
6900				len = name.length;
6901
6902				for ( ; i < len; i++ ) {
6903					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
6904				}
6905
6906				return map;
6907			}
6908
6909			return value !== undefined ?
6910				jQuery.style( elem, name, value ) :
6911				jQuery.css( elem, name );
6912		}, name, value, arguments.length > 1 );
6913	},
6914	show: function() {
6915		return showHide( this, true );
6916	},
6917	hide: function() {
6918		return showHide( this );
6919	},
6920	toggle: function( state ) {
6921		if ( typeof state === "boolean" ) {
6922			return state ? this.show() : this.hide();
6923		}
6924
6925		return this.each(function() {
6926			if ( isHidden( this ) ) {
6927				jQuery( this ).show();
6928			} else {
6929				jQuery( this ).hide();
6930			}
6931		});
6932	}
6933});
6934
6935
6936function Tween( elem, options, prop, end, easing ) {
6937	return new Tween.prototype.init( elem, options, prop, end, easing );
6938}
6939jQuery.Tween = Tween;
6940
6941Tween.prototype = {
6942	constructor: Tween,
6943	init: function( elem, options, prop, end, easing, unit ) {
6944		this.elem = elem;
6945		this.prop = prop;
6946		this.easing = easing || "swing";
6947		this.options = options;
6948		this.start = this.now = this.cur();
6949		this.end = end;
6950		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
6951	},
6952	cur: function() {
6953		var hooks = Tween.propHooks[ this.prop ];
6954
6955		return hooks && hooks.get ?
6956			hooks.get( this ) :
6957			Tween.propHooks._default.get( this );
6958	},
6959	run: function( percent ) {
6960		var eased,
6961			hooks = Tween.propHooks[ this.prop ];
6962
6963		if ( this.options.duration ) {
6964			this.pos = eased = jQuery.easing[ this.easing ](
6965				percent, this.options.duration * percent, 0, 1, this.options.duration
6966			);
6967		} else {
6968			this.pos = eased = percent;
6969		}
6970		this.now = ( this.end - this.start ) * eased + this.start;
6971
6972		if ( this.options.step ) {
6973			this.options.step.call( this.elem, this.now, this );
6974		}
6975
6976		if ( hooks && hooks.set ) {
6977			hooks.set( this );
6978		} else {
6979			Tween.propHooks._default.set( this );
6980		}
6981		return this;
6982	}
6983};
6984
6985Tween.prototype.init.prototype = Tween.prototype;
6986
6987Tween.propHooks = {
6988	_default: {
6989		get: function( tween ) {
6990			var result;
6991
6992			if ( tween.elem[ tween.prop ] != null &&
6993				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
6994				return tween.elem[ tween.prop ];
6995			}
6996
6997			// passing an empty string as a 3rd parameter to .css will automatically
6998			// attempt a parseFloat and fallback to a string if the parse fails
6999			// so, simple values such as "10px" are parsed to Float.
7000			// complex values such as "rotate(1rad)" are returned as is.
7001			result = jQuery.css( tween.elem, tween.prop, "" );
7002			// Empty strings, null, undefined and "auto" are converted to 0.
7003			return !result || result === "auto" ? 0 : result;
7004		},
7005		set: function( tween ) {
7006			// use step hook for back compat - use cssHook if its there - use .style if its
7007			// available and use plain properties where available
7008			if ( jQuery.fx.step[ tween.prop ] ) {
7009				jQuery.fx.step[ tween.prop ]( tween );
7010			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
7011				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
7012			} else {
7013				tween.elem[ tween.prop ] = tween.now;
7014			}
7015		}
7016	}
7017};
7018
7019// Support: IE <=9
7020// Panic based approach to setting things on disconnected nodes
7021
7022Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
7023	set: function( tween ) {
7024		if ( tween.elem.nodeType && tween.elem.parentNode ) {
7025			tween.elem[ tween.prop ] = tween.now;
7026		}
7027	}
7028};
7029
7030jQuery.easing = {
7031	linear: function( p ) {
7032		return p;
7033	},
7034	swing: function( p ) {
7035		return 0.5 - Math.cos( p * Math.PI ) / 2;
7036	}
7037};
7038
7039jQuery.fx = Tween.prototype.init;
7040
7041// Back Compat <1.8 extension point
7042jQuery.fx.step = {};
7043
7044
7045
7046
7047var
7048	fxNow, timerId,
7049	rfxtypes = /^(?:toggle|show|hide)$/,
7050	rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ),
7051	rrun = /queueHooks$/,
7052	animationPrefilters = [ defaultPrefilter ],
7053	tweeners = {
7054		"*": [ function( prop, value ) {
7055			var tween = this.createTween( prop, value ),
7056				target = tween.cur(),
7057				parts = rfxnum.exec( value ),
7058				unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
7059
7060				// Starting value computation is required for potential unit mismatches
7061				start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
7062					rfxnum.exec( jQuery.css( tween.elem, prop ) ),
7063				scale = 1,
7064				maxIterations = 20;
7065
7066			if ( start && start[ 3 ] !== unit ) {
7067				// Trust units reported by jQuery.css
7068				unit = unit || start[ 3 ];
7069
7070				// Make sure we update the tween properties later on
7071				parts = parts || [];
7072
7073				// Iteratively approximate from a nonzero starting point
7074				start = +target || 1;
7075
7076				do {
7077					// If previous iteration zeroed out, double until we get *something*
7078					// Use a string for doubling factor so we don't accidentally see scale as unchanged below
7079					scale = scale || ".5";
7080
7081					// Adjust and apply
7082					start = start / scale;
7083					jQuery.style( tween.elem, prop, start + unit );
7084
7085				// Update scale, tolerating zero or NaN from tween.cur()
7086				// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
7087				} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
7088			}
7089
7090			// Update tween properties
7091			if ( parts ) {
7092				start = tween.start = +start || +target || 0;
7093				tween.unit = unit;
7094				// If a +=/-= token was provided, we're doing a relative animation
7095				tween.end = parts[ 1 ] ?
7096					start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
7097					+parts[ 2 ];
7098			}
7099
7100			return tween;
7101		} ]
7102	};
7103
7104// Animations created synchronously will run synchronously
7105function createFxNow() {
7106	setTimeout(function() {
7107		fxNow = undefined;
7108	});
7109	return ( fxNow = jQuery.now() );
7110}
7111
7112// Generate parameters to create a standard animation
7113function genFx( type, includeWidth ) {
7114	var which,
7115		attrs = { height: type },
7116		i = 0;
7117
7118	// if we include width, step value is 1 to do all cssExpand values,
7119	// if we don't include width, step value is 2 to skip over Left and Right
7120	includeWidth = includeWidth ? 1 : 0;
7121	for ( ; i < 4 ; i += 2 - includeWidth ) {
7122		which = cssExpand[ i ];
7123		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
7124	}
7125
7126	if ( includeWidth ) {
7127		attrs.opacity = attrs.width = type;
7128	}
7129
7130	return attrs;
7131}
7132
7133function createTween( value, prop, animation ) {
7134	var tween,
7135		collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
7136		index = 0,
7137		length = collection.length;
7138	for ( ; index < length; index++ ) {
7139		if ( (tween = collection[ index ].call( animation, prop, value )) ) {
7140
7141			// we're done with this property
7142			return tween;
7143		}
7144	}
7145}
7146
7147function defaultPrefilter( elem, props, opts ) {
7148	/* jshint validthis: true */
7149	var prop, value, toggle, tween, hooks, oldfire, display, dDisplay,
7150		anim = this,
7151		orig = {},
7152		style = elem.style,
7153		hidden = elem.nodeType && isHidden( elem ),
7154		dataShow = jQuery._data( elem, "fxshow" );
7155
7156	// handle queue: false promises
7157	if ( !opts.queue ) {
7158		hooks = jQuery._queueHooks( elem, "fx" );
7159		if ( hooks.unqueued == null ) {
7160			hooks.unqueued = 0;
7161			oldfire = hooks.empty.fire;
7162			hooks.empty.fire = function() {
7163				if ( !hooks.unqueued ) {
7164					oldfire();
7165				}
7166			};
7167		}
7168		hooks.unqueued++;
7169
7170		anim.always(function() {
7171			// doing this makes sure that the complete handler will be called
7172			// before this completes
7173			anim.always(function() {
7174				hooks.unqueued--;
7175				if ( !jQuery.queue( elem, "fx" ).length ) {
7176					hooks.empty.fire();
7177				}
7178			});
7179		});
7180	}
7181
7182	// height/width overflow pass
7183	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
7184		// Make sure that nothing sneaks out
7185		// Record all 3 overflow attributes because IE does not
7186		// change the overflow attribute when overflowX and
7187		// overflowY are set to the same value
7188		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
7189
7190		// Set display property to inline-block for height/width
7191		// animations on inline elements that are having width/height animated
7192		display = jQuery.css( elem, "display" );
7193		dDisplay = defaultDisplay( elem.nodeName );
7194		if ( display === "none" ) {
7195			display = dDisplay;
7196		}
7197		if ( display === "inline" &&
7198				jQuery.css( elem, "float" ) === "none" ) {
7199
7200			// inline-level elements accept inline-block;
7201			// block-level elements need to be inline with layout
7202			if ( !support.inlineBlockNeedsLayout || dDisplay === "inline" ) {
7203				style.display = "inline-block";
7204			} else {
7205				style.zoom = 1;
7206			}
7207		}
7208	}
7209
7210	if ( opts.overflow ) {
7211		style.overflow = "hidden";
7212		if ( !support.shrinkWrapBlocks() ) {
7213			anim.always(function() {
7214				style.overflow = opts.overflow[ 0 ];
7215				style.overflowX = opts.overflow[ 1 ];
7216				style.overflowY = opts.overflow[ 2 ];
7217			});
7218		}
7219	}
7220
7221	// show/hide pass
7222	for ( prop in props ) {
7223		value = props[ prop ];
7224		if ( rfxtypes.exec( value ) ) {
7225			delete props[ prop ];
7226			toggle = toggle || value === "toggle";
7227			if ( value === ( hidden ? "hide" : "show" ) ) {
7228
7229				// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
7230				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
7231					hidden = true;
7232				} else {
7233					continue;
7234				}
7235			}
7236			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
7237		}
7238	}
7239
7240	if ( !jQuery.isEmptyObject( orig ) ) {
7241		if ( dataShow ) {
7242			if ( "hidden" in dataShow ) {
7243				hidden = dataShow.hidden;
7244			}
7245		} else {
7246			dataShow = jQuery._data( elem, "fxshow", {} );
7247		}
7248
7249		// store state if its toggle - enables .stop().toggle() to "reverse"
7250		if ( toggle ) {
7251			dataShow.hidden = !hidden;
7252		}
7253		if ( hidden ) {
7254			jQuery( elem ).show();
7255		} else {
7256			anim.done(function() {
7257				jQuery( elem ).hide();
7258			});
7259		}
7260		anim.done(function() {
7261			var prop;
7262			jQuery._removeData( elem, "fxshow" );
7263			for ( prop in orig ) {
7264				jQuery.style( elem, prop, orig[ prop ] );
7265			}
7266		});
7267		for ( prop in orig ) {
7268			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
7269
7270			if ( !( prop in dataShow ) ) {
7271				dataShow[ prop ] = tween.start;
7272				if ( hidden ) {
7273					tween.end = tween.start;
7274					tween.start = prop === "width" || prop === "height" ? 1 : 0;
7275				}
7276			}
7277		}
7278	}
7279}
7280
7281function propFilter( props, specialEasing ) {
7282	var index, name, easing, value, hooks;
7283
7284	// camelCase, specialEasing and expand cssHook pass
7285	for ( index in props ) {
7286		name = jQuery.camelCase( index );
7287		easing = specialEasing[ name ];
7288		value = props[ index ];
7289		if ( jQuery.isArray( value ) ) {
7290			easing = value[ 1 ];
7291			value = props[ index ] = value[ 0 ];
7292		}
7293
7294		if ( index !== name ) {
7295			props[ name ] = value;
7296			delete props[ index ];
7297		}
7298
7299		hooks = jQuery.cssHooks[ name ];
7300		if ( hooks && "expand" in hooks ) {
7301			value = hooks.expand( value );
7302			delete props[ name ];
7303
7304			// not quite $.extend, this wont overwrite keys already present.
7305			// also - reusing 'index' from above because we have the correct "name"
7306			for ( index in value ) {
7307				if ( !( index in props ) ) {
7308					props[ index ] = value[ index ];
7309					specialEasing[ index ] = easing;
7310				}
7311			}
7312		} else {
7313			specialEasing[ name ] = easing;
7314		}
7315	}
7316}
7317
7318function Animation( elem, properties, options ) {
7319	var result,
7320		stopped,
7321		index = 0,
7322		length = animationPrefilters.length,
7323		deferred = jQuery.Deferred().always( function() {
7324			// don't match elem in the :animated selector
7325			delete tick.elem;
7326		}),
7327		tick = function() {
7328			if ( stopped ) {
7329				return false;
7330			}
7331			var currentTime = fxNow || createFxNow(),
7332				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
7333				// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
7334				temp = remaining / animation.duration || 0,
7335				percent = 1 - temp,
7336				index = 0,
7337				length = animation.tweens.length;
7338
7339			for ( ; index < length ; index++ ) {
7340				animation.tweens[ index ].run( percent );
7341			}
7342
7343			deferred.notifyWith( elem, [ animation, percent, remaining ]);
7344
7345			if ( percent < 1 && length ) {
7346				return remaining;
7347			} else {
7348				deferred.resolveWith( elem, [ animation ] );
7349				return false;
7350			}
7351		},
7352		animation = deferred.promise({
7353			elem: elem,
7354			props: jQuery.extend( {}, properties ),
7355			opts: jQuery.extend( true, { specialEasing: {} }, options ),
7356			originalProperties: properties,
7357			originalOptions: options,
7358			startTime: fxNow || createFxNow(),
7359			duration: options.duration,
7360			tweens: [],
7361			createTween: function( prop, end ) {
7362				var tween = jQuery.Tween( elem, animation.opts, prop, end,
7363						animation.opts.specialEasing[ prop ] || animation.opts.easing );
7364				animation.tweens.push( tween );
7365				return tween;
7366			},
7367			stop: function( gotoEnd ) {
7368				var index = 0,
7369					// if we are going to the end, we want to run all the tweens
7370					// otherwise we skip this part
7371					length = gotoEnd ? animation.tweens.length : 0;
7372				if ( stopped ) {
7373					return this;
7374				}
7375				stopped = true;
7376				for ( ; index < length ; index++ ) {
7377					animation.tweens[ index ].run( 1 );
7378				}
7379
7380				// resolve when we played the last frame
7381				// otherwise, reject
7382				if ( gotoEnd ) {
7383					deferred.resolveWith( elem, [ animation, gotoEnd ] );
7384				} else {
7385					deferred.rejectWith( elem, [ animation, gotoEnd ] );
7386				}
7387				return this;
7388			}
7389		}),
7390		props = animation.props;
7391
7392	propFilter( props, animation.opts.specialEasing );
7393
7394	for ( ; index < length ; index++ ) {
7395		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
7396		if ( result ) {
7397			return result;
7398		}
7399	}
7400
7401	jQuery.map( props, createTween, animation );
7402
7403	if ( jQuery.isFunction( animation.opts.start ) ) {
7404		animation.opts.start.call( elem, animation );
7405	}
7406
7407	jQuery.fx.timer(
7408		jQuery.extend( tick, {
7409			elem: elem,
7410			anim: animation,
7411			queue: animation.opts.queue
7412		})
7413	);
7414
7415	// attach callbacks from options
7416	return animation.progress( animation.opts.progress )
7417		.done( animation.opts.done, animation.opts.complete )
7418		.fail( animation.opts.fail )
7419		.always( animation.opts.always );
7420}
7421
7422jQuery.Animation = jQuery.extend( Animation, {
7423	tweener: function( props, callback ) {
7424		if ( jQuery.isFunction( props ) ) {
7425			callback = props;
7426			props = [ "*" ];
7427		} else {
7428			props = props.split(" ");
7429		}
7430
7431		var prop,
7432			index = 0,
7433			length = props.length;
7434
7435		for ( ; index < length ; index++ ) {
7436			prop = props[ index ];
7437			tweeners[ prop ] = tweeners[ prop ] || [];
7438			tweeners[ prop ].unshift( callback );
7439		}
7440	},
7441
7442	prefilter: function( callback, prepend ) {
7443		if ( prepend ) {
7444			animationPrefilters.unshift( callback );
7445		} else {
7446			animationPrefilters.push( callback );
7447		}
7448	}
7449});
7450
7451jQuery.speed = function( speed, easing, fn ) {
7452	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
7453		complete: fn || !fn && easing ||
7454			jQuery.isFunction( speed ) && speed,
7455		duration: speed,
7456		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
7457	};
7458
7459	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
7460		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
7461
7462	// normalize opt.queue - true/undefined/null -> "fx"
7463	if ( opt.queue == null || opt.queue === true ) {
7464		opt.queue = "fx";
7465	}
7466
7467	// Queueing
7468	opt.old = opt.complete;
7469
7470	opt.complete = function() {
7471		if ( jQuery.isFunction( opt.old ) ) {
7472			opt.old.call( this );
7473		}
7474
7475		if ( opt.queue ) {
7476			jQuery.dequeue( this, opt.queue );
7477		}
7478	};
7479
7480	return opt;
7481};
7482
7483jQuery.fn.extend({
7484	fadeTo: function( speed, to, easing, callback ) {
7485
7486		// show any hidden elements after setting opacity to 0
7487		return this.filter( isHidden ).css( "opacity", 0 ).show()
7488
7489			// animate to the value specified
7490			.end().animate({ opacity: to }, speed, easing, callback );
7491	},
7492	animate: function( prop, speed, easing, callback ) {
7493		var empty = jQuery.isEmptyObject( prop ),
7494			optall = jQuery.speed( speed, easing, callback ),
7495			doAnimation = function() {
7496				// Operate on a copy of prop so per-property easing won't be lost
7497				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
7498
7499				// Empty animations, or finishing resolves immediately
7500				if ( empty || jQuery._data( this, "finish" ) ) {
7501					anim.stop( true );
7502				}
7503			};
7504			doAnimation.finish = doAnimation;
7505
7506		return empty || optall.queue === false ?
7507			this.each( doAnimation ) :
7508			this.queue( optall.queue, doAnimation );
7509	},
7510	stop: function( type, clearQueue, gotoEnd ) {
7511		var stopQueue = function( hooks ) {
7512			var stop = hooks.stop;
7513			delete hooks.stop;
7514			stop( gotoEnd );
7515		};
7516
7517		if ( typeof type !== "string" ) {
7518			gotoEnd = clearQueue;
7519			clearQueue = type;
7520			type = undefined;
7521		}
7522		if ( clearQueue && type !== false ) {
7523			this.queue( type || "fx", [] );
7524		}
7525
7526		return this.each(function() {
7527			var dequeue = true,
7528				index = type != null && type + "queueHooks",
7529				timers = jQuery.timers,
7530				data = jQuery._data( this );
7531
7532			if ( index ) {
7533				if ( data[ index ] && data[ index ].stop ) {
7534					stopQueue( data[ index ] );
7535				}
7536			} else {
7537				for ( index in data ) {
7538					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
7539						stopQueue( data[ index ] );
7540					}
7541				}
7542			}
7543
7544			for ( index = timers.length; index--; ) {
7545				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
7546					timers[ index ].anim.stop( gotoEnd );
7547					dequeue = false;
7548					timers.splice( index, 1 );
7549				}
7550			}
7551
7552			// start the next in the queue if the last step wasn't forced
7553			// timers currently will call their complete callbacks, which will dequeue
7554			// but only if they were gotoEnd
7555			if ( dequeue || !gotoEnd ) {
7556				jQuery.dequeue( this, type );
7557			}
7558		});
7559	},
7560	finish: function( type ) {
7561		if ( type !== false ) {
7562			type = type || "fx";
7563		}
7564		return this.each(function() {
7565			var index,
7566				data = jQuery._data( this ),
7567				queue = data[ type + "queue" ],
7568				hooks = data[ type + "queueHooks" ],
7569				timers = jQuery.timers,
7570				length = queue ? queue.length : 0;
7571
7572			// enable finishing flag on private data
7573			data.finish = true;
7574
7575			// empty the queue first
7576			jQuery.queue( this, type, [] );
7577
7578			if ( hooks && hooks.stop ) {
7579				hooks.stop.call( this, true );
7580			}
7581
7582			// look for any active animations, and finish them
7583			for ( index = timers.length; index--; ) {
7584				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
7585					timers[ index ].anim.stop( true );
7586					timers.splice( index, 1 );
7587				}
7588			}
7589
7590			// look for any animations in the old queue and finish them
7591			for ( index = 0; index < length; index++ ) {
7592				if ( queue[ index ] && queue[ index ].finish ) {
7593					queue[ index ].finish.call( this );
7594				}
7595			}
7596
7597			// turn off finishing flag
7598			delete data.finish;
7599		});
7600	}
7601});
7602
7603jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
7604	var cssFn = jQuery.fn[ name ];
7605	jQuery.fn[ name ] = function( speed, easing, callback ) {
7606		return speed == null || typeof speed === "boolean" ?
7607			cssFn.apply( this, arguments ) :
7608			this.animate( genFx( name, true ), speed, easing, callback );
7609	};
7610});
7611
7612// Generate shortcuts for custom animations
7613jQuery.each({
7614	slideDown: genFx("show"),
7615	slideUp: genFx("hide"),
7616	slideToggle: genFx("toggle"),
7617	fadeIn: { opacity: "show" },
7618	fadeOut: { opacity: "hide" },
7619	fadeToggle: { opacity: "toggle" }
7620}, function( name, props ) {
7621	jQuery.fn[ name ] = function( speed, easing, callback ) {
7622		return this.animate( props, speed, easing, callback );
7623	};
7624});
7625
7626jQuery.timers = [];
7627jQuery.fx.tick = function() {
7628	var timer,
7629		timers = jQuery.timers,
7630		i = 0;
7631
7632	fxNow = jQuery.now();
7633
7634	for ( ; i < timers.length; i++ ) {
7635		timer = timers[ i ];
7636		// Checks the timer has not already been removed
7637		if ( !timer() && timers[ i ] === timer ) {
7638			timers.splice( i--, 1 );
7639		}
7640	}
7641
7642	if ( !timers.length ) {
7643		jQuery.fx.stop();
7644	}
7645	fxNow = undefined;
7646};
7647
7648jQuery.fx.timer = function( timer ) {
7649	jQuery.timers.push( timer );
7650	if ( timer() ) {
7651		jQuery.fx.start();
7652	} else {
7653		jQuery.timers.pop();
7654	}
7655};
7656
7657jQuery.fx.interval = 13;
7658
7659jQuery.fx.start = function() {
7660	if ( !timerId ) {
7661		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
7662	}
7663};
7664
7665jQuery.fx.stop = function() {
7666	clearInterval( timerId );
7667	timerId = null;
7668};
7669
7670jQuery.fx.speeds = {
7671	slow: 600,
7672	fast: 200,
7673	// Default speed
7674	_default: 400
7675};
7676
7677
7678// Based off of the plugin by Clint Helfers, with permission.
7679// http://blindsignals.com/index.php/2009/07/jquery-delay/
7680jQuery.fn.delay = function( time, type ) {
7681	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
7682	type = type || "fx";
7683
7684	return this.queue( type, function( next, hooks ) {
7685		var timeout = setTimeout( next, time );
7686		hooks.stop = function() {
7687			clearTimeout( timeout );
7688		};
7689	});
7690};
7691
7692
7693(function() {
7694	var a, input, select, opt,
7695		div = document.createElement("div" );
7696
7697	// Setup
7698	div.setAttribute( "className", "t" );
7699	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
7700	a = div.getElementsByTagName("a")[ 0 ];
7701
7702	// First batch of tests.
7703	select = document.createElement("select");
7704	opt = select.appendChild( document.createElement("option") );
7705	input = div.getElementsByTagName("input")[ 0 ];
7706
7707	a.style.cssText = "top:1px";
7708
7709	// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
7710	support.getSetAttribute = div.className !== "t";
7711
7712	// Get the style information from getAttribute
7713	// (IE uses .cssText instead)
7714	support.style = /top/.test( a.getAttribute("style") );
7715
7716	// Make sure that URLs aren't manipulated
7717	// (IE normalizes it by default)
7718	support.hrefNormalized = a.getAttribute("href") === "/a";
7719
7720	// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
7721	support.checkOn = !!input.value;
7722
7723	// Make sure that a selected-by-default option has a working selected property.
7724	// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
7725	support.optSelected = opt.selected;
7726
7727	// Tests for enctype support on a form (#6743)
7728	support.enctype = !!document.createElement("form").enctype;
7729
7730	// Make sure that the options inside disabled selects aren't marked as disabled
7731	// (WebKit marks them as disabled)
7732	select.disabled = true;
7733	support.optDisabled = !opt.disabled;
7734
7735	// Support: IE8 only
7736	// Check if we can trust getAttribute("value")
7737	input = document.createElement( "input" );
7738	input.setAttribute( "value", "" );
7739	support.input = input.getAttribute( "value" ) === "";
7740
7741	// Check if an input maintains its value after becoming a radio
7742	input.value = "t";
7743	input.setAttribute( "type", "radio" );
7744	support.radioValue = input.value === "t";
7745
7746	// Null elements to avoid leaks in IE.
7747	a = input = select = opt = div = null;
7748})();
7749
7750
7751var rreturn = /\r/g;
7752
7753jQuery.fn.extend({
7754	val: function( value ) {
7755		var hooks, ret, isFunction,
7756			elem = this[0];
7757
7758		if ( !arguments.length ) {
7759			if ( elem ) {
7760				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
7761
7762				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
7763					return ret;
7764				}
7765
7766				ret = elem.value;
7767
7768				return typeof ret === "string" ?
7769					// handle most common string cases
7770					ret.replace(rreturn, "") :
7771					// handle cases where value is null/undef or number
7772					ret == null ? "" : ret;
7773			}
7774
7775			return;
7776		}
7777
7778		isFunction = jQuery.isFunction( value );
7779
7780		return this.each(function( i ) {
7781			var val;
7782
7783			if ( this.nodeType !== 1 ) {
7784				return;
7785			}
7786
7787			if ( isFunction ) {
7788				val = value.call( this, i, jQuery( this ).val() );
7789			} else {
7790				val = value;
7791			}
7792
7793			// Treat null/undefined as ""; convert numbers to string
7794			if ( val == null ) {
7795				val = "";
7796			} else if ( typeof val === "number" ) {
7797				val += "";
7798			} else if ( jQuery.isArray( val ) ) {
7799				val = jQuery.map( val, function( value ) {
7800					return value == null ? "" : value + "";
7801				});
7802			}
7803
7804			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
7805
7806			// If set returns undefined, fall back to normal setting
7807			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
7808				this.value = val;
7809			}
7810		});
7811	}
7812});
7813
7814jQuery.extend({
7815	valHooks: {
7816		option: {
7817			get: function( elem ) {
7818				var val = jQuery.find.attr( elem, "value" );
7819				return val != null ?
7820					val :
7821					jQuery.text( elem );
7822			}
7823		},
7824		select: {
7825			get: function( elem ) {
7826				var value, option,
7827					options = elem.options,
7828					index = elem.selectedIndex,
7829					one = elem.type === "select-one" || index < 0,
7830					values = one ? null : [],
7831					max = one ? index + 1 : options.length,
7832					i = index < 0 ?
7833						max :
7834						one ? index : 0;
7835
7836				// Loop through all the selected options
7837				for ( ; i < max; i++ ) {
7838					option = options[ i ];
7839
7840					// oldIE doesn't update selected after form reset (#2551)
7841					if ( ( option.selected || i === index ) &&
7842							// Don't return options that are disabled or in a disabled optgroup
7843							( support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
7844							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
7845
7846						// Get the specific value for the option
7847						value = jQuery( option ).val();
7848
7849						// We don't need an array for one selects
7850						if ( one ) {
7851							return value;
7852						}
7853
7854						// Multi-Selects return an array
7855						values.push( value );
7856					}
7857				}
7858
7859				return values;
7860			},
7861
7862			set: function( elem, value ) {
7863				var optionSet, option,
7864					options = elem.options,
7865					values = jQuery.makeArray( value ),
7866					i = options.length;
7867
7868				while ( i-- ) {
7869					option = options[ i ];
7870
7871					if ( jQuery.inArray( jQuery.valHooks.option.get( option ), values ) >= 0 ) {
7872
7873						// Support: IE6
7874						// When new option element is added to select box we need to
7875						// force reflow of newly added node in order to workaround delay
7876						// of initialization properties
7877						try {
7878							option.selected = optionSet = true;
7879
7880						} catch ( _ ) {
7881
7882							// Will be executed only in IE6
7883							option.scrollHeight;
7884						}
7885
7886					} else {
7887						option.selected = false;
7888					}
7889				}
7890
7891				// Force browsers to behave consistently when non-matching value is set
7892				if ( !optionSet ) {
7893					elem.selectedIndex = -1;
7894				}
7895
7896				return options;
7897			}
7898		}
7899	}
7900});
7901
7902// Radios and checkboxes getter/setter
7903jQuery.each([ "radio", "checkbox" ], function() {
7904	jQuery.valHooks[ this ] = {
7905		set: function( elem, value ) {
7906			if ( jQuery.isArray( value ) ) {
7907				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
7908			}
7909		}
7910	};
7911	if ( !support.checkOn ) {
7912		jQuery.valHooks[ this ].get = function( elem ) {
7913			// Support: Webkit
7914			// "" is returned instead of "on" if a value isn't specified
7915			return elem.getAttribute("value") === null ? "on" : elem.value;
7916		};
7917	}
7918});
7919
7920
7921
7922
7923var nodeHook, boolHook,
7924	attrHandle = jQuery.expr.attrHandle,
7925	ruseDefault = /^(?:checked|selected)$/i,
7926	getSetAttribute = support.getSetAttribute,
7927	getSetInput = support.input;
7928
7929jQuery.fn.extend({
7930	attr: function( name, value ) {
7931		return access( this, jQuery.attr, name, value, arguments.length > 1 );
7932	},
7933
7934	removeAttr: function( name ) {
7935		return this.each(function() {
7936			jQuery.removeAttr( this, name );
7937		});
7938	}
7939});
7940
7941jQuery.extend({
7942	attr: function( elem, name, value ) {
7943		var hooks, ret,
7944			nType = elem.nodeType;
7945
7946		// don't get/set attributes on text, comment and attribute nodes
7947		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
7948			return;
7949		}
7950
7951		// Fallback to prop when attributes are not supported
7952		if ( typeof elem.getAttribute === strundefined ) {
7953			return jQuery.prop( elem, name, value );
7954		}
7955
7956		// All attributes are lowercase
7957		// Grab necessary hook if one is defined
7958		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
7959			name = name.toLowerCase();
7960			hooks = jQuery.attrHooks[ name ] ||
7961				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
7962		}
7963
7964		if ( value !== undefined ) {
7965
7966			if ( value === null ) {
7967				jQuery.removeAttr( elem, name );
7968
7969			} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
7970				return ret;
7971
7972			} else {
7973				elem.setAttribute( name, value + "" );
7974				return value;
7975			}
7976
7977		} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
7978			return ret;
7979
7980		} else {
7981			ret = jQuery.find.attr( elem, name );
7982
7983			// Non-existent attributes return null, we normalize to undefined
7984			return ret == null ?
7985				undefined :
7986				ret;
7987		}
7988	},
7989
7990	removeAttr: function( elem, value ) {
7991		var name, propName,
7992			i = 0,
7993			attrNames = value && value.match( rnotwhite );
7994
7995		if ( attrNames && elem.nodeType === 1 ) {
7996			while ( (name = attrNames[i++]) ) {
7997				propName = jQuery.propFix[ name ] || name;
7998
7999				// Boolean attributes get special treatment (#10870)
8000				if ( jQuery.expr.match.bool.test( name ) ) {
8001					// Set corresponding property to false
8002					if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
8003						elem[ propName ] = false;
8004					// Support: IE<9
8005					// Also clear defaultChecked/defaultSelected (if appropriate)
8006					} else {
8007						elem[ jQuery.camelCase( "default-" + name ) ] =
8008							elem[ propName ] = false;
8009					}
8010
8011				// See #9699 for explanation of this approach (setting first, then removal)
8012				} else {
8013					jQuery.attr( elem, name, "" );
8014				}
8015
8016				elem.removeAttribute( getSetAttribute ? name : propName );
8017			}
8018		}
8019	},
8020
8021	attrHooks: {
8022		type: {
8023			set: function( elem, value ) {
8024				if ( !support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
8025					// Setting the type on a radio button after the value resets the value in IE6-9
8026					// Reset value to default in case type is set after value during creation
8027					var val = elem.value;
8028					elem.setAttribute( "type", value );
8029					if ( val ) {
8030						elem.value = val;
8031					}
8032					return value;
8033				}
8034			}
8035		}
8036	}
8037});
8038
8039// Hook for boolean attributes
8040boolHook = {
8041	set: function( elem, value, name ) {
8042		if ( value === false ) {
8043			// Remove boolean attributes when set to false
8044			jQuery.removeAttr( elem, name );
8045		} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
8046			// IE<8 needs the *property* name
8047			elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
8048
8049		// Use defaultChecked and defaultSelected for oldIE
8050		} else {
8051			elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
8052		}
8053
8054		return name;
8055	}
8056};
8057
8058// Retrieve booleans specially
8059jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
8060
8061	var getter = attrHandle[ name ] || jQuery.find.attr;
8062
8063	attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?
8064		function( elem, name, isXML ) {
8065			var ret, handle;
8066			if ( !isXML ) {
8067				// Avoid an infinite loop by temporarily removing this function from the getter
8068				handle = attrHandle[ name ];
8069				attrHandle[ name ] = ret;
8070				ret = getter( elem, name, isXML ) != null ?
8071					name.toLowerCase() :
8072					null;
8073				attrHandle[ name ] = handle;
8074			}
8075			return ret;
8076		} :
8077		function( elem, name, isXML ) {
8078			if ( !isXML ) {
8079				return elem[ jQuery.camelCase( "default-" + name ) ] ?
8080					name.toLowerCase() :
8081					null;
8082			}
8083		};
8084});
8085
8086// fix oldIE attroperties
8087if ( !getSetInput || !getSetAttribute ) {
8088	jQuery.attrHooks.value = {
8089		set: function( elem, value, name ) {
8090			if ( jQuery.nodeName( elem, "input" ) ) {
8091				// Does not return so that setAttribute is also used
8092				elem.defaultValue = value;
8093			} else {
8094				// Use nodeHook if defined (#1954); otherwise setAttribute is fine
8095				return nodeHook && nodeHook.set( elem, value, name );
8096			}
8097		}
8098	};
8099}
8100
8101// IE6/7 do not support getting/setting some attributes with get/setAttribute
8102if ( !getSetAttribute ) {
8103
8104	// Use this for any attribute in IE6/7
8105	// This fixes almost every IE6/7 issue
8106	nodeHook = {
8107		set: function( elem, value, name ) {
8108			// Set the existing or create a new attribute node
8109			var ret = elem.getAttributeNode( name );
8110			if ( !ret ) {
8111				elem.setAttributeNode(
8112					(ret = elem.ownerDocument.createAttribute( name ))
8113				);
8114			}
8115
8116			ret.value = value += "";
8117
8118			// Break association with cloned elements by also using setAttribute (#9646)
8119			if ( name === "value" || value === elem.getAttribute( name ) ) {
8120				return value;
8121			}
8122		}
8123	};
8124
8125	// Some attributes are constructed with empty-string values when not defined
8126	attrHandle.id = attrHandle.name = attrHandle.coords =
8127		function( elem, name, isXML ) {
8128			var ret;
8129			if ( !isXML ) {
8130				return (ret = elem.getAttributeNode( name )) && ret.value !== "" ?
8131					ret.value :
8132					null;
8133			}
8134		};
8135
8136	// Fixing value retrieval on a button requires this module
8137	jQuery.valHooks.button = {
8138		get: function( elem, name ) {
8139			var ret = elem.getAttributeNode( name );
8140			if ( ret && ret.specified ) {
8141				return ret.value;
8142			}
8143		},
8144		set: nodeHook.set
8145	};
8146
8147	// Set contenteditable to false on removals(#10429)
8148	// Setting to empty string throws an error as an invalid value
8149	jQuery.attrHooks.contenteditable = {
8150		set: function( elem, value, name ) {
8151			nodeHook.set( elem, value === "" ? false : value, name );
8152		}
8153	};
8154
8155	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
8156	// This is for removals
8157	jQuery.each([ "width", "height" ], function( i, name ) {
8158		jQuery.attrHooks[ name ] = {
8159			set: function( elem, value ) {
8160				if ( value === "" ) {
8161					elem.setAttribute( name, "auto" );
8162					return value;
8163				}
8164			}
8165		};
8166	});
8167}
8168
8169if ( !support.style ) {
8170	jQuery.attrHooks.style = {
8171		get: function( elem ) {
8172			// Return undefined in the case of empty string
8173			// Note: IE uppercases css property names, but if we were to .toLowerCase()
8174			// .cssText, that would destroy case senstitivity in URL's, like in "background"
8175			return elem.style.cssText || undefined;
8176		},
8177		set: function( elem, value ) {
8178			return ( elem.style.cssText = value + "" );
8179		}
8180	};
8181}
8182
8183
8184
8185
8186var rfocusable = /^(?:input|select|textarea|button|object)$/i,
8187	rclickable = /^(?:a|area)$/i;
8188
8189jQuery.fn.extend({
8190	prop: function( name, value ) {
8191		return access( this, jQuery.prop, name, value, arguments.length > 1 );
8192	},
8193
8194	removeProp: function( name ) {
8195		name = jQuery.propFix[ name ] || name;
8196		return this.each(function() {
8197			// try/catch handles cases where IE balks (such as removing a property on window)
8198			try {
8199				this[ name ] = undefined;
8200				delete this[ name ];
8201			} catch( e ) {}
8202		});
8203	}
8204});
8205
8206jQuery.extend({
8207	propFix: {
8208		"for": "htmlFor",
8209		"class": "className"
8210	},
8211
8212	prop: function( elem, name, value ) {
8213		var ret, hooks, notxml,
8214			nType = elem.nodeType;
8215
8216		// don't get/set properties on text, comment and attribute nodes
8217		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
8218			return;
8219		}
8220
8221		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
8222
8223		if ( notxml ) {
8224			// Fix name and attach hooks
8225			name = jQuery.propFix[ name ] || name;
8226			hooks = jQuery.propHooks[ name ];
8227		}
8228
8229		if ( value !== undefined ) {
8230			return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
8231				ret :
8232				( elem[ name ] = value );
8233
8234		} else {
8235			return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
8236				ret :
8237				elem[ name ];
8238		}
8239	},
8240
8241	propHooks: {
8242		tabIndex: {
8243			get: function( elem ) {
8244				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
8245				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
8246				// Use proper attribute retrieval(#12072)
8247				var tabindex = jQuery.find.attr( elem, "tabindex" );
8248
8249				return tabindex ?
8250					parseInt( tabindex, 10 ) :
8251					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
8252						0 :
8253						-1;
8254			}
8255		}
8256	}
8257});
8258
8259// Some attributes require a special call on IE
8260// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
8261if ( !support.hrefNormalized ) {
8262	// href/src property should get the full normalized URL (#10299/#12915)
8263	jQuery.each([ "href", "src" ], function( i, name ) {
8264		jQuery.propHooks[ name ] = {
8265			get: function( elem ) {
8266				return elem.getAttribute( name, 4 );
8267			}
8268		};
8269	});
8270}
8271
8272// Support: Safari, IE9+
8273// mis-reports the default selected property of an option
8274// Accessing the parent's selectedIndex property fixes it
8275if ( !support.optSelected ) {
8276	jQuery.propHooks.selected = {
8277		get: function( elem ) {
8278			var parent = elem.parentNode;
8279
8280			if ( parent ) {
8281				parent.selectedIndex;
8282
8283				// Make sure that it also works with optgroups, see #5701
8284				if ( parent.parentNode ) {
8285					parent.parentNode.selectedIndex;
8286				}
8287			}
8288			return null;
8289		}
8290	};
8291}
8292
8293jQuery.each([
8294	"tabIndex",
8295	"readOnly",
8296	"maxLength",
8297	"cellSpacing",
8298	"cellPadding",
8299	"rowSpan",
8300	"colSpan",
8301	"useMap",
8302	"frameBorder",
8303	"contentEditable"
8304], function() {
8305	jQuery.propFix[ this.toLowerCase() ] = this;
8306});
8307
8308// IE6/7 call enctype encoding
8309if ( !support.enctype ) {
8310	jQuery.propFix.enctype = "encoding";
8311}
8312
8313
8314
8315
8316var rclass = /[\t\r\n\f]/g;
8317
8318jQuery.fn.extend({
8319	addClass: function( value ) {
8320		var classes, elem, cur, clazz, j, finalValue,
8321			i = 0,
8322			len = this.length,
8323			proceed = typeof value === "string" && value;
8324
8325		if ( jQuery.isFunction( value ) ) {
8326			return this.each(function( j ) {
8327				jQuery( this ).addClass( value.call( this, j, this.className ) );
8328			});
8329		}
8330
8331		if ( proceed ) {
8332			// The disjunction here is for better compressibility (see removeClass)
8333			classes = ( value || "" ).match( rnotwhite ) || [];
8334
8335			for ( ; i < len; i++ ) {
8336				elem = this[ i ];
8337				cur = elem.nodeType === 1 && ( elem.className ?
8338					( " " + elem.className + " " ).replace( rclass, " " ) :
8339					" "
8340				);
8341
8342				if ( cur ) {
8343					j = 0;
8344					while ( (clazz = classes[j++]) ) {
8345						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
8346							cur += clazz + " ";
8347						}
8348					}
8349
8350					// only assign if different to avoid unneeded rendering.
8351					finalValue = jQuery.trim( cur );
8352					if ( elem.className !== finalValue ) {
8353						elem.className = finalValue;
8354					}
8355				}
8356			}
8357		}
8358
8359		return this;
8360	},
8361
8362	removeClass: function( value ) {
8363		var classes, elem, cur, clazz, j, finalValue,
8364			i = 0,
8365			len = this.length,
8366			proceed = arguments.length === 0 || typeof value === "string" && value;
8367
8368		if ( jQuery.isFunction( value ) ) {
8369			return this.each(function( j ) {
8370				jQuery( this ).removeClass( value.call( this, j, this.className ) );
8371			});
8372		}
8373		if ( proceed ) {
8374			classes = ( value || "" ).match( rnotwhite ) || [];
8375
8376			for ( ; i < len; i++ ) {
8377				elem = this[ i ];
8378				// This expression is here for better compressibility (see addClass)
8379				cur = elem.nodeType === 1 && ( elem.className ?
8380					( " " + elem.className + " " ).replace( rclass, " " ) :
8381					""
8382				);
8383
8384				if ( cur ) {
8385					j = 0;
8386					while ( (clazz = classes[j++]) ) {
8387						// Remove *all* instances
8388						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
8389							cur = cur.replace( " " + clazz + " ", " " );
8390						}
8391					}
8392
8393					// only assign if different to avoid unneeded rendering.
8394					finalValue = value ? jQuery.trim( cur ) : "";
8395					if ( elem.className !== finalValue ) {
8396						elem.className = finalValue;
8397					}
8398				}
8399			}
8400		}
8401
8402		return this;
8403	},
8404
8405	toggleClass: function( value, stateVal ) {
8406		var type = typeof value;
8407
8408		if ( typeof stateVal === "boolean" && type === "string" ) {
8409			return stateVal ? this.addClass( value ) : this.removeClass( value );
8410		}
8411
8412		if ( jQuery.isFunction( value ) ) {
8413			return this.each(function( i ) {
8414				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
8415			});
8416		}
8417
8418		return this.each(function() {
8419			if ( type === "string" ) {
8420				// toggle individual class names
8421				var className,
8422					i = 0,
8423					self = jQuery( this ),
8424					classNames = value.match( rnotwhite ) || [];
8425
8426				while ( (className = classNames[ i++ ]) ) {
8427					// check each className given, space separated list
8428					if ( self.hasClass( className ) ) {
8429						self.removeClass( className );
8430					} else {
8431						self.addClass( className );
8432					}
8433				}
8434
8435			// Toggle whole class name
8436			} else if ( type === strundefined || type === "boolean" ) {
8437				if ( this.className ) {
8438					// store className if set
8439					jQuery._data( this, "__className__", this.className );
8440				}
8441
8442				// If the element has a class name or if we're passed "false",
8443				// then remove the whole classname (if there was one, the above saved it).
8444				// Otherwise bring back whatever was previously saved (if anything),
8445				// falling back to the empty string if nothing was stored.
8446				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
8447			}
8448		});
8449	},
8450
8451	hasClass: function( selector ) {
8452		var className = " " + selector + " ",
8453			i = 0,
8454			l = this.length;
8455		for ( ; i < l; i++ ) {
8456			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
8457				return true;
8458			}
8459		}
8460
8461		return false;
8462	}
8463});
8464
8465
8466
8467
8468// Return jQuery for attributes-only inclusion
8469
8470
8471jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
8472	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
8473	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
8474
8475	// Handle event binding
8476	jQuery.fn[ name ] = function( data, fn ) {
8477		return arguments.length > 0 ?
8478			this.on( name, null, data, fn ) :
8479			this.trigger( name );
8480	};
8481});
8482
8483jQuery.fn.extend({
8484	hover: function( fnOver, fnOut ) {
8485		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
8486	},
8487
8488	bind: function( types, data, fn ) {
8489		return this.on( types, null, data, fn );
8490	},
8491	unbind: function( types, fn ) {
8492		return this.off( types, null, fn );
8493	},
8494
8495	delegate: function( selector, types, data, fn ) {
8496		return this.on( types, selector, data, fn );
8497	},
8498	undelegate: function( selector, types, fn ) {
8499		// ( namespace ) or ( selector, types [, fn] )
8500		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
8501	}
8502});
8503
8504
8505var nonce = jQuery.now();
8506
8507var rquery = (/\?/);
8508
8509
8510
8511var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;
8512
8513jQuery.parseJSON = function( data ) {
8514	// Attempt to parse using the native JSON parser first
8515	if ( window.JSON && window.JSON.parse ) {
8516		// Support: Android 2.3
8517		// Workaround failure to string-cast null input
8518		return window.JSON.parse( data + "" );
8519	}
8520
8521	var requireNonComma,
8522		depth = null,
8523		str = jQuery.trim( data + "" );
8524
8525	// Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
8526	// after removing valid tokens
8527	return str && !jQuery.trim( str.replace( rvalidtokens, function( token, comma, open, close ) {
8528
8529		// Force termination if we see a misplaced comma
8530		if ( requireNonComma && comma ) {
8531			depth = 0;
8532		}
8533
8534		// Perform no more replacements after returning to outermost depth
8535		if ( depth === 0 ) {
8536			return token;
8537		}
8538
8539		// Commas must not follow "[", "{", or ","
8540		requireNonComma = open || comma;
8541
8542		// Determine new depth
8543		// array/object open ("[" or "{"): depth += true - false (increment)
8544		// array/object close ("]" or "}"): depth += false - true (decrement)
8545		// other cases ("," or primitive): depth += true - true (numeric cast)
8546		depth += !close - !open;
8547
8548		// Remove this token
8549		return "";
8550	}) ) ?
8551		( Function( "return " + str ) )() :
8552		jQuery.error( "Invalid JSON: " + data );
8553};
8554
8555
8556// Cross-browser xml parsing
8557jQuery.parseXML = function( data ) {
8558	var xml, tmp;
8559	if ( !data || typeof data !== "string" ) {
8560		return null;
8561	}
8562	try {
8563		if ( window.DOMParser ) { // Standard
8564			tmp = new DOMParser();
8565			xml = tmp.parseFromString( data, "text/xml" );
8566		} else { // IE
8567			xml = new ActiveXObject( "Microsoft.XMLDOM" );
8568			xml.async = "false";
8569			xml.loadXML( data );
8570		}
8571	} catch( e ) {
8572		xml = undefined;
8573	}
8574	if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
8575		jQuery.error( "Invalid XML: " + data );
8576	}
8577	return xml;
8578};
8579
8580
8581var
8582	// Document location
8583	ajaxLocParts,
8584	ajaxLocation,
8585
8586	rhash = /#.*$/,
8587	rts = /([?&])_=[^&]*/,
8588	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
8589	// #7653, #8125, #8152: local protocol detection
8590	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
8591	rnoContent = /^(?:GET|HEAD)$/,
8592	rprotocol = /^\/\//,
8593	rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,
8594
8595	/* Prefilters
8596	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
8597	 * 2) These are called:
8598	 *    - BEFORE asking for a transport
8599	 *    - AFTER param serialization (s.data is a string if s.processData is true)
8600	 * 3) key is the dataType
8601	 * 4) the catchall symbol "*" can be used
8602	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
8603	 */
8604	prefilters = {},
8605
8606	/* Transports bindings
8607	 * 1) key is the dataType
8608	 * 2) the catchall symbol "*" can be used
8609	 * 3) selection will start with transport dataType and THEN go to "*" if needed
8610	 */
8611	transports = {},
8612
8613	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
8614	allTypes = "*/".concat("*");
8615
8616// #8138, IE may throw an exception when accessing
8617// a field from window.location if document.domain has been set
8618try {
8619	ajaxLocation = location.href;
8620} catch( e ) {
8621	// Use the href attribute of an A element
8622	// since IE will modify it given document.location
8623	ajaxLocation = document.createElement( "a" );
8624	ajaxLocation.href = "";
8625	ajaxLocation = ajaxLocation.href;
8626}
8627
8628// Segment location into parts
8629ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
8630
8631// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
8632function addToPrefiltersOrTransports( structure ) {
8633
8634	// dataTypeExpression is optional and defaults to "*"
8635	return function( dataTypeExpression, func ) {
8636
8637		if ( typeof dataTypeExpression !== "string" ) {
8638			func = dataTypeExpression;
8639			dataTypeExpression = "*";
8640		}
8641
8642		var dataType,
8643			i = 0,
8644			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
8645
8646		if ( jQuery.isFunction( func ) ) {
8647			// For each dataType in the dataTypeExpression
vendor: 4,101 bytes, lines 8648-8797
8648			while ( (dataType = dataTypes[i++]) ) {
8649				// Prepend if requested
8650				if ( dataType.charAt( 0 ) === "+" ) {
8651					dataType = dataType.slice( 1 ) || "*";
8652					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );
8653
8654				// Otherwise append
8655				} else {
8656					(structure[ dataType ] = structure[ dataType ] || []).push( func );
8657				}
8658			}
8659		}
8660	};
8661}
8662
8663// Base inspection function for prefilters and transports
8664function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
8665
8666	var inspected = {},
8667		seekingTransport = ( structure === transports );
8668
8669	function inspect( dataType ) {
8670		var selected;
8671		inspected[ dataType ] = true;
8672		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
8673			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
8674			if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
8675				options.dataTypes.unshift( dataTypeOrTransport );
8676				inspect( dataTypeOrTransport );
8677				return false;
8678			} else if ( seekingTransport ) {
8679				return !( selected = dataTypeOrTransport );
8680			}
8681		});
8682		return selected;
8683	}
8684
8685	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
8686}
8687
8688// A special extend for ajax options
8689// that takes "flat" options (not to be deep extended)
8690// Fixes #9887
8691function ajaxExtend( target, src ) {
8692	var deep, key,
8693		flatOptions = jQuery.ajaxSettings.flatOptions || {};
8694
8695	for ( key in src ) {
8696		if ( src[ key ] !== undefined ) {
8697			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
8698		}
8699	}
8700	if ( deep ) {
8701		jQuery.extend( true, target, deep );
8702	}
8703
8704	return target;
8705}
8706
8707/* Handles responses to an ajax request:
8708 * - finds the right dataType (mediates between content-type and expected dataType)
8709 * - returns the corresponding response
8710 */
8711function ajaxHandleResponses( s, jqXHR, responses ) {
8712	var firstDataType, ct, finalDataType, type,
8713		contents = s.contents,
8714		dataTypes = s.dataTypes;
8715
8716	// Remove auto dataType and get content-type in the process
8717	while ( dataTypes[ 0 ] === "*" ) {
8718		dataTypes.shift();
8719		if ( ct === undefined ) {
8720			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
8721		}
8722	}
8723
8724	// Check if we're dealing with a known content-type
8725	if ( ct ) {
8726		for ( type in contents ) {
8727			if ( contents[ type ] && contents[ type ].test( ct ) ) {
8728				dataTypes.unshift( type );
8729				break;
8730			}
8731		}
8732	}
8733
8734	// Check to see if we have a response for the expected dataType
8735	if ( dataTypes[ 0 ] in responses ) {
8736		finalDataType = dataTypes[ 0 ];
8737	} else {
8738		// Try convertible dataTypes
8739		for ( type in responses ) {
8740			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
8741				finalDataType = type;
8742				break;
8743			}
8744			if ( !firstDataType ) {
8745				firstDataType = type;
8746			}
8747		}
8748		// Or just use first one
8749		finalDataType = finalDataType || firstDataType;
8750	}
8751
8752	// If we found a dataType
8753	// We add the dataType to the list if needed
8754	// and return the corresponding response
8755	if ( finalDataType ) {
8756		if ( finalDataType !== dataTypes[ 0 ] ) {
8757			dataTypes.unshift( finalDataType );
8758		}
8759		return responses[ finalDataType ];
8760	}
8761}
8762
8763/* Chain conversions given the request and the original response
8764 * Also sets the responseXXX fields on the jqXHR instance
8765 */
8766function ajaxConvert( s, response, jqXHR, isSuccess ) {
8767	var conv2, current, conv, tmp, prev,
8768		converters = {},
8769		// Work with a copy of dataTypes in case we need to modify it for conversion
8770		dataTypes = s.dataTypes.slice();
8771
8772	// Create converters map with lowercased keys
8773	if ( dataTypes[ 1 ] ) {
8774		for ( conv in s.converters ) {
8775			converters[ conv.toLowerCase() ] = s.converters[ conv ];
8776		}
8777	}
8778
8779	current = dataTypes.shift();
8780
8781	// Convert to each sequential dataType
8782	while ( current ) {
8783
8784		if ( s.responseFields[ current ] ) {
8785			jqXHR[ s.responseFields[ current ] ] = response;
8786		}
8787
8788		// Apply the dataFilter if provided
8789		if ( !prev && isSuccess && s.dataFilter ) {
8790			response = s.dataFilter( response, s.dataType );
8791		}
8792
8793		prev = current;
8794		current = dataTypes.shift();
8795
8796		if ( current ) {
8797
8798			// There's only work to do if current dataType is non-auto
8799			if ( current === "*" ) {
8800
8801				current = prev;
8802
8803			// Convert response if prev dataType is non-auto and differs from current
8804			} else if ( prev !== "*" && prev !== current ) {
8805
8806				// Seek a direct converter
8807				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
8808
8809				// If none found, seek a pair
8810				if ( !conv ) {
8811					for ( conv2 in converters ) {
8812
8813						// If conv2 outputs current
8814						tmp = conv2.split( " " );
8815						if ( tmp[ 1 ] === current ) {
8816
8817							// If prev can be converted to accepted input
8818							conv = converters[ prev + " " + tmp[ 0 ] ] ||
8819								converters[ "* " + tmp[ 0 ] ];
8820							if ( conv ) {
8821								// Condense equivalence converters
8822								if ( conv === true ) {
8823									conv = converters[ conv2 ];
8824
8825								// Otherwise, insert the intermediate dataType
8826								} else if ( converters[ conv2 ] !== true ) {
8827									current = tmp[ 0 ];
8828									dataTypes.unshift( tmp[ 1 ] );
8829								}
8830								break;
8831							}
8832						}
8833					}
8834				}
8835
8836				// Apply converter (if not an equivalence)
8837				if ( conv !== true ) {
8838
8839					// Unless errors are allowed to bubble, catch and return them
8840					if ( conv && s[ "throws" ] ) {
8841						response = conv( response );
8842					} else {
8843						try {
8844							response = conv( response );
8845						} catch ( e ) {
8846							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
8847						}
8848					}
8849				}
8850			}
8851		}
8852	}
8853
8854	return { state: "success", data: response };
8855}
8856
8857jQuery.extend({
8858
8859	// Counter for holding the number of active queries
8860	active: 0,
8861
8862	// Last-Modified header cache for next request
8863	lastModified: {},
8864	etag: {},
8865
8866	ajaxSettings: {
8867		url: ajaxLocation,
8868		type: "GET",
8869		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
8870		global: true,
8871		processData: true,
8872		async: true,
8873		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
8874		/*
8875		timeout: 0,
8876		data: null,
8877		dataType: null,
8878		username: null,
8879		password: null,
8880		cache: null,
8881		throws: false,
8882		traditional: false,
8883		headers: {},
8884		*/
8885
8886		accepts: {
8887			"*": allTypes,
8888			text: "text/plain",
8889			html: "text/html",
8890			xml: "application/xml, text/xml",
8891			json: "application/json, text/javascript"
8892		},
8893
8894		contents: {
8895			xml: /xml/,
8896			html: /html/,
8897			json: /json/
8898		},
8899
8900		responseFields: {
8901			xml: "responseXML",
8902			text: "responseText",
8903			json: "responseJSON"
8904		},
8905
8906		// Data converters
8907		// Keys separate source (or catchall "*") and destination types with a single space
8908		converters: {
8909
8910			// Convert anything to text
8911			"* text": String,
8912
8913			// Text to html (true = no transformation)
8914			"text html": true,
8915
8916			// Evaluate text as a json expression
8917			"text json": jQuery.parseJSON,
8918
8919			// Parse text as xml
8920			"text xml": jQuery.parseXML
8921		},
8922
8923		// For options that shouldn't be deep extended:
8924		// you can add your own custom options here if
8925		// and when you create one that shouldn't be
8926		// deep extended (see ajaxExtend)
8927		flatOptions: {
8928			url: true,
8929			context: true
8930		}
8931	},
8932
8933	// Creates a full fledged settings object into target
8934	// with both ajaxSettings and settings fields.
8935	// If target is omitted, writes into ajaxSettings.
8936	ajaxSetup: function( target, settings ) {
8937		return settings ?
8938
8939			// Building a settings object
8940			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
8941
8942			// Extending ajaxSettings
8943			ajaxExtend( jQuery.ajaxSettings, target );
8944	},
8945
8946	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
8947	ajaxTransport: addToPrefiltersOrTransports( transports ),
8948
8949	// Main method
8950	ajax: function( url, options ) {
8951
8952		// If url is an object, simulate pre-1.5 signature
8953		if ( typeof url === "object" ) {
8954			options = url;
8955			url = undefined;
8956		}
8957
8958		// Force options to be an object
8959		options = options || {};
8960
8961		var // Cross-domain detection vars
8962			parts,
8963			// Loop variable
8964			i,
8965			// URL without anti-cache param
8966			cacheURL,
8967			// Response headers as string
8968			responseHeadersString,
8969			// timeout handle
8970			timeoutTimer,
8971
8972			// To know if global events are to be dispatched
8973			fireGlobals,
8974
8975			transport,
8976			// Response headers
8977			responseHeaders,
8978			// Create the final options object
8979			s = jQuery.ajaxSetup( {}, options ),
8980			// Callbacks context
8981			callbackContext = s.context || s,
8982			// Context for global events is callbackContext if it is a DOM node or jQuery collection
8983			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
8984				jQuery( callbackContext ) :
8985				jQuery.event,
8986			// Deferreds
8987			deferred = jQuery.Deferred(),
8988			completeDeferred = jQuery.Callbacks("once memory"),
8989			// Status-dependent callbacks
8990			statusCode = s.statusCode || {},
8991			// Headers (they are sent all at once)
8992			requestHeaders = {},
8993			requestHeadersNames = {},
8994			// The jqXHR state
8995			state = 0,
8996			// Default abort message
8997			strAbort = "canceled",
8998			// Fake xhr
8999			jqXHR = {
9000				readyState: 0,
9001
9002				// Builds headers hashtable if needed
9003				getResponseHeader: function( key ) {
9004					var match;
9005					if ( state === 2 ) {
9006						if ( !responseHeaders ) {
9007							responseHeaders = {};
9008							while ( (match = rheaders.exec( responseHeadersString )) ) {
9009								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
9010							}
9011						}
9012						match = responseHeaders[ key.toLowerCase() ];
9013					}
9014					return match == null ? null : match;
9015				},
9016
9017				// Raw string
9018				getAllResponseHeaders: function() {
9019					return state === 2 ? responseHeadersString : null;
9020				},
9021
9022				// Caches the header
9023				setRequestHeader: function( name, value ) {
9024					var lname = name.toLowerCase();
9025					if ( !state ) {
9026						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
9027						requestHeaders[ name ] = value;
9028					}
9029					return this;
9030				},
9031
9032				// Overrides response content-type header
9033				overrideMimeType: function( type ) {
9034					if ( !state ) {
9035						s.mimeType = type;
9036					}
9037					return this;
9038				},
9039
9040				// Status-dependent callbacks
9041				statusCode: function( map ) {
9042					var code;
9043					if ( map ) {
9044						if ( state < 2 ) {
9045							for ( code in map ) {
9046								// Lazy-add the new callback in a way that preserves old ones
9047								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
9048							}
9049						} else {
9050							// Execute the appropriate callbacks
9051							jqXHR.always( map[ jqXHR.status ] );
9052						}
9053					}
9054					return this;
9055				},
9056
9057				// Cancel the request
9058				abort: function( statusText ) {
9059					var finalText = statusText || strAbort;
9060					if ( transport ) {
9061						transport.abort( finalText );
9062					}
9063					done( 0, finalText );
9064					return this;
9065				}
9066			};
9067
9068		// Attach deferreds
9069		deferred.promise( jqXHR ).complete = completeDeferred.add;
9070		jqXHR.success = jqXHR.done;
9071		jqXHR.error = jqXHR.fail;
9072
9073		// Remove hash character (#7531: and string promotion)
9074		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
9075		// Handle falsy url in the settings object (#10093: consistency with old signature)
9076		// We also use the url parameter if available
9077		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
9078
9079		// Alias method option to type as per ticket #12004
9080		s.type = options.method || options.type || s.method || s.type;
9081
9082		// Extract dataTypes list
9083		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
9084
9085		// A cross-domain request is in order when we have a protocol:host:port mismatch
9086		if ( s.crossDomain == null ) {
9087			parts = rurl.exec( s.url.toLowerCase() );
9088			s.crossDomain = !!( parts &&
9089				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
9090					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
9091						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
9092			);
9093		}
9094
9095		// Convert data if not already a string
9096		if ( s.data && s.processData && typeof s.data !== "string" ) {
9097			s.data = jQuery.param( s.data, s.traditional );
9098		}
9099
9100		// Apply prefilters
9101		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
9102
9103		// If request was aborted inside a prefilter, stop there
9104		if ( state === 2 ) {
9105			return jqXHR;
9106		}
9107
9108		// We can fire global events as of now if asked to
9109		fireGlobals = s.global;
9110
9111		// Watch for a new set of requests
9112		if ( fireGlobals && jQuery.active++ === 0 ) {
9113			jQuery.event.trigger("ajaxStart");
9114		}
9115
9116		// Uppercase the type
9117		s.type = s.type.toUpperCase();
9118
9119		// Determine if request has content
9120		s.hasContent = !rnoContent.test( s.type );
9121
9122		// Save the URL in case we're toying with the If-Modified-Since
9123		// and/or If-None-Match header later on
9124		cacheURL = s.url;
9125
9126		// More options handling for requests with no content
9127		if ( !s.hasContent ) {
9128
9129			// If data is available, append data to url
9130			if ( s.data ) {
9131				cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
9132				// #9682: remove data so that it's not used in an eventual retry
9133				delete s.data;
9134			}
9135
9136			// Add anti-cache in url if needed
9137			if ( s.cache === false ) {
9138				s.url = rts.test( cacheURL ) ?
9139
9140					// If there is already a '_' parameter, set its value
9141					cacheURL.replace( rts, "$1_=" + nonce++ ) :
9142
9143					// Otherwise add one to the end
9144					cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
9145			}
9146		}
9147
9148		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
9149		if ( s.ifModified ) {
9150			if ( jQuery.lastModified[ cacheURL ] ) {
9151				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
9152			}
9153			if ( jQuery.etag[ cacheURL ] ) {
9154				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
9155			}
9156		}
9157
9158		// Set the correct header, if data is being sent
9159		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
9160			jqXHR.setRequestHeader( "Content-Type", s.contentType );
9161		}
9162
9163		// Set the Accepts header for the server, depending on the dataType
9164		jqXHR.setRequestHeader(
9165			"Accept",
9166			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
9167				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
9168				s.accepts[ "*" ]
9169		);
9170
9171		// Check for headers option
9172		for ( i in s.headers ) {
9173			jqXHR.setRequestHeader( i, s.headers[ i ] );
9174		}
9175
9176		// Allow custom headers/mimetypes and early abort
9177		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
9178			// Abort if not done already and return
9179			return jqXHR.abort();
9180		}
9181
9182		// aborting is no longer a cancellation
9183		strAbort = "abort";
9184
9185		// Install callbacks on deferreds
9186		for ( i in { success: 1, error: 1, complete: 1 } ) {
9187			jqXHR[ i ]( s[ i ] );
9188		}
9189
9190		// Get transport
9191		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
9192
9193		// If no transport, we auto-abort
9194		if ( !transport ) {
9195			done( -1, "No Transport" );
9196		} else {
9197			jqXHR.readyState = 1;
9198
9199			// Send global event
9200			if ( fireGlobals ) {
9201				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
9202			}
9203			// Timeout
9204			if ( s.async && s.timeout > 0 ) {
9205				timeoutTimer = setTimeout(function() {
9206					jqXHR.abort("timeout");
9207				}, s.timeout );
9208			}
9209
9210			try {
9211				state = 1;
9212				transport.send( requestHeaders, done );
9213			} catch ( e ) {
9214				// Propagate exception as error if not done
9215				if ( state < 2 ) {
9216					done( -1, e );
9217				// Simply rethrow otherwise
9218				} else {
9219					throw e;
9220				}
9221			}
9222		}
9223
9224		// Callback for when everything is done
9225		function done( status, nativeStatusText, responses, headers ) {
9226			var isSuccess, success, error, response, modified,
9227				statusText = nativeStatusText;
9228
9229			// Called once
9230			if ( state === 2 ) {
9231				return;
9232			}
9233
9234			// State is "done" now
9235			state = 2;
9236
9237			// Clear timeout if it exists
9238			if ( timeoutTimer ) {
9239				clearTimeout( timeoutTimer );
9240			}
9241
9242			// Dereference transport for early garbage collection
9243			// (no matter how long the jqXHR object will be used)
9244			transport = undefined;
9245
9246			// Cache response headers
9247			responseHeadersString = headers || "";
9248
9249			// Set readyState
9250			jqXHR.readyState = status > 0 ? 4 : 0;
9251
9252			// Determine if successful
9253			isSuccess = status >= 200 && status < 300 || status === 304;
9254
9255			// Get response data
9256			if ( responses ) {
9257				response = ajaxHandleResponses( s, jqXHR, responses );
9258			}
9259
9260			// Convert no matter what (that way responseXXX fields are always set)
9261			response = ajaxConvert( s, response, jqXHR, isSuccess );
9262
9263			// If successful, handle type chaining
9264			if ( isSuccess ) {
9265
9266				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
9267				if ( s.ifModified ) {
9268					modified = jqXHR.getResponseHeader("Last-Modified");
9269					if ( modified ) {
9270						jQuery.lastModified[ cacheURL ] = modified;
9271					}
9272					modified = jqXHR.getResponseHeader("etag");
9273					if ( modified ) {
9274						jQuery.etag[ cacheURL ] = modified;
9275					}
9276				}
9277
9278				// if no content
9279				if ( status === 204 || s.type === "HEAD" ) {
9280					statusText = "nocontent";
9281
9282				// if not modified
9283				} else if ( status === 304 ) {
9284					statusText = "notmodified";
9285
9286				// If we have data, let's convert it
9287				} else {
9288					statusText = response.state;
9289					success = response.data;
9290					error = response.error;
9291					isSuccess = !error;
9292				}
9293			} else {
9294				// We extract error from statusText
9295				// then normalize statusText and status for non-aborts
9296				error = statusText;
9297				if ( status || !statusText ) {
9298					statusText = "error";
9299					if ( status < 0 ) {
9300						status = 0;
9301					}
9302				}
9303			}
9304
9305			// Set data for the fake xhr object
9306			jqXHR.status = status;
9307			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
9308
9309			// Success/Error
9310			if ( isSuccess ) {
9311				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
9312			} else {
9313				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
9314			}
9315
9316			// Status-dependent callbacks
9317			jqXHR.statusCode( statusCode );
9318			statusCode = undefined;
9319
9320			if ( fireGlobals ) {
9321				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
9322					[ jqXHR, s, isSuccess ? success : error ] );
9323			}
9324
9325			// Complete
9326			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
9327
9328			if ( fireGlobals ) {
9329				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
9330				// Handle the global AJAX counter
9331				if ( !( --jQuery.active ) ) {
9332					jQuery.event.trigger("ajaxStop");
9333				}
9334			}
9335		}
9336
9337		return jqXHR;
9338	},
9339
9340	getJSON: function( url, data, callback ) {
9341		return jQuery.get( url, data, callback, "json" );
9342	},
9343
9344	getScript: function( url, callback ) {
9345		return jQuery.get( url, undefined, callback, "script" );
9346	}
9347});
9348
9349jQuery.each( [ "get", "post" ], function( i, method ) {
9350	jQuery[ method ] = function( url, data, callback, type ) {
9351		// shift arguments if data argument was omitted
9352		if ( jQuery.isFunction( data ) ) {
9353			type = type || callback;
9354			callback = data;
9355			data = undefined;
9356		}
9357
9358		return jQuery.ajax({
9359			url: url,
9360			type: method,
9361			dataType: type,
9362			data: data,
9363			success: callback
9364		});
9365	};
9366});
9367
9368// Attach a bunch of functions for handling common AJAX events
9369jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
9370	jQuery.fn[ type ] = function( fn ) {
9371		return this.on( type, fn );
9372	};
9373});
9374
9375
9376jQuery._evalUrl = function( url ) {
9377	return jQuery.ajax({
9378		url: url,
9379		type: "GET",
9380		dataType: "script",
9381		async: false,
9382		global: false,
9383		"throws": true
9384	});
9385};
9386
9387
9388jQuery.fn.extend({
9389	wrapAll: function( html ) {
9390		if ( jQuery.isFunction( html ) ) {
9391			return this.each(function(i) {
9392				jQuery(this).wrapAll( html.call(this, i) );
9393			});
9394		}
9395
9396		if ( this[0] ) {
9397			// The elements to wrap the target around
9398			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
9399
9400			if ( this[0].parentNode ) {
9401				wrap.insertBefore( this[0] );
9402			}
9403
9404			wrap.map(function() {
9405				var elem = this;
9406
9407				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
9408					elem = elem.firstChild;
9409				}
9410
9411				return elem;
9412			}).append( this );
9413		}
9414
9415		return this;
9416	},
9417
9418	wrapInner: function( html ) {
9419		if ( jQuery.isFunction( html ) ) {
9420			return this.each(function(i) {
9421				jQuery(this).wrapInner( html.call(this, i) );
9422			});
9423		}
9424
9425		return this.each(function() {
9426			var self = jQuery( this ),
9427				contents = self.contents();
9428
9429			if ( contents.length ) {
9430				contents.wrapAll( html );
9431
9432			} else {
9433				self.append( html );
9434			}
9435		});
9436	},
9437
9438	wrap: function( html ) {
9439		var isFunction = jQuery.isFunction( html );
9440
9441		return this.each(function(i) {
9442			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
9443		});
9444	},
9445
9446	unwrap: function() {
9447		return this.parent().each(function() {
9448			if ( !jQuery.nodeName( this, "body" ) ) {
9449				jQuery( this ).replaceWith( this.childNodes );
9450			}
9451		}).end();
9452	}
9453});
9454
9455
9456jQuery.expr.filters.hidden = function( elem ) {
9457	// Support: Opera <= 12.12
9458	// Opera reports offsetWidths and offsetHeights less than zero on some elements
9459	return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||
9460		(!support.reliableHiddenOffsets() &&
9461			((elem.style && elem.style.display) || jQuery.css( elem, "dis
9461play" )) === "none");
9462};
9463
9464jQuery.expr.filters.visible = function( elem ) {
9465	return !jQuery.expr.filters.hidden( elem );
9466};
9467
9468
9469
9470
9471var r20 = /%20/g,
9472	rbracket = /\[\]$/,
9473	rCRLF = /\r?\n/g,
9474	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
9475	rsubmittable = /^(?:input|select|textarea|keygen)/i;
9476
9477function buildParams( prefix, obj, traditional, add ) {
9478	var name;
9479
9480	if ( jQuery.isArray( obj ) ) {
9481		// Serialize array item.
9482		jQuery.each( obj, function( i, v ) {
9483			if ( traditional || rbracket.test( prefix ) ) {
9484				// Treat each array item as a scalar.
9485				add( prefix, v );
9486
9487			} else {
9488				// Item is non-scalar (array or object), encode its numeric index.
9489				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
9490			}
9491		});
9492
9493	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
9494		// Serialize object item.
9495		for ( name in obj ) {
9496			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
9497		}
9498
9499	} else {
9500		// Serialize scalar item.
9501		add( prefix, obj );
9502	}
9503}
9504
9505// Serialize an array of form elements or a set of
9506// key/values into a query string
9507jQuery.param = function( a, traditional ) {
9508	var prefix,
9509		s = [],
9510		add = function( key, value ) {
9511			// If value is a function, invoke it and return its value
9512			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
9513			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
9514		};
9515
9516	// Set traditional to true for jQuery <= 1.3.2 behavior.
9517	if ( traditional === undefined ) {
9518		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
9519	}
9520
9521	// If an array was passed in, assume that it is an array of form elements.
9522	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
9523		// Serialize the form elements
9524		jQuery.each( a, function() {
9525			add( this.name, this.value );
9526		});
9527
9528	} else {
9529		// If traditional, encode the "old" way (the way 1.3.2 or older
9530		// did it), otherwise encode params recursively.
9531		for ( prefix in a ) {
9532			buildParams( prefix, a[ prefix ], traditional, add );
9533		}
9534	}
9535
9536	// Return the resulting serialization
9537	return s.join( "&" ).replace( r20, "+" );
9538};
9539
9540jQuery.fn.extend({
9541	serialize: function() {
9542		return jQuery.param( this.serializeArray() );
9543	},
9544	serializeArray: function() {
9545		return this.map(function() {
9546			// Can add propHook for "elements" to filter or add form elements
9547			var elements = jQuery.prop( this, "elements" );
9548			return elements ? jQuery.makeArray( elements ) : this;
9549		})
9550		.filter(function() {
9551			var type = this.type;
9552			// Use .is(":disabled") so that fieldset[disabled] works
9553			return this.name && !jQuery( this ).is( ":disabled" ) &&
9554				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
9555				( this.checked || !rcheckableType.test( type ) );
9556		})
9557		.map(function( i, elem ) {
9558			var val = jQuery( this ).val();
9559
9560			return val == null ?
9561				null :
9562				jQuery.isArray( val ) ?
9563					jQuery.map( val, function( val ) {
9564						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
9565					}) :
9566					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
9567		}).get();
9568	}
9569});
9570
9571
9572// Create the request object
9573// (This is still attached to ajaxSettings for backward compatibility)
9574jQuery.ajaxSettings.xhr = window.ActiveXObject !== undefined ?
9575	// Support: IE6+
9576	function() {
9577
9578		// XHR cannot access local files, always use ActiveX for that case
9579		return !this.isLocal &&
9580
9581			// Support: IE7-8
9582			// oldIE XHR does not support non-RFC2616 methods (#13240)
9583			// See http://msdn.microsoft.com/en-us/library/ie/ms536648(v=vs.85).aspx
9584			// and http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9
9585			// Although this check for six methods instead of eight
9586			// since IE also does not support "trace" and "connect"
9587			/^(get|post|head|put|delete|options)$/i.test( this.type ) &&
9588
9589			createStandardXHR() || createActiveXHR();
9590	} :
9591	// For all other browsers, use the standard XMLHttpRequest object
9592	createStandardXHR;
9593
9594var xhrId = 0,
9595	xhrCallbacks = {},
9596	xhrSupported = jQuery.ajaxSettings.xhr();
9597
9598// Support: IE<10
9599// Open requests must be manually aborted on unload (#5280)
9600if ( window.ActiveXObject ) {
9601	jQuery( window ).on( "unload", function() {
9602		for ( var key in xhrCallbacks ) {
9603			xhrCallbacks[ key ]( undefined, true );
9604		}
9605	});
9606}
9607
9608// Determine support properties
9609support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
9610xhrSupported = support.ajax = !!xhrSupported;
9611
9612// Create transport if the browser can provide an xhr
9613if ( xhrSupported ) {
9614
9615	jQuery.ajaxTransport(function( options ) {
9616		// Cross domain only allowed if supported through XMLHttpRequest
9617		if ( !options.crossDomain || support.cors ) {
9618
9619			var callback;
9620
9621			return {
9622				send: function( headers, complete ) {
9623					var i,
9624						xhr = options.xhr(),
9625						id = ++xhrId;
9626
9627					// Open the socket
9628					xhr.open( options.type, options.url, options.async, options.username, options.password );
9629
9630					// Apply custom fields if provided
9631					if ( options.xhrFields ) {
9632						for ( i in options.xhrFields ) {
9633							xhr[ i ] = options.xhrFields[ i ];
9634						}
9635					}
9636
9637					// Override mime type if needed
9638					if ( options.mimeType && xhr.overrideMimeType ) {
9639						xhr.overrideMimeType( options.mimeType );
9640					}
9641
9642					// X-Requested-With header
9643					// For cross-domain requests, seeing as conditions for a preflight are
9644					// akin to a jigsaw puzzle, we simply never set it to be sure.
9645					// (it can always be set on a per-request basis or even using ajaxSetup)
9646					// For same-domain requests, won't change header if already provided.
9647					if ( !options.crossDomain && !headers["X-Requested-With"] ) {
9648						headers["X-Requested-With"] = "XMLHttpRequest";
9649					}
9650
9651					// Set headers
9652					for ( i in headers ) {
9653						// Support: IE<9
9654						// IE's ActiveXObject throws a 'Type Mismatch' exception when setting
9655						// request header to a null-value.
9656						//
9657						// To keep consistent with other XHR implementations, cast the value
9658						// to string and ignore `undefined`.
9659						if ( headers[ i ] !== undefined ) {
9660							xhr.setRequestHeader( i, headers[ i ] + "" );
9661						}
9662					}
9663
9664					// Do send the request
9665					// This may raise an exception which is actually
9666					// handled in jQuery.ajax (so no try/catch here)
9667					xhr.send( ( options.hasContent && options.data ) || null );
9668
9669					// Listener
9670					callback = function( _, isAbort ) {
9671						var status, statusText, responses;
9672
9673						// Was never called and is aborted or complete
9674						if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
9675							// Clean up
9676							delete xhrCallbacks[ id ];
9677							callback = undefined;
9678							xhr.onreadystatechange = jQuery.noop;
9679
9680							// Abort manually if needed
9681							if ( isAbort ) {
9682								if ( xhr.readyState !== 4 ) {
9683									xhr.abort();
9684								}
9685							} else {
9686								responses = {};
9687								status = xhr.status;
9688
9689								// Support: IE<10
9690								// Accessing binary-data responseText throws an exception
9691								// (#11426)
9692								if ( typeof xhr.responseText === "string" ) {
9693									responses.text = xhr.responseText;
9694								}
9695
9696								// Firefox throws an exception when accessing
9697								// statusText for faulty cross-domain requests
9698								try {
9699									statusText = xhr.statusText;
9700								} catch( e ) {
9701									// We normalize with Webkit giving an empty statusText
9702									statusText = "";
9703								}
9704
9705								// Filter status for non standard behaviors
9706
9707								// If the request is local and we have data: assume a success
9708								// (success with no data won't get notified, that's the best we
9709								// can do given current implementations)
9710								if ( !status && options.isLocal && !options.crossDomain ) {
9711									status = responses.text ? 200 : 404;
9712								// IE - #1450: sometimes returns 1223 when it should be 204
9713								} else if ( status === 1223 ) {
9714									status = 204;
9715								}
9716							}
9717						}
9718
9719						// Call complete if needed
9720						if ( responses ) {
9721							complete( status, statusText, responses, xhr.getAllResponseHeaders() );
9722						}
9723					};
9724
9725					if ( !options.async ) {
9726						// if we're in sync mode we fire the callback
9727						callback();
9728					} else if ( xhr.readyState === 4 ) {
9729						// (IE6 & IE7) if it's in cache and has been
9730						// retrieved directly we need to fire the callback
9731						setTimeout( callback );
9732					} else {
9733						// Add to the list of active xhr callbacks
9734						xhr.onreadystatechange = xhrCallbacks[ id ] = callback;
9735					}
9736				},
9737
9738				abort: function() {
9739					if ( callback ) {
9740						callback( undefined, true );
9741					}
9742				}
9743			};
9744		}
9745	});
9746}
9747
9748// Functions to create xhrs
9749function createStandardXHR() {
9750	try {
9751		return new window.XMLHttpRequest();
9752	} catch( e ) {}
9753}
9754
9755function createActiveXHR() {
9756	try {
9757		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
9758	} catch( e ) {}
9759}
9760
9761
9762
9763
9764// Install script dataType
9765jQuery.ajaxSetup({
9766	accepts: {
9767		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
9768	},
9769	contents: {
9770		script: /(?:java|ecma)script/
9771	},
9772	converters: {
9773		"text script": function( text ) {
9774			jQuery.globalEval( text );
9775			return text;
9776		}
9777	}
9778});
9779
9780// Handle cache's special case and global
9781jQuery.ajaxPrefilter( "script", function( s ) {
9782	if ( s.cache === undefined ) {
9783		s.cache = false;
9784	}
9785	if ( s.crossDomain ) {
9786		s.type = "GET";
9787		s.global = false;
9788	}
9789});
9790
9791// Bind script tag hack transport
9792jQuery.ajaxTransport( "script", function(s) {
9793
9794	// This transport only deals with cross domain requests
9795	if ( s.crossDomain ) {
9796
9797		var script,
9798			head = document.head || jQuery("head")[0] || document.documentElement;
9799
9800		return {
9801
9802			send: function( _, callback ) {
9803
9804				script = document.createElement("script");
9805
9806				script.async = true;
9807
9808				if ( s.scriptCharset ) {
9809					script.charset = s.scriptCharset;
9810				}
9811
9812				script.src = s.url;
9813
9814				// Attach handlers for all browsers
9815				script.onload = script.onreadystatechange = function( _, isAbort ) {
9816
9817					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
9818
9819						// Handle memory leak in IE
9820						script.onload = script.onreadystatechange = null;
9821
9822						// Remove the script
9823						if ( script.parentNode ) {
9824							script.parentNode.removeChild( script );
9825						}
9826
9827						// Dereference the script
9828						script = null;
9829
9830						// Callback if not abort
9831						if ( !isAbort ) {
9832							callback( 200, "success" );
9833						}
9834					}
9835				};
9836
9837				// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
9838				// Use native DOM manipulation to avoid our domManip AJAX trickery
9839				head.insertBefore( script, head.firstChild );
9840			},
9841
9842			abort: function() {
9843				if ( script ) {
9844					script.onload( undefined, true );
9845				}
9846			}
9847		};
9848	}
9849});
9850
9851
9852
9853
9854var oldCallbacks = [],
9855	rjsonp = /(=)\?(?=&|$)|\?\?/;
9856
9857// Default jsonp settings
9858jQuery.ajaxSetup({
9859	jsonp: "callback",
9860	jsonpCallback: function() {
9861		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
9862		this[ callback ] = true;
9863		return callback;
9864	}
9865});
9866
9867// Detect, normalize options and install callbacks for jsonp requests
9868jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
9869
9870	var callbackName, overwritten, responseContainer,
9871		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
9872			"url" :
9873			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
9874		);
9875
9876	// Handle iff the expected data type is "jsonp" or we have a parameter to set
9877	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
9878
9879		// Get callback name, remembering preexisting value associated with it
9880		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
9881			s.jsonpCallback() :
9882			s.jsonpCallback;
9883
9884		// Insert callback into url or form data
9885		if ( jsonProp ) {
9886			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
9887		} else if ( s.jsonp !== false ) {
9888			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
9889		}
9890
9891		// Use data converter to retrieve json after script execution
9892		s.converters["script json"] = function() {
9893			if ( !responseContainer ) {
9894				jQuery.error( callbackName + " was not called" );
9895			}
9896			return responseContainer[ 0 ];
9897		};
9898
9899		// force json dataType
9900		s.dataTypes[ 0 ] = "json";
9901
9902		// Install callback
9903		overwritten = window[ callbackName ];
9904		window[ callbackName ] = function() {
9905			responseContainer = arguments;
9906		};
9907
9908		// Clean-up function (fires after converters)
9909		jqXHR.always(function() {
9910			// Restore preexisting value
9911			window[ callbackName ] = overwritten;
9912
9913			// Save back as free
9914			if ( s[ callbackName ] ) {
9915				// make sure that re-using the options doesn't screw things around
9916				s.jsonpCallback = originalSettings.jsonpCallback;
9917
9918				// save the callback name for future use
9919				oldCallbacks.push( callbackName );
9920			}
9921
9922			// Call if it was a function and we have a response
9923			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
9924				overwritten( responseContainer[ 0 ] );
9925			}
9926
9927			responseContainer = overwritten = undefined;
9928		});
9929
9930		// Delegate to script
9931		return "script";
9932	}
9933});
9934
9935
9936
9937
9938// data: string of html
9939// context (optional): If specified, the fragment will be created in this context, defaults to document
9940// keepScripts (optional): If true, will include scripts passed in the html string
9941jQuery.parseHTML = function( data, context, keepScripts ) {
9942	if ( !data || typeof data !== "string" ) {
9943		return null;
9944	}
9945	if ( typeof context === "boolean" ) {
9946		keepScripts = context;
9947		context = false;
9948	}
9949	context = context || document;
9950
9951	var parsed = rsingleTag.exec( data ),
9952		scripts = !keepScripts && [];
9953
9954	// Single tag
9955	if ( parsed ) {
9956		return [ context.createElement( parsed[1] ) ];
9957	}
9958
9959	parsed = jQuery.buildFragment( [ data ], context, scripts );
9960
9961	if ( scripts && scripts.length ) {
9962		jQuery( scripts ).remove();
9963	}
9964
9965	return jQuery.merge( [], parsed.childNodes );
9966};
9967
9968
9969// Keep a copy of the old load method
9970var _load = jQuery.fn.load;
9971
9972/**
9973 * Load a url into a page
9974 */
9975jQuery.fn.load = function( url, params, callback ) {
9976	if ( typeof url !== "string" && _load ) {
9977		return _load.apply( this, arguments );
9978	}
9979
9980	var selector, response, type,
9981		self = this,
9982		off = url.indexOf(" ");
9983
9984	if ( off >= 0 ) {
9985		selector = url.slice( off, url.length );
9986		url = url.slice( 0, off );
9987	}
9988
9989	// If it's a function
9990	if ( jQuery.isFunction( params ) ) {
9991
9992		// We assume that it's the callback
9993		callback = params;
9994		params = undefined;
9995
9996	// Otherwise, build a param string
9997	} else if ( params && typeof params === "object" ) {
9998		type = "POST";
9999	}
10000
10001	// If we have elements to modify, make the request
10002	if ( self.length > 0 ) {
10003		jQuery.ajax({
10004			url: url,
10005
10006			// if "type" variable is undefined, then "GET" method will be used
10007			type: type,
10008			dataType: "html",
10009			data: params
10010		}).done(function( responseText ) {
10011
10012			// Save response for use in complete callback
10013			response = arguments;
10014
10015			self.html( selector ?
10016
10017				// If a selector was specified, locate the right elements in a dummy div
10018				// Exclude scripts to avoid IE 'Permission Denied' errors
10019				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
10020
10021				// Otherwise use the full result
10022				responseText );
10023
10024		}).complete( callback && function( jqXHR, status ) {
10025			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
10026		});
10027	}
10028
10029	return this;
10030};
10031
10032
10033
10034
10035jQuery.expr.filters.animated = function( elem ) {
10036	return jQuery.grep(jQuery.timers, function( fn ) {
10037		return elem === fn.elem;
10038	}).length;
10039};
10040
10041
10042
10043
10044
10045var docElem = window.document.documentElement;
10046
10047/**
10048 * Gets a window from an element
10049 */
10050function getWindow( elem ) {
10051	return jQuery.isWindow( elem ) ?
10052		elem :
10053		elem.nodeType === 9 ?
10054			elem.defaultView || elem.parentWindow :
10055			false;
10056}
10057
10058jQuery.offset = {
10059	setOffset: function( elem, options, i ) {
10060		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
10061			position = jQuery.css( elem, "position" ),
10062			curElem = jQuery( elem ),
10063			props = {};
10064
10065		// set position first, in-case top/left are set even on static elem
10066		if ( position === "static" ) {
10067			elem.style.position = "relative";
10068		}
10069
10070		curOffset = curElem.offset();
10071		curCSSTop = jQuery.css( elem, "top" );
10072		curCSSLeft = jQuery.css( elem, "left" );
10073		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
10074			jQuery.inArray("auto", [ curCSSTop, curCSSLeft ] ) > -1;
10075
10076		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
10077		if ( calculatePosition ) {
10078			curPosition = curElem.position();
10079			curTop = curPosition.top;
10080			curLeft = curPosition.left;
10081		} else {
10082			curTop = parseFloat( curCSSTop ) || 0;
10083			curLeft = parseFloat( curCSSLeft ) || 0;
10084		}
10085
10086		if ( jQuery.isFunction( options ) ) {
10087			options = options.call( elem, i, curOffset );
10088		}
10089
10090		if ( options.top != null ) {
10091			props.top = ( options.top - curOffset.top ) + curTop;
10092		}
10093		if ( options.left != null ) {
10094			props.left = ( options.left - curOffset.left ) + curLeft;
10095		}
10096
10097		if ( "using" in options ) {
10098			options.using.call( elem, props );
10099		} else {
10100			curElem.css( props );
10101		}
10102	}
10103};
10104
10105jQuery.fn.extend({
10106	offset: function( options ) {
10107		if ( arguments.length ) {
10108			return options === undefined ?
10109				this :
10110				this.each(function( i ) {
10111					jQuery.offset.setOffset( this, options, i );
10112				});
10113		}
10114
10115		var docElem, win,
10116			box = { top: 0, left: 0 },
10117			elem = this[ 0 ],
10118			doc = elem && elem.ownerDocument;
10119
10120		if ( !doc ) {
10121			return;
10122		}
10123
10124		docElem = doc.documentElement;
10125
10126		// Make sure it's not a disconnected DOM node
10127		if ( !jQuery.contains( docElem, elem ) ) {
10128			return box;
10129		}
10130
10131		// If we don't have gBCR, just use 0,0 rather than error
10132		// BlackBerry 5, iOS 3 (original iPhone)
10133		if ( typeof elem.getBoundingClientRect !== strundefined ) {
10134			box = elem.getBoundingClientRect();
10135		}
10136		win = getWindow( doc );
10137		return {
10138			top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
10139			left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
10140		};
10141	},
10142
10143	position: function() {
10144		if ( !this[ 0 ] ) {
10145			return;
10146		}
10147
10148		var offsetParent, offset,
10149			parentOffset = { top: 0, left: 0 },
10150			elem = this[ 0 ];
10151
10152		// fixed elements are offset from window (parentOffset = {top:0, left
10152: 0}, because it is its only offset parent
10153		if ( jQuery.css( elem, "position" ) === "fixed" ) {
10154			// we assume that getBoundingClientRect is available when computed position is fixed
10155			offset = elem.getBoundingClientRect();
10156		} else {
10157			// Get *real* offsetParent
10158			offsetParent = this.offsetParent();
10159
10160			// Get correct offsets
10161			offset = this.offset();
10162			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
10163				parentOffset = offsetParent.offset();
10164			}
10165
10166			// Add offsetParent borders
10167			parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
10168			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
10169		}
10170
10171		// Subtract parent offsets and element margins
10172		// note: when an element has margin: auto the offsetLeft and marginLeft
10173		// are the same in Safari causing offset.left to incorrectly be 0
10174		return {
10175			top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
10176			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
10177		};
10178	},
10179
10180	offsetParent: function() {
10181		return this.map(function() {
10182			var offsetParent = this.offsetParent || docElem;
10183
10184			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) {
10185				offsetParent = offsetParent.offsetParent;
10186			}
10187			return offsetParent || docElem;
10188		});
10189	}
10190});
10191
10192// Create scrollLeft and scrollTop methods
10193jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
10194	var top = /Y/.test( prop );
10195
10196	jQuery.fn[ method ] = function( val ) {
10197		return access( this, function( elem, method, val ) {
10198			var win = getWindow( elem );
10199
10200			if ( val === undefined ) {
10201				return win ? (prop in win) ? win[ prop ] :
10202					win.document.documentElement[ method ] :
10203					elem[ method ];
10204			}
10205
10206			if ( win ) {
10207				win.scrollTo(
10208					!top ? val : jQuery( win ).scrollLeft(),
10209					top ? val : jQuery( win ).scrollTop()
10210				);
10211
10212			} else {
10213				elem[ method ] = val;
10214			}
10215		}, method, val, arguments.length, null );
10216	};
10217});
10218
10219// Add the top/left cssHooks using jQuery.fn.position
10220// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
10221// getComputedStyle returns percent when specified for top/left/bottom/right
10222// rather than make the css module depend on the offset module, we just check for it here
10223jQuery.each( [ "top", "left" ], function( i, prop ) {
10224	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
10225		function( elem, computed ) {
10226			if ( computed ) {
10227				computed = curCSS( elem, prop );
10228				// if curCSS returns percentage, fallback to offset
10229				return rnumnonpx.test( computed ) ?
10230					jQuery( elem ).position()[ prop ] + "px" :
10231					computed;
10232			}
10233		}
10234	);
10235});
10236
10237
10238// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
10239jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
10240	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
10241		// margin is only for outerHeight, outerWidth
10242		jQuery.fn[ funcName ] = function( margin, value ) {
10243			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
10244				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
10245
10246			return access( this, function( elem, type, value ) {
10247				var doc;
10248
10249				if ( jQuery.isWindow( elem ) ) {
10250					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
10251					// isn't a whole lot we can do. See pull request at this URL for discussion:
10252					// https://github.com/jquery/jquery/pull/764
10253					return elem.document.documentElement[ "client" + name ];
10254				}
10255
10256				// Get document width or height
10257				if ( elem.nodeType === 9 ) {
10258					doc = elem.documentElement;
10259
10260					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
10261					// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
10262					return Math.max(
10263						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
10264						elem.body[ "offset" + name ], doc[ "offset" + name ],
10265						doc[ "client" + name ]
10266					);
10267				}
10268
10269				return value === undefined ?
10270					// Get width or height on the element, requesting but not forcing parseFloat
10271					jQuery.css( elem, type, extra ) :
10272
10273					// Set width or height on the element
10274					jQuery.style( elem, type, value, extra );
10275			}, type, chainable ? margin : undefined, chainable, null );
10276		};
10277	});
10278});
10279
10280
10281// The number of elements contained in the matched element set
10282jQuery.fn.size = function() {
10283	return this.length;
10284};
10285
10286jQuery.fn.andSelf = jQuery.fn.addBack;
10287
10288
10289
10290
10291// Register as a named AMD module, since jQuery can be concatenated with other
10292// files that may use define, but not via a proper concatenation script that
10293// understands anonymous AMD modules. A named AMD is safest and most robust
10294// way to register. Lowercase jquery is used because AMD module names are
10295// derived from file names, and jQuery is normally delivered in a lowercase
10296// file name. Do this after creating the global so that if an AMD module wants
10297// to call noConflict to hide this version of jQuery, it will work.
10298if ( typeof define === "function" && define.amd ) {
10299	define( "jquery", [], function() {
10300		return jQuery;
10301	});
10302}
10303
10304
10305
10306
10307var
10308	// Map over jQuery in case of overwrite
10309	_jQuery = window.jQuery,
10310
10311	// Map over the $ in case of overwrite
10312	_$ = window.$;
10313
10314jQuery.noConflict = function( deep ) {
10315	if ( window.$ === jQuery ) {
10316		window.$ = _$;
10317	}
10318
10319	if ( deep && window.jQuery === jQuery ) {
10320		window.jQuery = _jQuery;
10321	}
10322
10323	return jQuery;
10324};
10325
10326// Expose jQuery and $ identifiers, even in
10327// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
10328// and CommonJS for browser emulators (#13566)
10329if ( typeof noGlobal === strundefined ) {
10330	window.jQuery = window.$ = jQuery;
10331}
10332
10333
10334
10335
10336return jQuery;
10337
10338}));
10339/* jshint node: true */
10340
10341/**
10342 * Unobtrusive scripting adapter for jQuery
10343 * https://github.com/rails/jquery-ujs
10344 *
10345 * Requires jQuery 1.8.0 or later.
10346 *
10347 * Released under the MIT license
10348 *
10349 */
10350
10351
10352(function() {
10353  'use strict';
10354
10355  var jqueryUjsInit = function($, undefined) {
10356
10357  // Cut down on the number of issues from people inadvertently including jquery_ujs twice
10358  // by detecting and raising an error when it happens.
10359  if ( $.rails !== undefined ) {
10360    $.error('jquery-ujs has already been loaded!');
10361  }
10362
10363  // Shorthand to make it a little easier to call public rails functions from within rails.js
10364  var rails;
10365  var $document = $(document);
10366
10367  $.rails = rails = {
10368    // Link elements bound by jquery-ujs
10369    linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',
10370
10371    // Button elements bound by jquery-ujs
10372    buttonClickSelector: 'button[data-remote]:not([form]):not(form button), button[data-confirm]:not([form]):not(form button)',
10373
10374    // Select elements bound by jquery-ujs
10375    inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
10376
10377    // Form elements bound by jquery-ujs
10378    formSubmitSelector: 'form:not([data-turbo=true])',
10379
10380    // Form input elements bound by jquery-ujs
10381    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])',
10382
10383    // Form input elements disabled during form submission
10384    disableSelector: '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',
10385
10386    // Form input elements re-enabled after form submission
10387    enableSelector: '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',
10388
10389    // Form required input elements
10390    requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])',
10391
10392    // Form file input elements
10393    fileInputSelector: 'input[name][type=file]:not([disabled])',
10394
10395    // Link onClick disable selector with possible reenable after remote submission
10396    linkDisableSelector: 'a[data-disable-with], a[data-disable]',
10397
10398    // Button onClick disable selector with possible reenable after remote submission
10399    buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]',
10400
10401    // Up-to-date Cross-Site Request Forgery token
10402    csrfToken: function() {
10403     return $('meta[name=csrf-token]').attr('content');
10404    },
10405
10406    // URL param that must contain the CSRF token
10407    csrfParam: function() {
10408     return $('meta[name=csrf-param]').attr('content');
10409    },
10410
10411    // Make sure that every Ajax request sends the CSRF token
10412    CSRFProtection: function(xhr) {
10413      var token = rails.csrfToken();
10414      if (token) xhr.setRequestHeader('X-CSRF-Token', token);
10415    },
10416
10417    // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
10418    refreshCSRFTokens: function(){
10419      $('form input[name="' + rails.csrfParam() + '"]').val(rails.csrfToken());
10420    },
10421
10422    // Triggers an event on an element and returns false if the event result is false
10423    fire: function(obj, name, data) {
10424      var event = $.Event(name);
10425      obj.trigger(event, data);
10426      return event.result !== false;
10427    },
10428
10429    // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
10430    confirm: function(message) {
10431      return confirm(message);
10432    },
10433
10434    // Default ajax function, may be overridden with custom function in $.rails.ajax
10435    ajax: function(options) {
10436      return $.ajax(options);
10437    },
10438
10439    // Default way to get an element's href. May be overridden at $.rails.href.
10440    href: function(element) {
10441      return element[0].href;
10442    },
10443
10444    // Checks "data-remote" if true to handle the request through a XHR request.
10445    isRemote: function(element) {
10446      return element.data('remote') !== undefined && element.data('remote') !== false;
10447    },
10448
10449    // Submits "remote" forms and links with ajax
10450    handleRemote: function(element) {
10451      var method, url, data, withCredentials, dataType, options;
10452
10453      if (rails.fire(element, 'ajax:before')) {
10454        withCredentials = element.data('with-credentials') || null;
10455        dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
10456
10457        if (element.is('form')) {
10458          method = element.data('ujs:submit-button-formmethod') || element.attr('method');
10459          url = element.data('ujs:submit-button-formaction') || element.attr('action');
10460          data = $(element[0]).serializeArray();
10461          // memoized value from clicked submit button
10462          var button = element.data('ujs:submit-button');
10463          if (button) {
10464            data.push(button);
10465            element.data('ujs:submit-button', null);
10466          }
10467          element.data('ujs:submit-button-formmethod', null);
10468          element.data('ujs:submit-button-formaction', null);
10469        } else if (element.is(rails.inputChangeSelector)) {
10470          method = element.data('method');
10471          url = element.data('url');
10472          data = element.serialize();
10473          if (element.data('params')) data = data + '&' + element.data('params');
10474        } else if (element.is(rails.buttonClickSelector)) {
10475          method = element.data('method') || 'get';
10476          url = element.data('url');
10477          data = element.serialize();
10478          if (element.data('params')) data = data + '&' + element.data('params');
10479        } else {
10480          method = element.data('method');
10481          url = rails.href(element);
10482          data = element.data('params') || null;
10483        }
10484
10485        options = {
10486          type: method || 'GET', data: data, dataType: dataType,
10487          // stopping the "ajax:beforeSend" event will cancel the ajax request
10488          beforeSend: function(xhr, settings) {
10489            if (settings.dataType === undefined) {
10490              xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
10491            }
10492            if (rails.fire(element, 'ajax:beforeSend', [xhr, settings])) {
10493              element.trigger('ajax:send', xhr);
10494            } else {
10495              return false;
10496            }
10497          },
10498          success: function(data, status, xhr) {
10499            element.trigger('ajax:success', [data, status, xhr]);
10500          },
10501          complete: function(xhr, status) {
10502            element.trigger('ajax:complete', [xhr, status]);
10503          },
10504          error: function(xhr, status, error) {
10505            element.trigger('ajax:error', [xhr, status, error]);
10506          },
10507          crossDomain: rails.isCrossDomain(url)
10508        };
10509
10510        // There is no withCredentials for IE6-8 when
10511        // "Enable native XMLHTTP support" is disabled
10512        if (withCredentials) {
10513          options.xhrFields = {
10514            withCredentials: withCredentials
10515          };
10516        }
10517
10518        // Only pass url to `ajax` options if not blank
10519        if (url) { options.url = url; }
10520
10521        return rails.ajax(options);
10522      } else {
10523        return false;
10524      }
10525    },
10526
10527    // Determines if the request is a cross domain request.
10528    isCrossDomain: function(url) {
10529      var originAnchor = document.createElement('a');
10530      originAnchor.href = location.href;
10531      var urlAnchor = document.createElement('a');
10532
10533      try {
10534        urlAnchor.href = url;
10535        // This is a workaround to a IE bug.
10536        urlAnchor.href = urlAnchor.href;
10537
10538        // If URL protocol is false or is a string containing a single colon
10539        // *and* host are false, assume it is not a cross-domain request
10540        // (should only be the case for IE7 and IE compatibility mode).
10541        // Otherwise, evaluate protocol and host of the URL against the origin
10542        // protocol and host.
10543        return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) ||
10544          (originAnchor.protocol + '//' + originAnchor.host ===
10545            urlAnchor.protocol + '//' + urlAnchor.host));
10546      } catch (e) {
10547        // If there is an error parsing the URL, assume it is crossDomain.
10548        return true;
10549      }
10550    },
10551
10552    // Handles "data-method" on links such as:
10553    // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
10554    handleMethod: function(link) {
10555      var href = rails.href(link),
10556        method = link.data('method'),
10557        target = link.attr('target'),
10558        csrfToken = rails.csrfToken(),
10559        csrfParam = rails.csrfParam(),
10560        form = $('<form method="post" action="' + href + '"></form>'),
10561        metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
10562
10563      if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
10564        metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
10565      }
10566
10567      if (target) { form.attr('target', target); }
10568
10569      form.hide().append(metadataInput).appendTo('body');
10570      form.submit();
10571    },
10572
10573    // Helper function that returns form elements that match the specified CSS selector
10574    // If form is actually a "form" element this will return associated elements outside the from that have
10575    // the html form attribute set
10576    formElements: function(form, selector) {
10577      return form.is('form') ? $(form[0].elements).filter(selector) : form.find(selector);
10578    },
10579
10580    /* Disables form elements:
10581      - Caches element value in 'ujs:enable-with' data store
10582      - Replaces element text with value of 'data-disable-with' attribute
10583      - Sets disabled property to true
10584    */
10585    disableFormElements: function(form) {
10586      rails.formElements(form, rails.disableSelector).each(function() {
10587        rails.disableFormElement($(this));
10588      });
10589    },
10590
10591    disableFormElement: function(element) {
10592      var method, replacement;
10593
10594      method = element.is('button') ? 'html' : 'val';
10595      replacement = element.data('disable-with');
10596
10597      if (replacement !== undefined) {
10598        element.data('ujs:enable-with', element[method]());
10599        element[method](replacement);
10600      }
10601
10602      element.prop('disabled', true);
10603      element.data('ujs:disabled', true);
10604    },
10605
10606    /* Re-enables disabled form elements:
10607      - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
10608      - Sets disabled property to false
10609    */
10610    enableFormElements: function(form) {
10611      rails.formElements(form, rails.enableSelector).each(function() {
10612        rails.enableFormElement($(this));
10613      });
10614    },
10615
10616    enableFormElement: function(element) {
10617      var method = element.is('button') ? 'html' : 'val';
10618      if (element.data('ujs:enable-with') !== undefined) {
10619        element[method](element.data('ujs:enable-with'));
10620        element.removeData('ujs:enable-with'); // clean up cache
10621      }
10622      element.prop('disabled', false);
10623      element.removeData('ujs:disabled');
10624    },
10625
10626   /* For 'data-confirm' attribute:
10627      - Fires `confirm` event
10628      - Shows the confirmation dialog
10629      - Fires the `confirm:complete` event
10630
10631      Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
10632      Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
10633      Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
10634      return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
10635   */
10636    allowAction: function(element) {
10637      var message = element.data('confirm'),
10638          answer = false, callback;
10639      if (!message) { return true; }
10640
10641      if (rails.fire(element, 'confirm')) {
10642        try {
10643          answer = rails.confirm(message);
10644        } catch (e) {
10645          (console.error || console.log).call(console, e.stack || e);
10646        }
10647        callback = rails.fire(element, 'confirm:complete', [answer]);
10648      }
10649      return answer && callback;
10650    },
10651
10652    // Helper function which checks for blank inputs in a form that match the specified CSS selector
10653    blankInputs: function(form, specifiedSelector, nonBlank) {
10654      var foundInputs = $(),
10655        input,
10656        valueToCheck,
10657        radiosForNameWithNoneSelected,
10658        radioName,
10659        selector = specifiedSelector || 'input,textarea',
10660        requiredInputs = form.find(selector),
10661        checkedRadioButtonNames = {};
10662
10663      requiredInputs.each(function() {
10664        input = $(this);
10665        if (input.is('input[type=radio]')) {
10666
10667          // Don't count unchecked required radio as blank if other radio with same name is checked,
10668          // regardless of whether same-name radio input has required attribute or not. The spec
10669          // states https://www.w3.org/TR/html5/forms.html#the-required-attribute
10670          radioName = input.attr('name');
10671
10672          // Skip if we've already seen the radio with this name.
10673          if (!checkedRadioButtonNames[radioName]) {
10674
10675            // If none checked
10676            if (form.find('input[type=radio]:checked[name="' + radioName + '"]').length === 0) {
10677              radiosForNameWithNoneSelected = form.find(
10678                'input[type=radio][name="' + radioName + '"]');
10679              foundInputs = foundInputs.add(radiosForNameWithNoneSelected);
10680            }
10681
10682            // We only need to check each name once.
10683            checkedRadioButtonNames[radioName] = radioName;
10684          }
10685        } else {
10686          valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : !!input.val();
10687          if (valueToCheck === nonBlank) {
10688            foundInputs = foundInputs.add(input);
10689          }
10690        }
10691      });
10692      return foundInputs.length ? foundInputs : false;
10693    },
10694
10695    // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
10696    nonBlankInputs: function(form, specifiedSelector) {
10697      return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
10698    },
10699
10700    // Helper function, needed to provide consistent behavior in IE
10701    stopEverything: function(e) {
10702      $(e.target).trigger('ujs:everythingStopped');
10703      e.stopImmediatePropagation();
10704      return false;
10705    },
10706
10707    //  Replace element's html with the 'data-disable-with' after storing original html
10708    //  and prevent clicking on it
10709    disableElement: function(element) {
10710      var replacement = element.data('disable-with');
10711
10712      if (replacement !== undefined) {
10713        element.data('ujs:enable-with', element.html()); // store enabled state
10714        element.html(replacement);
10715      }
10716
10717      element.on('click.railsDisable', function(e) { // prevent further clicking
10718        return rails.stopEverything(e);
10719      });
10720      element.data('ujs:disabled', true);
10721    },
10722
10723    // Restore element to its original state which was disabled by 'disableElement' above
10724    enableElement: function(element) {
10725      if (element.data('ujs:enable-with') !== undefined) {
10726        element.html(element.data('ujs:enable-with')); // set to old enabled state
10727        element.removeData('ujs:enable-with'); // clean up cache
10728      }
10729      element.off('click.railsDisable'); // enable element
10730      element.removeData('ujs:disabled');
10731    }
10732  };
10733
10734  if (rails.fire($document, 'rails:attachBindings')) {
10735
10736    $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
10737
10738    // This event works the same as the load event, except that it fires every
10739    // time the page is loaded.
10740    //
10741    // See https://github.com/rails/jquery-ujs/issues/357
10742    // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
10743    $(window).on('pageshow.rails', function () {
10744      $($.rails.enableSelector).each(function () {
10745        var element = $(this);
10746
10747        if (element.data('ujs:disabled')) {
10748          $.rails.enableFormElement(element);
10749        }
10750      });
10751
10752      $($.rails.linkDisableSelector).each(function () {
10753        var element = $(this);
10754
10755        if (element.data('ujs:disabled')) {
10756          $.rails.enableElement(element);
10757        }
10758      });
10759    });
10760
10761    $document.on('ajax:complete', rails.linkDisableSelector, function() {
10762        rails.enableElement($(this));
10763    });
10764
10765    $document.on('ajax:complete', rails.buttonDisableSelector, function() {
10766        rails.enableFormElement($(this));
10767    });
10768
10769    $document.on('click.rails', rails.linkClickSelector, function(e) {
10770      var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
10771      if (!rails.allowAction(link)) return rails.stopEverything(e);
10772
10773      if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
10774
10775      if (rails.isRemote(link)) {
10776        if (metaClick && (!method || method === 'GET') && !data) { return true; }
10777
10778        var handleRemote = rails.handleRemote(link);
10779        // Response from rails.handleRemote() will either be false or a deferred object promise.
10780        if (handleRemote === false) {
10781          rails.enableElement(link);
10782        } else {
10783          handleRemote.fail( function() { rails.enableElement(link); } );
10784        }
10785        return false;
10786
10787      } else if (method) {
10788        rails.handleMethod(link);
10789        return false;
10790      }
10791    });
10792
10793    $document.on('click.rails', rails.buttonClickSelector, function(e) {
10794      var button = $(this);
10795
10796      if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e);
10797
10798      if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button);
10799
10800      var handleRemote = rails.handleRemote(button);
10801      // Response from rails.handleRemote() will either be false or a deferred object promise.
10802      if (handleRemote === false) {
10803        rails.enableFormElement(button);
10804      } else {
10805        handleRemote.fail( function() { rails.enableFormElement(button); } );
10806      }
10807      return false;
10808    });
10809
10810    $document.on('change.rails', rails.inputChangeSelector, function(e) {
10811      var link = $(this);
10812      if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e);
10813
10814      rails.handleRemote(link);
10815      return false;
10816    });
10817
10818    $document.on('submit.rails', rails.formSubmitSelector, function(e) {
10819      var form = $(this),
10820        remote = rails.isRemote(form),
10821        blankRequiredInputs,
10822        nonBlankFileInputs;
10823
10824      if (!rails.allowAction(form)) return rails.stopEverything(e);
10825
10826      // Skip other logic when required values are missing or file upload is present
10827      if (form.attr('novalidate') === undefined) {
10828        if (form.data('ujs:formnovalidate-button') === undefined) {
10829          blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false);
10830          if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
10831            return rails.stopEverything(e);
10832          }
10833        } else {
10834          // Clear the formnovalidate in case the next button click is not on a formnovalidate button
10835          // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain
10836          form.data('ujs:formnovalidate-button', undefined);
10837        }
10838      }
10839
10840      if (remote) {
10841        nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
10842        if (nonBlankFileInputs) {
10843          // Slight timeout so that the submit button gets properly serialized
10844          // (make it easy for event handler to serialize form without disabled values)
10845          setTimeout(function(){ rails.disableFormElements(form); }, 13);
10846          var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
10847
10848          // Re-enable form elements if event bindings return false (canceling normal form submission)
10849          if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
10850
10851          return aborted;
10852        }
10853
10854        rails.handleRemote(form);
10855        return false;
10856
10857      } else {
10858        // Slight timeout so that the submit button gets properly serialized
10859        setTimeout(function(){ rails.disableFormElements(form); }, 13);
10860      }
10861    });
10862
10863    $document.on('click.rails', rails.formInputClickSelector, function(event) {
10864      var button = $(this);
10865
10866      if (!rails.allowAction(button)) return rails.stopEverything(event);
10867
10868      // Register the pressed submit button
10869      var name = button.attr('name'),
10870        data = name ? {name:name, value:button.val()} : null;
10871
10872      var form = button.closest('form');
10873      if (form.length === 0) {
10874        form = $('#' + button.attr('form'));
10875      }
10876      form.data('ujs:submit-button', data);
10877
10878      // Save attributes from button
10879      form.data('ujs:formnovalidate-button', button.attr('formnovalidate'));
10880      form.data('ujs:submit-button-formaction', button.attr('formaction'));
10881      form.data('ujs:submit-button-formmethod', button.attr('formmethod'));
10882    });
10883
10884    $document.on('ajax:send.rails', rails.formSubmitSelector, function(event) {
10885      if (this === event.target) rails.disableFormElements($(this));
10886    });
10887
10888    $document.on('ajax:complete.rails', rails.formSubmitSelector, function(event) {
10889      if (this === event.target) rails.enableFormElements($(this));
10890    });
10891
10892    $(function(){
10893      rails.refreshCSRFTokens();
10894    });
10895  }
10896
10897  };
10898
10899  if (window.jQuery) {
10900    jqueryUjsInit(jQuery);
10901  } else if (typeof exports === 'object' && typeof module === 'object') {
10902    module.exports = jqueryUjsInit;
10903  }
10904})();
10905/*!
10906 * JavaScript Cookie v2.2.0
10907 * https://github.com/js-cookie/js-cookie
10908 *
10909 * Copyright 2006, 2015 Klaus Hartl & Fagner Brack
10910 * Released under the MIT license
10911 */
10912
10913;(function (factory) {
10914	var registeredInModuleLoader = false;
10915	if (typeof define === 'function' && define.amd) {
10916		define(factory);
10917		registeredInModuleLoader = true;
10918	}
10919	if (typeof exports === 'object') {
10920		module.exports = factory();
10921		registeredInModuleLoader = true;
10922	}
10923	if (!registeredInModuleLoader) {
10924		var OldCookies = window.Cookies;
10925		var api = window.Cookies = factory();
10926		api.noConflict = function () {
10927			window.Cookies = OldCookies;
10928			return api;
10929		};
10930	}
10931}(function () {
10932	function extend () {
10933		var i = 0;
10934		var result = {};
10935		for (; i < arguments.length; i++) {
10936			var attributes = arguments[ i ];
10937			for (var key in attributes) {
10938				result[key] = attributes[key];
10939			}
10940		}
10941		return result;
10942	}
10943
10944	function init (converter) {
10945		function api (key, value, attributes) {
10946			var result;
10947			if (typeof document === 'undefined') {
10948				return;
10949			}
10950
10951			// Write
10952
10953			if (arguments.length > 1) {
10954				attributes = extend({
10955					path: '/'
10956				}, api.defaults, attributes);
10957
10958				if (typeof attributes.expires === 'number') {
10959					var expires = new Date();
10960					expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
10961					attributes.expires = expires;
10962				}
10963
10964				// We're using "expires" because "max-age" is not supported by IE
10965				attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '';
10966
10967				try {
10968					result = JSON.stringify(value);
10969					if (/^[\{\[]/.test(result)) {
10970						value = result;
10971					}
10972				} catch (e) {}
10973
10974				if (!converter.write) {
10975					value = encodeURIComponent(String(value))
10976						.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
10977				} else {
10978					value = converter.write(value, key);
10979				}
10980
10981				key = encodeURIComponent(String(key));
10982				key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
10983				key = key.replace(/[\(\)]/g, escape);
10984
10985				var stringifiedAttributes = '';
10986
10987				for (var attributeName in attributes) {
10988					if (!attributes[attributeName]) {
10989						continue;
10990					}
10991					stringifiedAttributes += '; ' + attributeName;
10992					if (attributes[attributeName] === true) {
10993						continue;
10994					}
10995					stringifiedAttributes += '=' + attributes[attributeName];
10996				}
10997				return (document.cookie = key + '=' + value + stringifiedAttributes);
10998			}
10999
11000			// Read
11001
11002			if (!key) {
11003				result = {};
11004			}
11005
11006			// To prevent the for loop in the first place assign an empty array
11007			// in case there are no cookies at all. Also prevents odd result when
11008			// calling "get()"
11009			var cookies = document.cookie ? document.cookie.split('; ') : [];
11010			var rdecode = /(%[0-9A-Z]{2})+/g;
11011			var i = 0;
11012
11013			for (; i < cookies.length; i++) {
11014				var parts = cookies[i].split('=');
11015				var cookie = parts.slice(1).join('=');
11016
11017				if (!this.json && cookie.charAt(0) === '"') {
11018					cookie = cookie.slice(1, -1);
11019				}
11020
11021				try {
11022					var name = parts[0].replace(rdecode, decodeURIComponent);
11023					cookie = converter.read ?
11024						converter.read(cookie, name) : converter(cookie, name) ||
11025						cookie.replace(rdecode, decodeURIComponent);
11026
11027					if (this.json) {
11028						try {
11029							cookie = JSON.parse(cookie);
11030						} catch (e) {}
11031					}
11032
11033					if (key === name) {
11034						result = cookie;
11035						break;
11036					}
11037
11038					if (!key) {
11039						result[name] = cookie;
11040					}
11041				} catch (e) {}
11042			}
11043
11044			return result;
11045		}
11046
11047		api.set = api;
11048		api.get = function (key) {
11049			return api.call(api, key);
11050		};
11051		api.getJSON = function () {
11052			return api.apply({
11053				json: true
11054			}, [].slice.call(arguments));
11055		};
11056		api.defaults = {};
11057
11058		api.remove = function (key, attributes) {
11059			api(key, '', extend(attributes, {
11060				expires: -1
11061			}));
11062		};
11063
11064		api.withConverter = init;
11065
11066		return api;
11067	}
11068
11069	return init(function () {});
11070}));
11071/*
11072  jQuery deparam is an extraction of the deparam method from Ben Alman's jQuery BBQ
11073  http://benalman.com/projects/jquery-bbq-plugin/
11074*/
11075
11076(function ($) {
11077  $.deparam = function (params, coerce) {
11078    var obj = {},
11079        coerce_types = { 'true': !0, 'false': !1, 'null': null };
11080      
11081    // Iterate over all name=value pairs.
11082    $.each(params.replace(/\+/g, ' ').split('&'), function (j,v) {
11083      var param = v.split('='),
11084          key,
11085          val,
11086          cur = obj,
11087          i = 0;
11088
11089      // Try to decode the key parameter safely
11090      try {
11091        key = decodeURIComponent(param[0])
11092      } catch (e) {
11093        if (typeof window !== 'undefined'
11094          && window.Sentry
11095          && window.Sentry.captureException
11096        ) {
11097          window.Sentry.captureException(new Error('URL parameter key decoding failed'), {
11098            tags: {
11099              error_type: 'url_decode_failure',
11100              library: 'jquery_deparam',
11101              param_type: 'key',
11102            },
11103            extra: {
11104              original_key: param[0],
11105              full_param: v,
11106              params_string: params,
11107              error_message: e.message,
11108              stack_trace: e.stack,
11109              timestamp: new Date().toISOString(),
11110              page_url: typeof window !== 'undefined' ? window.location.href : 'unknown',
11111            },
11112            level: 'warning',
11113          })
11114        }
11115        key = param[0]
11116      }
11117
11118      // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
11119      // into its component parts.
11120      var keys = key.split(']['),
11121      keys_last = keys.length - 1;
11122
11123      // If the first keys part contains [ and the last ends with ], then []
11124      // are correctly balanced.
11125      if (/\[/.test(keys[0]) && /\]$/.test(keys[keys_last])) {
11126        // Remove the trailing ] from the last keys part.
11127        keys[keys_last] = keys[keys_last].replace(/\]$/, '');
11128          
11129        // Split first keys part into two parts on the [ and add them back onto
11130        // the beginning of the keys array.
11131        keys = keys.shift().split('[').concat(keys);
11132          
11133        keys_last = keys.length - 1;
11134      } else {
11135        // Basic 'foo' style key.
11136        keys_last = 0;
11137      }
11138        
11139      // Are we dealing with a name=value pair, or just a name?
11140      if (param.length === 2) {
11141        try {
11142          val = decodeURIComponent(param[1])
11143        } catch (e) {
11144          if (typeof window !== 'undefined'
11145            && window.Sentry
11146            && window.Sentry.captureException
11147          ) {
11148            window.Sentry.captureException(new Error('URL parameter value decoding failed'), {
11149              tags: {
11150                error_type: 'url_decode_failure',
11151                library: 'jquery_deparam',
11152                param_type: 'value',
11153              },
11154              extra: {
11155                original_value: param[1],
11156                decoded_key: key,
11157                full_param: v,
11158                params_string: params,
11159                error_message: e.message,
11160                stack_trace: e.stack,
11161                timestamp: new Date().toISOString(),
11162                page_url: typeof window !== 'undefined' ? window.location.href : 'unknown',
11163              },
11164              level: 'warning',
11165            })
11166          }
11167          val = param[1]
11168        }
11169
11170        // Coerce values.
11171        if (coerce) {
11172          val = val && !isNaN(val)              ? +val              // number
11173              : val === 'undefined'             ? undefined         // undefined
11174              : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
11175              : val;                                                // string
11176        }
11177          
11178        if ( keys_last ) {
11179          // Complex key, build deep object structure based on a few rules:
11180          // * The 'cur' pointer starts at the object top-level.
11181          // * [] = array push (n is set to array length), [n] = array if n is 
11182          //   numeric, otherwise object.
11183          // * If at the last keys part, set the value.
11184          // * For each keys part, if the current level is undefined create an
11185          //   object or array based on the type of the next keys part.
11186          // * Move the 'cur' pointer to the next level.
11187          // * Rinse & repeat.
11188          for (; i <= keys_last; i++) {
11189            key = keys[i] === '' ? cur.length : keys[i];
11190            cur = cur[key] = i < keys_last
11191              ? cur[key] || (keys[i+1] && isNaN(keys[i+1]) ? {} : [])
11192              : val;
11193          }
11194            
11195        } else {
11196          // Simple key, even simpler rules, since only scalars and shallow
11197          // arrays are allowed.
11198            
11199          if ($.isArray(obj[key])) {
11200            // val is already an array, so push on the next value.
11201            obj[key].push( val );
11202              
11203          } else if (obj[key] !== undefined) {
11204            // val isn't an array, but since a second value has been specified,
11205            // convert val into an array.
vendor: 16,190 bytes, lines 11206-11779
11206            obj[key] = [obj[key], val];
11207              
11208          } else {
11209            // val is a scalar.
11210            obj[key] = val;
11211          }
11212        }
11213          
11214      } else if (key) {
11215        // No value was defined, so set something meaningful.
11216        obj[key] = coerce
11217          ? undefined
11218          : '';
11219      }
11220    });
11221      
11222    return obj;
11223  };
11224})(jQuery);
11225/*! jQuery UI - v1.11.4+CommonJS - 2015-08-28
11226* http://jqueryui.com
11227* Includes: widget.js
11228* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
11229
11230
11231(function( factory ) {
11232	if ( typeof define === "function" && define.amd ) {
11233
11234		// AMD. Register as an anonymous module.
11235		define([ "jquery" ], factory );
11236
11237	} else if ( typeof exports === "object" ) {
11238
11239		// Node/CommonJS
11240		factory( require( "jquery" ) );
11241
11242	} else {
11243
11244		// Browser globals
11245		factory( jQuery );
11246	}
11247}(function( $ ) {
11248/*!
11249 * jQuery UI Widget 1.11.4
11250 * http://jqueryui.com
11251 *
11252 * Copyright jQuery Foundation and other contributors
11253 * Released under the MIT license.
11254 * http://jquery.org/license
11255 *
11256 * http://api.jqueryui.com/jQuery.widget/
11257 */
11258
11259
11260var widget_uuid = 0,
11261	widget_slice = Array.prototype.slice;
11262
11263$.cleanData = (function( orig ) {
11264	return function( elems ) {
11265		var events, elem, i;
11266		for ( i = 0; (elem = elems[i]) != null; i++ ) {
11267			try {
11268
11269				// Only trigger remove when necessary to save time
11270				events = $._data( elem, "events" );
11271				if ( events && events.remove ) {
11272					$( elem ).triggerHandler( "remove" );
11273				}
11274
11275			// http://bugs.jquery.com/ticket/8235
11276			} catch ( e ) {}
11277		}
11278		orig( elems );
11279	};
11280})( $.cleanData );
11281
11282$.widget = function( name, base, prototype ) {
11283	var fullName, existingConstructor, constructor, basePrototype,
11284		// proxiedPrototype allows the provided prototype to remain unmodified
11285		// so that it can be used as a mixin for multiple widgets (#8876)
11286		proxiedPrototype = {},
11287		namespace = name.split( "." )[ 0 ];
11288
11289	name = name.split( "." )[ 1 ];
11290	fullName = namespace + "-" + name;
11291
11292	if ( !prototype ) {
11293		prototype = base;
11294		base = $.Widget;
11295	}
11296
11297	// create selector for plugin
11298	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
11299		return !!$.data( elem, fullName );
11300	};
11301
11302	$[ namespace ] = $[ namespace ] || {};
11303	existingConstructor = $[ namespace ][ name ];
11304	constructor = $[ namespace ][ name ] = function( options, element ) {
11305		// allow instantiation without "new" keyword
11306		if ( !this._createWidget ) {
11307			return new constructor( options, element );
11308		}
11309
11310		// allow instantiation without initializing for simple inheritance
11311		// must use "new" keyword (the code above always passes args)
11312		if ( arguments.length ) {
11313			this._createWidget( options, element );
11314		}
11315	};
11316	// extend with the existing constructor to carry over any static properties
11317	$.extend( constructor, existingConstructor, {
11318		version: prototype.version,
11319		// copy the object used to create the prototype in case we need to
11320		// redefine the widget later
11321		_proto: $.extend( {}, prototype ),
11322		// track widgets that inherit from this widget in case this widget is
11323		// redefined after a widget inherits from it
11324		_childConstructors: []
11325	});
11326
11327	basePrototype = new base();
11328	// we need to make the options hash a property directly on the new instance
11329	// otherwise we'll modify the options hash on the prototype that we're
11330	// inheriting from
11331	basePrototype.options = $.widget.extend( {}, basePrototype.options );
11332	$.each( prototype, function( prop, value ) {
11333		if ( !$.isFunction( value ) ) {
11334			proxiedPrototype[ prop ] = value;
11335			return;
11336		}
11337		proxiedPrototype[ prop ] = (function() {
11338			var _super = function() {
11339					return base.prototype[ prop ].apply( this, arguments );
11340				},
11341				_superApply = function( args ) {
11342					return base.prototype[ prop ].apply( this, args );
11343				};
11344			return function() {
11345				var __super = this._super,
11346					__superApply = this._superApply,
11347					returnValue;
11348
11349				this._super = _super;
11350				this._superApply = _superApply;
11351
11352				returnValue = value.apply( this, arguments );
11353
11354				this._super = __super;
11355				this._superApply = __superApply;
11356
11357				return returnValue;
11358			};
11359		})();
11360	});
11361	constructor.prototype = $.widget.extend( basePrototype, {
11362		// TODO: remove support for widgetEventPrefix
11363		// always use the name + a colon as the prefix, e.g., draggable:start
11364		// don't prefix for widgets that aren't DOM-based
11365		widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
11366	}, proxiedPrototype, {
11367		constructor: constructor,
11368		namespace: namespace,
11369		widgetName: name,
11370		widgetFullName: fullName
11371	});
11372
11373	// If this widget is being redefined then we need to find all widgets that
11374	// are inheriting from it and redefine all of them so that they inherit from
11375	// the new version of this widget. We're essentially trying to replace one
11376	// level in the prototype chain.
11377	if ( existingConstructor ) {
11378		$.each( existingConstructor._childConstructors, function( i, child ) {
11379			var childPrototype = child.prototype;
11380
11381			// redefine the child widget using the same prototype that was
11382			// originally used, but inherit from the new version of the base
11383			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
11384		});
11385		// remove the list of existing child constructors from the old constructor
11386		// so the old child constructors can be garbage collected
11387		delete existingConstructor._childConstructors;
11388	} else {
11389		base._childConstructors.push( constructor );
11390	}
11391
11392	$.widget.bridge( name, constructor );
11393
11394	return constructor;
11395};
11396
11397$.widget.extend = function( target ) {
11398	var input = widget_slice.call( arguments, 1 ),
11399		inputIndex = 0,
11400		inputLength = input.length,
11401		key,
11402		value;
11403	for ( ; inputIndex < inputLength; inputIndex++ ) {
11404		for ( key in input[ inputIndex ] ) {
11405			value = input[ inputIndex ][ key ];
11406			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
11407				// Clone objects
11408				if ( $.isPlainObject( value ) ) {
11409					target[ key ] = $.isPlainObject( target[ key ] ) ?
11410						$.widget.extend( {}, target[ key ], value ) :
11411						// Don't extend strings, arrays, etc. with objects
11412						$.widget.extend( {}, value );
11413				// Copy everything else by reference
11414				} else {
11415					target[ key ] = value;
11416				}
11417			}
11418		}
11419	}
11420	return target;
11421};
11422
11423$.widget.bridge = function( name, object ) {
11424	var fullName = object.prototype.widgetFullName || name;
11425	$.fn[ name ] = function( options ) {
11426		var isMethodCall = typeof options === "string",
11427			args = widget_slice.call( arguments, 1 ),
11428			returnValue = this;
11429
11430		if ( isMethodCall ) {
11431			this.each(function() {
11432				var methodValue,
11433					instance = $.data( this, fullName );
11434				if ( options === "instance" ) {
11435					returnValue = instance;
11436					return false;
11437				}
11438				if ( !instance ) {
11439					return $.error( "cannot call methods on " + name + " prior to initialization; " +
11440						"attempted to call method '" + options + "'" );
11441				}
11442				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
11443					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
11444				}
11445				methodValue = instance[ options ].apply( instance, args );
11446				if ( methodValue !== instance && methodValue !== undefined ) {
11447					returnValue = methodValue && methodValue.jquery ?
11448						returnValue.pushStack( methodValue.get() ) :
11449						methodValue;
11450					return false;
11451				}
11452			});
11453		} else {
11454
11455			// Allow multiple hashes to be passed on init
11456			if ( args.length ) {
11457				options = $.widget.extend.apply( null, [ options ].concat(args) );
11458			}
11459
11460			this.each(function() {
11461				var instance = $.data( this, fullName );
11462				if ( instance ) {
11463					instance.option( options || {} );
11464					if ( instance._init ) {
11465						instance._init();
11466					}
11467				} else {
11468					$.data( this, fullName, new object( options, this ) );
11469				}
11470			});
11471		}
11472
11473		return returnValue;
11474	};
11475};
11476
11477$.Widget = function( /* options, element */ ) {};
11478$.Widget._childConstructors = [];
11479
11480$.Widget.prototype = {
11481	widgetName: "widget",
11482	widgetEventPrefix: "",
11483	defaultElement: "<div>",
11484	options: {
11485		disabled: false,
11486
11487		// callbacks
11488		create: null
11489	},
11490	_createWidget: function( options, element ) {
11491		element = $( element || this.defaultElement || this )[ 0 ];
11492		this.element = $( element );
11493		this.uuid = widget_uuid++;
11494		this.eventNamespace = "." + this.widgetName + this.uuid;
11495
11496		this.bindings = $();
11497		this.hoverable = $();
11498		this.focusable = $();
11499
11500		if ( element !== this ) {
11501			$.data( element, this.widgetFullName, this );
11502			this._on( true, this.element, {
11503				remove: function( event ) {
11504					if ( event.target === element ) {
11505						this.destroy();
11506					}
11507				}
11508			});
11509			this.document = $( element.style ?
11510				// element within the document
11511				element.ownerDocument :
11512				// element is window or document
11513				element.document || element );
11514			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
11515		}
11516
11517		this.options = $.widget.extend( {},
11518			this.options,
11519			this._getCreateOptions(),
11520			options );
11521
11522		this._create();
11523		this._trigger( "create", null, this._getCreateEventData() );
11524		this._init();
11525	},
11526	_getCreateOptions: $.noop,
11527	_getCreateEventData: $.noop,
11528	_create: $.noop,
11529	_init: $.noop,
11530
11531	destroy: function() {
11532		this._destroy();
11533		// we can probably remove the unbind calls in 2.0
11534		// all event bindings should go through this._on()
11535		this.element
11536			.unbind( this.eventNamespace )
11537			.removeData( this.widgetFullName )
11538			// support: jquery <1.6.3
11539			// http://bugs.jquery.com/ticket/9413
11540			.removeData( $.camelCase( this.widgetFullName ) );
11541		this.widget()
11542			.unbind( this.eventNamespace )
11543			.removeAttr( "aria-disabled" )
11544			.removeClass(
11545				this.widgetFullName + "-disabled " +
11546				"ui-state-disabled" );
11547
11548		// clean up events and states
11549		this.bindings.unbind( this.eventNamespace );
11550		this.hoverable.removeClass( "ui-state-hover" );
11551		this.focusable.removeClass( "ui-state-focus" );
11552	},
11553	_destroy: $.noop,
11554
11555	widget: function() {
11556		return this.element;
11557	},
11558
11559	option: function( key, value ) {
11560		var options = key,
11561			parts,
11562			curOption,
11563			i;
11564
11565		if ( arguments.length === 0 ) {
11566			// don't return a reference to the internal hash
11567			return $.widget.extend( {}, this.options );
11568		}
11569
11570		if ( typeof key === "string" ) {
11571			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
11572			options = {};
11573			parts = key.split( "." );
11574			key = parts.shift();
11575			if ( parts.length ) {
11576				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
11577				for ( i = 0; i < parts.length - 1; i++ ) {
11578					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
11579					curOption = curOption[ parts[ i ] ];
11580				}
11581				key = parts.pop();
11582				if ( arguments.length === 1 ) {
11583					return curOption[ key ] === undefined ? null : curOption[ key ];
11584				}
11585				curOption[ key ] = value;
11586			} else {
11587				if ( arguments.length === 1 ) {
11588					return this.options[ key ] === undefined ? null : this.options[ key ];
11589				}
11590				options[ key ] = value;
11591			}
11592		}
11593
11594		this._setOptions( options );
11595
11596		return this;
11597	},
11598	_setOptions: function( options ) {
11599		var key;
11600
11601		for ( key in options ) {
11602			this._setOption( key, options[ key ] );
11603		}
11604
11605		return this;
11606	},
11607	_setOption: function( key, value ) {
11608		this.options[ key ] = value;
11609
11610		if ( key === "disabled" ) {
11611			this.widget()
11612				.toggleClass( this.widgetFullName + "-disabled", !!value );
11613
11614			// If the widget is becoming disabled, then nothing is interactive
11615			if ( value ) {
11616				this.hoverable.removeClass( "ui-state-hover" );
11617				this.focusable.removeClass( "ui-state-focus" );
11618			}
11619		}
11620
11621		return this;
11622	},
11623
11624	enable: function() {
11625		return this._setOptions({ disabled: false });
11626	},
11627	disable: function() {
11628		return this._setOptions({ disabled: true });
11629	},
11630
11631	_on: function( suppressDisabledCheck, element, handlers ) {
11632		var delegateElement,
11633			instance = this;
11634
11635		// no suppressDisabledCheck flag, shuffle arguments
11636		if ( typeof suppressDisabledCheck !== "boolean" ) {
11637			handlers = element;
11638			element = suppressDisabledCheck;
11639			suppressDisabledCheck = false;
11640		}
11641
11642		// no element argument, shuffle and use this.element
11643		if ( !handlers ) {
11644			handlers = element;
11645			element = this.element;
11646			delegateElement = this.widget();
11647		} else {
11648			element = delegateElement = $( element );
11649			this.bindings = this.bindings.add( element );
11650		}
11651
11652		$.each( handlers, function( event, handler ) {
11653			function handlerProxy() {
11654				// allow widgets to customize the disabled handling
11655				// - disabled as an array instead of boolean
11656				// - disabled class as method for disabling individual parts
11657				if ( !suppressDisabledCheck &&
11658						( instance.options.disabled === true ||
11659							$( this ).hasClass( "ui-state-disabled" ) ) ) {
11660					return;
11661				}
11662				return ( typeof handler === "string" ? instance[ handler ] : handler )
11663					.apply( instance, arguments );
11664			}
11665
11666			// copy the guid so direct unbinding works
11667			if ( typeof handler !== "string" ) {
11668				handlerProxy.guid = handler.guid =
11669					handler.guid || handlerProxy.guid || $.guid++;
11670			}
11671
11672			var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
11673				eventName = match[1] + instance.eventNamespace,
11674				selector = match[2];
11675			if ( selector ) {
11676				delegateElement.delegate( selector, eventName, handlerProxy );
11677			} else {
11678				element.bind( eventName, handlerProxy );
11679			}
11680		});
11681	},
11682
11683	_off: function( element, eventName ) {
11684		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
11685			this.eventNamespace;
11686		element.unbind( eventName ).undelegate( eventName );
11687
11688		// Clear the stack to avoid memory leaks (#10056)
11689		this.bindings = $( this.bindings.not( element ).get() );
11690		this.focusable = $( this.focusable.not( element ).get() );
11691		this.hoverable = $( this.hoverable.not( element ).get() );
11692	},
11693
11694	_delay: function( handler, delay ) {
11695		function handlerProxy() {
11696			return ( typeof handler === "string" ? instance[ handler ] : handler )
11697				.apply( instance, arguments );
11698		}
11699		var instance = this;
11700		return setTimeout( handlerProxy, delay || 0 );
11701	},
11702
11703	_hoverable: function( element ) {
11704		this.hoverable = this.hoverable.add( element );
11705		this._on( element, {
11706			mouseenter: function( event ) {
11707				$( event.currentTarget ).addClass( "ui-state-hover" );
11708			},
11709			mouseleave: function( event ) {
11710				$( event.currentTarget ).removeClass( "ui-state-hover" );
11711			}
11712		});
11713	},
11714
11715	_focusable: function( element ) {
11716		this.focusable = this.focusable.add( element );
11717		this._on( element, {
11718			focusin: function( event ) {
11719				$( event.currentTarget ).addClass( "ui-state-focus" );
11720			},
11721			focusout: function( event ) {
11722				$( event.currentTarget ).removeClass( "ui-state-focus" );
11723			}
11724		});
11725	},
11726
11727	_trigger: function( type, event, data ) {
11728		var prop, orig,
11729			callback = this.options[ type ];
11730
11731		data = data || {};
11732		event = $.Event( event );
11733		event.type = ( type === this.widgetEventPrefix ?
11734			type :
11735			this.widgetEventPrefix + type ).toLowerCase();
11736		// the original event may come from any element
11737		// so we need to reset the target on the new event
11738		event.target = this.element[ 0 ];
11739
11740		// copy original event properties over to the new event
11741		orig = event.originalEvent;
11742		if ( orig ) {
11743			for ( prop in orig ) {
11744				if ( !( prop in event ) ) {
11745					event[ prop ] = orig[ prop ];
11746				}
11747			}
11748		}
11749
11750		this.element.trigger( event, data );
11751		return !( $.isFunction( callback ) &&
11752			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
11753			event.isDefaultPrevented() );
11754	}
11755};
11756
11757$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
11758	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
11759		if ( typeof options === "string" ) {
11760			options = { effect: options };
11761		}
11762		var hasOptions,
11763			effectName = !options ?
11764				method :
11765				options === true || typeof options === "number" ?
11766					defaultEffect :
11767					options.effect || defaultEffect;
11768		options = options || {};
11769		if ( typeof options === "number" ) {
11770			options = { duration: options };
11771		}
11772		hasOptions = !$.isEmptyObject( options );
11773		options.complete = callback;
11774		if ( options.delay ) {
11775			element.delay( options.delay );
11776		}
11777		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
11778			element[ method ]( options );
11779		} else if ( effectName !== method && element[ effectName ] ) {
11780			element[ effectName ]( options.duration, options.easing, callback );
11781		} else {
11782			element.queue(function( next ) {
11783				$( this )[ method ]();
11784				if ( callback ) {
11785					callback.call( element[ 0 ] );
11786				}
11787				next();
11788			});
11789		}
11790	};
11791});
11792
11793var widget = $.widget;
11794
11795
11796
11797}));
11798/*
11799 * jQuery Iframe Transport Plugin
11800 * https://github.com/blueimp/jQuery-File-Upload
11801 *
11802 * Copyright 2011, Sebastian Tschan
11803 * https://blueimp.net
11804 *
11805 * Licensed under the MIT license:
11806 * http://www.opensource.org/licenses/MIT
11807 */
11808
11809/* global define, require, window, document */
11810
11811
11812;(function (factory) {
11813    'use strict';
11814    if (typeof define === 'function' && define.amd) {
11815        // Register as an anonymous AMD module:
11816        define(['jquery'], factory);
11817    } else if (typeof exports === 'object') {
11818        // Node/CommonJS:
11819        factory(require('jquery'));
11820    } else {
11821        // Browser globals:
11822        factory(window.jQuery);
11823    }
11824}(function ($) {
11825    'use strict';
11826
11827    // Helper variable to create unique names for the transport iframes:
11828    var counter = 0;
11829
11830    // The iframe transport accepts four additional options:
11831    // options.fileInput: a jQuery collection of file input fields
11832    // options.paramName: the parameter name for the file form data,
11833    //  overrides the name property of the file input field(s),
11834    //  can be a string or an array of strings.
11835    // options.formData: an array of objects with name and value properties,
11836    //  equivalent to the return data of .serializeArray(), e.g.:
11837    //  [{name: 'a', value: 1}, {name: 'b', value: 2}]
11838    // options.initialIframeSrc: the URL of the initial iframe src,
11839    //  by default set to "javascript:false;"
11840    $.ajaxTransport('iframe', function (options) {
11841        if (options.async) {
11842            // javascript:false as initial iframe src
11843            // prevents warning popups on HTTPS in IE6:
11844            /*jshint scripturl: true */
11845            var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
11846            /*jshint scripturl: false */
11847                form,
11848                iframe,
11849                addParamChar;
11850            return {
11851                send: function (_, completeCallback) {
11852                    form = $('<form style="display:none;"></form>');
11853                    form.attr('accept-charset', options.formAcceptCharset);
11854                    addParamChar = /\?/.test(options.url) ? '&' : '?';
11855                    // XDomainRequest only supports GET and POST:
11856                    if (options.type === 'DELETE') {
11857                        options.url = options.url + addParamChar + '_method=DELETE';
11858                        options.type = 'POST';
11859                    } else if (options.type === 'PUT') {
11860                        options.url = options.url + addParamChar + '_method=PUT';
11861                        options.type = 'POST';
11862                    } else if (options.type === 'PATCH') {
11863                        options.url = options.url + addParamChar + '_method=PATCH';
11864                        options.type = 'POST';
11865                    }
11866                    // IE versions below IE8 cannot set the name property of
11867                    // elements that have already been added to the DOM,
11868                    // so we set the name along with the iframe HTML markup:
11869                    counter += 1;
11870                    iframe = $(
11871                        '<iframe src="' + initialIframeSrc +
11872                            '" name="iframe-transport-' + counter + '"></iframe>'
11873                    ).bind('load', function () {
11874                        var fileInputClones,
11875                            paramNames = $.isArray(options.paramName) ?
11876                                    options.paramName : [options.paramName];
11877                        iframe
11878                            .unbind('load')
11879                            .bind('load', function () {
11880                                var response;
11881                                // Wrap in a try/catch block to catch exceptions thrown
11882                                // when trying to access cross-domain iframe contents:
11883                                try {
11884                                    response = iframe.contents();
11885                                    // Google Chrome and Firefox do not throw an
11886                                    // exception when calling iframe.contents() on
11887                                    // cross-domain requests, so we unify the response:
11888                                    if (!response.length || !response[0].firstChild) {
11889                                        throw new Error();
11890                                    }
11891                                } catch (e) {
11892                                    response = undefined;
11893                                }
11894                                // The complete callback returns the
11895                                // iframe content document as response object:
11896                                completeCallback(
11897                                    200,
11898                                    'success',
11899                                    {'iframe': response}
11900                                );
11901                                // Fix for IE endless progress bar activity bug
11902                                // (happens on form submits to iframe targets):
11903                                $('<iframe src="' + initialIframeSrc + '"></iframe>')
11904                                    .appendTo(form);
11905                                window.setTimeout(function () {
11906                                    // Removing the form in a setTimeout call
11907                                    // allows Chrome's developer tools to display
11908                                    // the response result
11909                                    form.remove();
11910                                }, 0);
11911                            });
11912                        form
11913                            .prop('target', iframe.prop('name'))
11914                            .prop('action', options.url)
11915                            .prop('method', options.type);
11916                        if (options.formData) {
11917                            $.each(options.formData, function (index, field) {
11918                                $('<input type="hidden"/>')
11919                                    .prop('name', field.name)
11920                                    .val(field.value)
11921                                    .appendTo(form);
11922                            });
11923                        }
11924                        if (options.fileInput && options.fileInput.length &&
11925                                options.type === 'POST') {
11926                            fileInputClones = options.fileInput.clone();
11927                            // Insert a clone for each file input field:
11928                            options.fileInput.after(function (index) {
11929                                return fileInputClones[index];
11930                            });
11931                            if (options.paramName) {
11932                                options.fileInput.each(function (index) {
11933                                    $(this).prop(
11934                                        'name',
11935                                        paramNames[index] || options.paramName
11936                                    );
11937                                });
11938                            }
11939                            // Appending the file input fields to the hidden form
11940                            // removes them from their original location:
11941                            form
11942                                .append(options.fileInput)
11943                                .prop('enctype', 'multipart/form-data')
11944                                // enctype must be set as encoding for IE:
11945                                .prop('encoding', 'multipart/form-data');
11946                            // Remove the HTML5 form attribute from the input(s):
11947                            options.fileInput.removeAttr('form');
11948                        }
11949                        form.submit();
11950                        // Insert the file input fields at their original location
11951                        // by replacing the clones with the originals:
11952                        if (fileInputClones && fileInputClones.length) {
11953                            options.fileInput.each(function (index, input) {
11954                                var clone = $(fileInputClones[index]);
11955                                // Restore the original name and form properties:
11956                                $(input)
11957                                    .prop('name', clone.prop('name'))
11958                                    .attr('form', clone.attr('form'));
11959                                clone.replaceWith(input);
11960                            });
11961                        }
11962                    });
11963                    form.append(iframe).appendTo(document.body);
11964                },
11965                abort: function () {
11966                    if (iframe) {
11967                        // javascript:false as iframe src aborts the request
11968                        // and prevents warning popups on HTTPS in IE6.
11969                        // concat is used to avoid the "Script URL" JSLint error:
11970                        iframe
11971                            .unbind('load')
11972                            .prop('src', initialIframeSrc);
11973                    }
11974                    if (form) {
11975                        form.remove();
11976                    }
11977                }
11978            };
11979        }
11980    });
11981
11982    // The iframe transport returns the iframe content document as response.
11983    // The following adds converters from iframe to text, json, html, xml
11984    // and script.
11985    // Please note that the Content-Type for JSON responses has to be text/plain
11986    // or text/html, if the browser doesn't include application/json in the
11987    // Accept header, else IE will show a download dialog.
11988    // The Content-Type for XML responses on the other hand has to be al
11988ways
11989    // application/xml or text/xml, so IE properly parses the XML response.
11990    // See also
11991    // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
11992    $.ajaxSetup({
11993        converters: {
11994            'iframe text': function (iframe) {
11995                return iframe && $(iframe[0].body).text();
11996            },
11997            'iframe json': function (iframe) {
11998                return iframe && $.parseJSON($(iframe[0].body).text());
11999            },
12000            'iframe html': function (iframe) {
12001                return iframe && $(iframe[0].body).html();
12002            },
12003            'iframe xml': function (iframe) {
12004                var xmlDoc = iframe && iframe[0];
12005                return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
12006                        $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
12007                            $(xmlDoc.body).html());
12008            },
12009            'iframe script': function (iframe) {
12010                return iframe && $.globalEval($(iframe[0].body).text());
12011            }
12012        }
12013    });
12014
12015}));
12016/*
12017 * jQuery File Upload Plugin
12018 * https://github.com/blueimp/jQuery-File-Upload
12019 *
12020 * Copyright 2010, Sebastian Tschan
12021 * https://blueimp.net
12022 *
12023 * Licensed under the MIT license:
12024 * http://www.opensource.org/licenses/MIT
12025 */
12026
12027/* jshint nomen:false */
12028/* global define, require, window, document, location, Blob, FormData */
12029
12030
12031;(function (factory) {
12032    'use strict';
12033    if (typeof define === 'function' && define.amd) {
12034        // Register as an anonymous AMD module:
12035        define([
12036            'jquery',
12037            'jquery.ui.widget'
12038        ], factory);
12039    } else if (typeof exports === 'object') {
12040        // Node/CommonJS:
12041        factory(
12042            require('jquery'),
12043            require('./vendor/jquery.ui.widget')
12044        );
12045    } else {
12046        // Browser globals:
12047        factory(window.jQuery);
12048    }
12049}(function ($) {
12050    'use strict';
12051
12052    // Detect file input support, based on
12053    // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
12054    $.support.fileInput = !(new RegExp(
12055        // Handle devices which give false positives for the feature detection:
12056        '(Android (1\\.[0156]|2\\.[01]))' +
12057            '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
12058            '|(w(eb)?OSBrowser)|(webOS)' +
12059            '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
12060    ).test(window.navigator.userAgent) ||
12061        // Feature detection for all other devices:
12062        $('<input type="file">').prop('disabled'));
12063
12064    // The FileReader API is not actually used, but works as feature detection,
12065    // as some Safari versions (5?) support XHR file uploads via the FormData API,
12066    // but not non-multipart XHR file uploads.
12067    // window.XMLHttpRequestUpload is not available on IE10, so we check for
12068    // window.ProgressEvent instead to detect XHR2 file upload capability:
12069    $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
12070    $.support.xhrFormDataFileUpload = !!window.FormData;
12071
12072    // Detect support for Blob slicing (required for chunked uploads):
12073    $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
12074        Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
12075
12076    // Helper function to create drag handlers for dragover/dragenter/dragleave:
12077    function getDragHandler(type) {
12078        var isDragOver = type === 'dragover';
12079        return function (e) {
12080            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
12081            var dataTransfer = e.dataTransfer;
12082            if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
12083                    this._trigger(
12084                        type,
12085                        $.Event(type, {delegatedEvent: e})
12086                    ) !== false) {
12087                e.preventDefault();
12088                if (isDragOver) {
12089                    dataTransfer.dropEffect = 'copy';
12090                }
12091            }
12092        };
12093    }
12094
12095    // The fileupload widget listens for change events on file input fields defined
12096    // via fileInput setting and paste or drop events of the given dropZone.
12097    // In addition to the default jQuery Widget methods, the fileupload widget
12098    // exposes the "add" and "send" methods, to add or directly send files using
12099    // the fileupload API.
12100    // By default, files added via file input selection, paste, drag & drop or
12101    // "add" method are uploaded immediately, but it is possible to override
12102    // the "add" callback option to queue file uploads.
12103    $.widget('blueimp.fileupload', {
12104
12105        options: {
12106            // The drop target element(s), by the default the complete document.
12107            // Set to null to disable drag & drop support:
12108            dropZone: $(document),
12109            // The paste target element(s), by the default undefined.
12110            // Set to a DOM node or jQuery object to enable file pasting:
12111            pasteZone: undefined,
12112            // The file input field(s), that are listened to for change events.
12113            // If undefined, it is set to the file input fields inside
12114            // of the widget element on plugin initialization.
12115            // Set to null to disable the change listener.
12116            fileInput: undefined,
12117            // By default, the file input field is replaced with a clone after
12118            // each input field change event. This is required for iframe transport
12119            // queues and allows change events to be fired for the same file
12120            // selection, but can be disabled by setting the following option to false:
12121            replaceFileInput: true,
12122            // The parameter name for the file form data (the request argument name).
12123            // If undefined or empty, the name property of the file input field is
12124            // used, or "files[]" if the file input name property is also empty,
12125            // can be a string or an array of strings:
12126            paramName: undefined,
12127            // By default, each file of a selection is uploaded using an individual
12128            // request for XHR type uploads. Set to false to upload file
12129            // selections in one request each:
12130            singleFileUploads: true,
12131            // To limit the number of files uploaded with one XHR request,
12132            // set the following option to an integer greater than 0:
12133            limitMultiFileUploads: undefined,
12134            // The following option limits the number of files uploaded with one
12135            // XHR request to keep the request size under or equal to the defined
12136            // limit in bytes:
12137            limitMultiFileUploadSize: undefined,
12138            // Multipart file uploads add a number of bytes to each uploaded file,
12139            // therefore the following option adds an overhead for each file used
12140            // in the limitMultiFileUploadSize configuration:
12141            limitMultiFileUploadSizeOverhead: 512,
12142            // Set the following option to true to issue all file upload requests
12143            // in a sequential order:
12144            sequentialUploads: false,
12145            // To limit the number of concurrent uploads,
12146            // set the following option to an integer greater than 0:
12147            limitConcurrentUploads: undefined,
12148            // Set the following option to true to force iframe transport uploads:
12149            forceIframeTransport: false,
12150            // Set the following option to the location of a redirect url on the
12151            // origin server, for cross-domain iframe transport uploads:
12152            redirect: undefined,
12153            // The parameter name for the redirect url, sent as part of the form
12154            // data and set to 'redirect' if this option is empty:
12155            redirectParamName: undefined,
12156            // Set the following option to the location of a postMessage window,
12157            // to enable postMessage transport uploads:
12158            postMessage: undefined,
12159            // By default, XHR file uploads are sent as multipart/form-data.
12160            // The iframe transport is always using multipart/form-data.
12161            // Set to false to enable non-multipart XHR uploads:
12162            multipart: true,
12163            // To upload large files in smaller chunks, set the following option
12164            // to a preferred maximum chunk size. If set to 0, null or undefined,
12165            // or the browser does not support the required Blob API, files will
12166            // be uploaded as a whole.
12167            maxChunkSize: undefined,
12168            // When a non-multipart upload or a chunked multipart upload has been
12169            // aborted, this option can be used to resume the upload by setting
12170            // it to the size of the already uploaded bytes. This option is most
12171            // useful when modifying the options object inside of the "add" or
12172            // "send" callbacks, as the options are cloned for each file upload.
12173            uploadedBytes: undefined,
12174            // By default, failed (abort or error) file uploads are removed from the
12175            // global progress calculation. Set the following option to fal
12175se to
12176            // prevent recalculating the global progress data:
12177            recalculateProgress: true,
12178            // Interval in milliseconds to calculate and trigger progress events:
12179            progressInterval: 100,
12180            // Interval in milliseconds to calculate progress bitrate:
12181            bitrateInterval: 500,
12182            // By default, uploads are started automatically when adding files:
12183            autoUpload: true,
12184
12185            // Error and info messages:
12186            messages: {
12187                uploadedBytes: 'Uploaded bytes exceed file size'
12188            },
12189
12190            // Translation function, gets the message key to be translated
12191            // and an object with context specific data as arguments:
12192            i18n: function (message, context) {
12193                message = this.messages[message] || message.toString();
12194                if (context) {
12195                    $.each(context, function (key, value) {
12196                        message = message.replace('{' + key + '}', value);
12197                    });
12198                }
12199                return message;
12200            },
12201
12202            // Additional form data to be sent along with the file uploads can be set
12203            // using this option, which accepts an array of objects with name and
12204            // value properties, a function returning such an array, a FormData
12205            // object (for XHR file uploads), or a simple object.
12206            // The form of the first fileInput is given as parameter to the function:
12207            formData: function (form) {
12208                return form.serializeArray();
12209            },
12210
12211            // The add callback is invoked as soon as files are added to the fileupload
12212            // widget (via file input selection, drag & drop, paste or add API call).
12213            // If the singleFileUploads option is enabled, this callback will be
12214            // called once for each file in the selection for XHR file uploads, else
12215            // once for each file selection.
12216            //
12217            // The upload starts when the submit method is invoked on the data parameter.
12218            // The data object contains a files property holding the added files
12219            // and allows you to override plugin options as well as define ajax settings.
12220            //
12221            // Listeners for this callback can also be bound the following way:
12222            // .bind('fileuploadadd', func);
12223            //
12224            // data.submit() returns a Promise object and allows to attach additional
12225            // handlers using jQuery's Deferred callbacks:
12226            // data.submit().done(func).fail(func).always(func);
12227            add: function (e, data) {
12228                if (e.isDefaultPrevented()) {
12229                    return false;
12230                }
12231                if (data.autoUpload || (data.autoUpload !== false &&
12232                        $(this).fileupload('option', 'autoUpload'))) {
12233                    data.process().done(function () {
12234                        data.submit();
12235                    });
12236                }
12237            },
12238
12239            // Other callbacks:
12240
12241            // Callback for the submit event of each file upload:
12242            // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
12243
12244            // Callback for the start of each file upload request:
12245            // send: function (e, data) {}, // .bind('fileuploadsend', func);
12246
12247            // Callback for successful uploads:
12248            // done: function (e, data) {}, // .bind('fileuploaddone', func);
12249
12250            // Callback for failed (abort or error) uploads:
12251            // fail: function (e, data) {}, // .bind('fileuploadfail', func);
12252
12253            // Callback for completed (success, abort or error) requests:
12254            // always: function (e, data) {}, // .bind('fileuploadalways', func);
12255
12256            // Callback for upload progress events:
12257            // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
12258
12259            // Callback for global upload progress events:
12260            // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
12261
12262            // Callback for uploads start, equivalent to the global ajaxStart event:
12263            // start: function (e) {}, // .bind('fileuploadstart', func);
12264
12265            // Callback for uploads stop, equivalent to the global ajaxStop event:
12266            // stop: function (e) {}, // .bind('fileuploadstop', func);
12267
12268            // Callback for change events of the fileInput(s):
12269            // change: function (e, data) {}, // .bind('fileuploadchange', func);
12270
12271            // Callback for paste events to the pasteZone(s):
12272            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
12273
12274            // Callback for drop events of the dropZone(s):
12275            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
12276
12277            // Callback for dragover events of the dropZone(s):
12278            // dragover: function (e) {}, // .bind('fileuploaddragover', func);
12279
12280            // Callback for the start of each chunk upload request:
12281            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
12282
12283            // Callback for successful chunk uploads:
12284            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
12285
12286            // Callback for failed (abort or error) chunk uploads:
12287            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
12288
12289            // Callback for completed (success, abort or error) chunk upload requests:
12290            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
12291
12292            // The plugin options are used as settings object for the ajax calls.
12293            // The following are jQuery ajax settings required for the file uploads:
12294            processData: false,
12295            contentType: false,
12296            cache: false,
12297            timeout: 0
12298        },
12299
12300        // A list of options that require reinitializing event listeners and/or
12301        // special initialization code:
12302        _specialOptions: [
12303            'fileInput',
12304            'dropZone',
12305            'pasteZone',
12306            'multipart',
12307            'forceIframeTransport'
12308        ],
12309
12310        _blobSlice: $.support.blobSlice && function () {
12311            var slice = this.slice || this.webkitSlice || this.mozSlice;
12312            return slice.apply(this, arguments);
12313        },
12314
12315        _BitrateTimer: function () {
12316            this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
12317            this.loaded = 0;
12318            this.bitrate = 0;
12319            this.getBitrate = function (now, loaded, interval) {
12320                var timeDiff = now - this.timestamp;
12321                if (!this.bitrate || !interval || timeDiff > interval) {
12322                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
12323                    this.loaded = loaded;
12324                    this.timestamp = now;
12325                }
12326                return this.bitrate;
12327            };
12328        },
12329
12330        _isXHRUpload: function (options) {
12331            return !options.forceIframeTransport &&
12332                ((!options.multipart && $.support.xhrFileUpload) ||
12333                $.support.xhrFormDataFileUpload);
12334        },
12335
12336        _getFormData: function (options) {
12337            var formData;
12338            if ($.type(options.formData) === 'function') {
12339                return options.formData(options.form);
12340            }
12341            if ($.isArray(options.formData)) {
12342                return options.formData;
12343            }
12344            if ($.type(options.formData) === 'object') {
12345                formData = [];
12346                $.each(options.formData, function (name, value) {
12347                    formData.push({name: name, value: value});
12348                });
12349                return formData;
12350            }
12351            return [];
12352        },
12353
12354        _getTotal: function (files) {
12355            var total = 0;
12356            $.each(files, function (index, file) {
12357                total += file.size || 1;
12358            });
12359            return total;
12360        },
12361
12362        _initProgressObject: function (obj) {
12363            var progress = {
12364                loaded: 0,
12365                total: 0,
12366                bitrate: 0
12367            };
12368            if (obj._progress) {
12369                $.extend(obj._progress, progress);
12370            } else {
12371                obj._progress = progress;
12372            }
12373        },
12374
12375        _initResponseObject: function (obj) {
12376            var prop;
12377            if (obj._response) {
12378                for (prop in obj._response) {
12379                    if (obj._response.hasOwnProperty(prop)) {
12380                        delete obj._response[prop];
12381                    }
12382                }
12383            } else {
12384                obj._response = {};
12385            }
12386        },
12387
12388        _onProgress: function (e, data) {
12389            if (e.lengthComputable) {
12390                var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
12391                    loaded;
12392                if (data._time && data.progressInterval &&
12393                        (now - data._time < data.progressInterval) &&
12394                        e.loaded !== e.total) {
12395                    return;
12396                }
12397                data._time = now;
12398                loaded = Math.floor(
12399                    e.loaded / e.total * (data.chunkSize || data._progress.total)
12400                ) + (data.uploadedBytes || 0);
12401                // Add the difference from the previously loaded state
12402                // to the global loaded counter:
12403                this._progress.loaded += (loaded - data._progress.loaded);
12404                this._progress.bitrate = this._bitrateTimer.getBitrate(
12405                    now,
12406                    this._progress.loaded,
12407                    data.bitrateInterval
12408                );
12409                data._progress.loaded = data.loaded = loaded;
12410                data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
12411                    now,
12412                    loaded,
12413                    data.bitrateInterval
12414                );
12415                // Trigger a custom progress event with a total data property set
12416                // to the file size(s) of the current upload and a loaded data
12417                // property calculated accordingly:
12418                this._trigger(
12419                    'progress',
12420                    $.Event('progress', {delegatedEvent: e}),
12421                    data
12422                );
12423                // Trigger a global progress event for all current file uploads,
12424                // including ajax calls queued for sequential file uploads:
12425                this._trigger(
12426                    'progressall',
12427                    $.Event('progressall', {delegatedEvent: e}),
12428                    this._progress
12429                );
12430            }
12431        },
12432
12433        _initProgressListener: function (options) {
12434            var that = this,
12435                xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
12436            // Accesss to the native XHR object is required to add event listeners
12437            // for the upload progress event:
12438            if (xhr.upload) {
12439                $(xhr.upload).bind('progress', function (e) {
12440                    var oe = e.originalEvent;
12441                    // Make sure the progress event properties get copied over:
12442                    e.lengthComputable = oe.lengthComputable;
12443                    e.loaded = oe.loaded;
12444                    e.total = oe.total;
12445                    that._onProgress(e, options);
12446                });
12447                options.xhr = function () {
12448                    return xhr;
12449                };
12450            }
12451        },
12452
12453        _isInstanceOf: function (type, obj) {
12454            // Cross-frame instanceof check
12455            return Object.prototype.toString.call(obj) === '[object ' + type + ']';
12456        },
12457
12458        _initXHRData: function (options) {
12459            var that = this,
12460                formData,
12461                file = options.files[0],
12462                // Ignore non-multipart setting if not supported:
12463                multipart = options.multipart || !$.support.xhrFileUpload,
12464                paramName = $.type(options.paramName) === 'array' ?
12465                    options.paramName[0] : options.paramName;
12466            options.headers = $.extend({}, options.headers);
12467            if (options.contentRange) {
12468                options.headers['Content-Range'] = options.contentRange;
12469            }
12470            if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
12471                options.headers['Content-Disposition'] = 'attachment; filename="' +
12472                    encodeURI(file.name) + '"';
12473            }
12474            if (!multipart) {
12475                options.contentType = file.type || 'application/octet-stream';
12476                options.data = options.blob || file;
12477            } else if ($.support.xhrFormDataFileUpload) {
12478                if (options.postMessage) {
12479                    // window.postMessage does not allow sending FormData
12480                    // objects, so we just add the File/Blob objects to
12481                    // the formData array and let the postMessage window
12482                    // create the FormData object out of this array:
12483                    formData = this._getFormData(options);
12484                    if (options.blob) {
12485                        formData.push({
12486                            name: paramName,
12487                            value: options.blob
12488                        });
12489                    } else {
12490                        $.each(options.files, function (index, file) {
12491                            formData.push({
12492                                name: ($.type(options.paramName) === 'array' &&
12493                                    options.paramName[index]) || paramName,
12494                                value: file
12495                            });
12496                        });
12497                    }
12498                } else {
12499                    if (that._isInstanceOf('FormData', options.formData)) {
12500                        formData = options.formData;
12501                    } else {
12502                        formData = new FormData();
12503                        $.each(this._getFormData(options), function (index, field) {
12504                            formData.append(field.name, field.value);
12505                        });
12506                    }
12507                    if (options.blob) {
12508                        formData.append(paramName, options.blob, file.name);
12509                    } else {
12510                        $.each(options.files, function (index, file) {
12511                            // This check allows the tests to run with
12512                            // dummy objects:
12513                            if (that._isInstanceOf('File', file) ||
12514                                    that._isInstanceOf('Blob', file)) {
12515                                formData.append(
12516                                    ($.type(options.paramName) === 'array' &&
12517                                        options.paramName[index]) || paramName,
12518                                    file,
12519                                    file.uploadName || file.name
12520                                );
12521                            }
12522                        });
12523                    }
12524                }
12525                options.data = formData;
12526            }
12527            // Blob reference is not needed anymore, free memory:
12528            options.blob = null;
12529        },
12530
12531        _initIframeSettings: function (options) {
12532            var targetHost = $('<a></a>').prop('href', options.url).prop('host');
12533            // Setting the dataType to iframe enables the iframe transport:
12534            options.dataType = 'iframe ' + (options.dataType || '');
12535            // The iframe transport accepts a serialized array as form data:
12536            options.formData = this._getFormData(options);
12537            // Add redirect url to form data on cross-domain uploads:
12538            if (options.redirect && targetHost && targetHost !== location.host) {
12539                options.formData.push({
12540                    name: options.redirectParamName || 'redirect',
12541                    value: options.redirect
12542                });
12543            }
12544        },
12545
12546        _initDataSettings: function (options) {
12547            if (this._isXHRUpload(options)) {
12548                if (!this._chunkedUpload(options, true)) {
12549                    if (!options.data) {
12550                        this._initXHRData(options);
12551                    }
12552                    this._initProgressListener(options);
12553                }
12554                if (options.postMessage) {
12555                    // Setting the dataType to postmessage enables the
12556                    // postMessage transport:
12557                    options.dataType = 'postmessage ' + (options.dataType || '');
12558                }
12559            } else {
12560                this._initIframeSettings(options);
12561            }
12562        },
12563
12564        _getParamName: function (options) {
12565            var fileInput = $(options.fileInput),
12566                paramName = options.paramName;
12567            if (!paramName) {
12568                paramName = [];
12569                fileInput.each(function () {
12570                    var input = $(this),
12571                        name = input.prop('name') || 'files[]',
12572                        i = (input.prop('files') || [1]).length;
12573                    while (i) {
12574                        paramName.push(name);
12575                        i -= 1;
12576                    }
12577                });
12578                if (!paramName.length) {
12579                    paramName = [fileInput.prop('name') || 'files[]'];
12580                }
12581            } else if (!$.isArray(paramName)) {
12582                paramName = [paramName];
12583            }
12584            return paramName;
12585        },
12586
12587        _initFormSettings: function (options) {
12588            // Retrieve missing options from the input field and the
12589            // associated form, if available:
12590            if (!options.form || !options.form.length) {
12591                options.form = $(options.fileInput.prop('form'));
12592                // If the given file input doesn't have an associated form,
12593                // use the default widget file input's form:
12594                if (!options.form.length) {
12595                    options.form = $(this.options.fileInput.prop('form'));
12596                }
12597            }
12598            options.paramName = this._getParamName(options);
12599            if (!options.url) {
12600                options.url = options.form.prop('action') || location.href;
12601            }
12602            // The HTTP request method must be "POST" or "PUT":
12603            options.type = (options.type ||
12604                ($.type(options.form.prop('method')) === 'string' &&
12605                    options.form.prop('method')) || ''
12606                ).toUpperCase();
12607            if (options.type !== 'POST' && options.type !== 'PUT' &&
12608                    options.type !== 'PATCH') {
12609                options.type = 'POST';
12610            }
12611            if (!options.formAcceptCharset) {
12612                options.formAcceptCharset = options.form.attr('accept-charset');
12613            }
12614        },
12615
12616        _getAJAXSettings: function (data) {
12617            var options = $.extend({}, this.options, data);
12618            this._initFormSettings(options);
12619            this._initDataSettings(options);
12620            return options;
12621        },
12622
12623        // jQuery 1.6 doesn't provide .state(),
12624        // while jQuery 1.8+ removed .isRejected() and .isResolved():
12625        _getDeferredState: function (deferred) {
12626            if (deferred.state) {
12627                return deferred.state();
12628            }
12629            if (deferred.isResolved()) {
12630                return 'resolved';
12631            }
12632            if (deferred.isRejected()) {
12633                return 'rejected';
12634            }
12635            return 'pending';
12636        },
12637
12638        // Maps jqXHR callbacks to the equivalent
12639        // methods of the given Promise object:
12640        _enhancePromise: function (promise) {
12641            promise.success = promise.done;
12642            promise.error = promise.fail;
12643            promise.complete = promise.always;
12644            return promise;
12645        },
12646
12647        // Creates and returns a Promise object enhanced with
12648        // the jqXHR methods abort, success, error and complete:
12649        _getXHRPromise: function (resolveOrReject, context, args
12649) {
12650            var dfd = $.Deferred(),
12651                promise = dfd.promise();
12652            context = context || this.options.context || promise;
12653            if (resolveOrReject === true) {
12654                dfd.resolveWith(context, args);
12655            } else if (resolveOrReject === false) {
12656                dfd.rejectWith(context, args);
12657            }
12658            promise.abort = dfd.promise;
12659            return this._enhancePromise(promise);
12660        },
12661
12662        // Adds convenience methods to the data callback argument:
12663        _addConvenienceMethods: function (e, data) {
12664            var that = this,
12665                getPromise = function (args) {
12666                    return $.Deferred().resolveWith(that, args).promise();
12667                };
12668            data.process = function (resolveFunc, rejectFunc) {
12669                if (resolveFunc || rejectFunc) {
12670                    data._processQueue = this._processQueue =
12671                        (this._processQueue || getPromise([this])).then(
12672                            function () {
12673                                if (data.errorThrown) {
12674                                    return $.Deferred()
12675                                        .rejectWith(that, [data]).promise();
12676                                }
12677                                return getPromise(arguments);
12678                            }
12679                        ).then(resolveFunc, rejectFunc);
12680                }
12681                return this._processQueue || getPromise([this]);
12682            };
12683            data.submit = function () {
12684                if (this.state() !== 'pending') {
12685                    data.jqXHR = this.jqXHR =
12686                        (that._trigger(
12687                            'submit',
12688                            $.Event('submit', {delegatedEvent: e}),
12689                            this
12690                        ) !== false) && that._onSend(e, this);
12691                }
12692                return this.jqXHR || that._getXHRPromise();
12693            };
12694            data.abort = function () {
12695                if (this.jqXHR) {
12696                    return this.jqXHR.abort();
12697                }
12698                this.errorThrown = 'abort';
12699                that._trigger('fail', null, this);
12700                return that._getXHRPromise(false);
12701            };
12702            data.state = function () {
12703                if (this.jqXHR) {
12704                    return that._getDeferredState(this.jqXHR);
12705                }
12706                if (this._processQueue) {
12707                    return that._getDeferredState(this._processQueue);
12708                }
12709            };
12710            data.processing = function () {
12711                return !this.jqXHR && this._processQueue && that
12712                    ._getDeferredState(this._processQueue) === 'pending';
12713            };
12714            data.progress = function () {
12715                return this._progress;
12716            };
12717            data.response = function () {
12718                return this._response;
12719            };
12720        },
12721
12722        // Parses the Range header from the server response
12723        // and returns the uploaded bytes:
12724        _getUploadedBytes: function (jqXHR) {
12725            var range = jqXHR.getResponseHeader('Range'),
12726                parts = range && range.split('-'),
12727                upperBytesPos = parts && parts.length > 1 &&
12728                    parseInt(parts[1], 10);
12729            return upperBytesPos && upperBytesPos + 1;
12730        },
12731
12732        // Uploads a file in multiple, sequential requests
12733        // by splitting the file up in multiple blob chunks.
12734        // If the second parameter is true, only tests if the file
12735        // should be uploaded in chunks, but does not invoke any
12736        // upload requests:
12737        _chunkedUpload: function (options, testOnly) {
12738            options.uploadedBytes = options.uploadedBytes || 0;
12739            var that = this,
12740                file = options.files[0],
12741                fs = file.size,
12742                ub = options.uploadedBytes,
12743                mcs = options.maxChunkSize || fs,
12744                slice = this._blobSlice,
12745                dfd = $.Deferred(),
12746                promise = dfd.promise(),
12747                jqXHR,
12748                upload;
12749            if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
12750                    options.data) {
12751                return false;
12752            }
12753            if (testOnly) {
12754                return true;
12755            }
12756            if (ub >= fs) {
12757                file.error = options.i18n('uploadedBytes');
12758                return this._getXHRPromise(
12759                    false,
12760                    options.context,
12761                    [null, 'error', file.error]
12762                );
12763            }
12764            // The chunk upload method:
12765            upload = function () {
12766                // Clone the options object for each chunk upload:
12767                var o = $.extend({}, options),
12768                    currentLoaded = o._progress.loaded;
12769                o.blob = slice.call(
12770                    file,
12771                    ub,
12772                    ub + mcs,
12773                    file.type
12774                );
12775                // Store the current chunk size, as the blob itself
12776                // will be dereferenced after data processing:
12777                o.chunkSize = o.blob.size;
12778                // Expose the chunk bytes position range:
12779                o.contentRange = 'bytes ' + ub + '-' +
12780                    (ub + o.chunkSize - 1) + '/' + fs;
12781                // Process the upload data (the blob and potential form data):
12782                that._initXHRData(o);
12783                // Add progress listeners for this chunk upload:
12784                that._initProgressListener(o);
12785                jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
12786                        that._getXHRPromise(false, o.context))
12787                    .done(function (result, textStatus, jqXHR) {
12788                        ub = that._getUploadedBytes(jqXHR) ||
12789                            (ub + o.chunkSize);
12790                        // Create a progress event if no final progress event
12791                        // with loaded equaling total has been triggered
12792                        // for this chunk:
12793                        if (currentLoaded + o.chunkSize - o._progress.loaded) {
12794                            that._onProgress($.Event('progress', {
12795                                lengthComputable: true,
12796                                loaded: ub - o.uploadedBytes,
12797                                total: ub - o.uploadedBytes
12798                            }), o);
12799                        }
12800                        options.uploadedBytes = o.uploadedBytes = ub;
12801                        o.result = result;
12802                        o.textStatus = textStatus;
12803                        o.jqXHR = jqXHR;
12804                        that._trigger('chunkdone', null, o);
12805                        that._trigger('chunkalways', null, o);
12806                        if (ub < fs) {
12807                            // File upload not yet complete,
12808                            // continue with the next chunk:
12809                            upload();
12810                        } else {
12811                            dfd.resolveWith(
12812                                o.context,
12813                                [result, textStatus, jqXHR]
12814                            );
12815                        }
12816                    })
12817                    .fail(function (jqXHR, textStatus, errorThrown) {
12818                        o.jqXHR = jqXHR;
12819                        o.textStatus = textStatus;
12820                        o.errorThrown = errorThrown;
12821                        that._trigger('chunkfail', null, o);
12822                        that._trigger('chunkalways', null, o);
12823                        dfd.rejectWith(
12824                            o.context,
12825                            [jqXHR, textStatus, errorThrown]
12826                        );
12827                    });
12828            };
12829            this._enhancePromise(promise);
12830            promise.abort = function () {
12831                return jqXHR.abort();
12832            };
12833            upload();
12834            return promise;
12835        },
12836
12837        _beforeSend: function (e, data) {
12838            if (this._active === 0) {
12839                // the start callback is triggered when an upload starts
12840                // and no other uploads are currently running,
12841                // equivalent to the global ajaxStart event:
12842                this._trigger('start');
12843                // Set timer for global bitrate progress calculation:
12844                this._bitrateTimer = new this._BitrateTimer();
12845                // Reset the global progress values:
12846                this._progress.loaded = this._progress.total = 0;
12847                this._progress.bitrate = 0;
12848            }
12849            // Make sure the container objects for the .response() and
12850            // .progress() methods on the data object are available
12851            // and reset to their initial state:
12852            this._initResponseObject(data);
12853            this._initProgressObject(data);
12854            data._progress.loaded = data.loaded = data.uploadedBytes || 0;
12855            data._progress.total = data.total = this._getTotal(data.files) || 1;
12856            data._progress.bitrate = data.bitrate = 0;
12857            this._active += 1;
12858            // Initialize the global progress values:
12859            this._progress.loaded += data.loaded;
12860            this._progress.total += data.total;
12861        },
12862
12863        _onDone: function (result, textStatus, jqXHR, options) {
12864            var total = options._progress.total,
12865                response = options._response;
12866            if (options._progress.loaded < total) {
12867                // Create a progress event if no final progress event
12868                // with loaded equaling total has been triggered:
12869                this._onProgress($.Event('progress', {
12870                    lengthComputable: true,
12871                    loaded: total,
12872                    total: total
12873                }), options);
12874            }
12875            response.result = options.result = result;
12876            response.textStatus = options.textStatus = textStatus;
12877            response.jqXHR = options.jqXHR = jqXHR;
12878            this._trigger('done', null, options);
12879        },
12880
12881        _onFail: function (jqXHR, textStatus, errorThrown, options) {
12882            var response = options._response;
12883            if (options.recalculateProgress) {
12884                // Remove the failed (error or abort) file upload from
12885                // the global progress calculation:
12886                this._progress.loaded -= options._progress.loaded;
12887                this._progress.total -= options._progress.total;
12888            }
12889            response.jqXHR = options.jqXHR = jqXHR;
12890            response.textStatus = options.textStatus = textStatus;
12891            response.errorThrown = options.errorThrown = errorThrown;
12892            this._trigger('fail', null, options);
12893        },
12894
12895        _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
12896            // jqXHRorResult, textStatus and jqXHRorError are added to the
12897            // options object via done and fail callbacks
12898            this._trigger('always', null, options);
12899        },
12900
12901        _onSend: function (e, data) {
12902            if (!data.submit) {
12903                this._addConvenienceMethods(e, data);
12904            }
12905            var that = this,
12906                jqXHR,
12907                aborted,
12908                slot,
12909                pipe,
12910                options = that._getAJAXSettings(data),
12911                send = function () {
12912                    that._sending += 1;
12913                    // Set timer for bitrate progress calculation:
12914                    options._bitrateTimer = new that._BitrateTimer();
12915                    jqXHR = jqXHR || (
12916                        ((aborted || that._trigger(
12917                            'send',
12918                            $.Event('send', {delegatedEvent: e}),
12919                            options
12920                        ) === false) &&
12921                        that._getXHRPromise(false, options.context, aborted)) ||
12922                        that._chunkedUpload(options) || $.ajax(options)
12923                    ).done(function (result, textStatus, jqXHR) {
12924                        that._onDone(result, textStatus, jqXHR, options);
12925                    }).fail(function (jqXHR, textStatus, errorThrown) {
12926                        that._onFail(jqXHR, textStatus, errorThrown, options);
12927                    }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
12928                        that._onAlways(
12929                            jqXHRorResult,
12930                            textStatus,
12931                            jqXHRorError,
12932                            options
12933                        );
12934                        that._sending -= 1;
12935                        that._active -= 1;
12936                        if (options.limitConcurrentUploads &&
12937                                options.limitConcurrentUploads > that._sending) {
12938                            // Start the next queued upload,
12939                            // that has not been aborted:
12940                            var nextSlot = that._slots.shift();
12941                            while (nextSlot) {
12942                                if (that._getDeferredState(nextSlot) === 'pending') {
12943                                    nextSlot.resolve();
12944                                    break;
12945                                }
12946                                nextSlot = that._slots.shift();
12947                            }
12948                        }
12949                        if (that._active === 0) {
12950                            // The stop callback is triggered when all uploads have
12951                            // been completed, equivalent to the global ajaxStop event:
12952                            that._trigger('stop');
12953                        }
12954                    });
12955                    return jqXHR;
12956                };
12957            this._beforeSend(e, options);
12958            if (this.options.sequentialUploads ||
12959                    (this.options.limitConcurrentUploads &&
12960                    this.options.limitConcurrentUploads <= this._sending)) {
12961                if (this.options.limitConcurrentUploads > 1) {
12962                    slot = $.Deferred();
12963                    this._slots.push(slot);
12964                    pipe = slot.then(send);
12965                } else {
12966                    this._sequence = this._sequence.then(send, send);
12967                    pipe = this._sequence;
12968                }
12969                // Return the piped Promise object, enhanced with an abort method,
12970                // which is delegated to the jqXHR object of the current upload,
12971                // and jqXHR callbacks mapped to the equivalent Promise methods:
12972                pipe.abort = function () {
12973                    aborted = [undefined, 'abort', 'abort'];
12974                    if (!jqXHR) {
12975                        if (slot) {
12976                            slot.rejectWith(options.context, aborted);
12977                        }
12978                        return send();
12979                    }
12980                    return jqXHR.abort();
12981                };
12982                return this._enhancePromise(pipe);
12983            }
12984            return send();
12985        },
12986
12987        _onAdd: function (e, data) {
12988            var that = this,
12989                result = true,
12990                options = $.extend({}, this.options, data),
12991                files = data.files,
12992                filesLength = files.length,
12993                limit = options.limitMultiFileUploads,
12994                limitSize = options.limitMultiFileUploadSize,
12995                overhead = options.limitMultiFileUploadSizeOverhead,
12996                batchSize = 0,
12997                paramName = this._getParamName(options),
12998                paramNameSet,
12999                paramNameSlice,
13000                fileSet,
13001                i,
13002                j = 0;
13003            if (!filesLength) {
13004                return false;
13005            }
13006            if (limitSize && files[0].size === undefined) {
13007                limitSize = undefined;
13008            }
13009            if (!(options.singleFileUploads || limit || limitSize) ||
13010                    !this._isXHRUpload(options)) {
13011                fileSet = [files];
13012                paramNameSet = [paramName];
13013            } else if (!(options.singleFileUploads || limitSize) && limit) {
13014                fileSet = [];
13015                paramNameSet = [];
13016                for (i = 0; i < filesLength; i += limit) {
13017                    fileSet.push(files.slice(i, i + limit));
13018                    paramNameSlice = paramName.slice(i, i + limit);
13019                    if (!paramNameSlice.length) {
13020                        paramNameSlice = paramName;
13021                    }
13022                    paramNameSet.push(paramNameSlice);
13023                }
13024            } else if (!options.singleFileUploads && limitSize) {
13025                fileSet = [];
13026                paramNameSet = [];
13027                for (i = 0; i < filesLength; i = i + 1) {
13028                    batchSize += files[i].size + overhead;
13029                    if (i + 1 === filesLength ||
13030                            ((batchSize + files[i + 1].size + overhead) > limitSize) ||
13031                            (limit && i + 1 - j >= limit)) {
13032                        fileSet.push(files.slice(j, i + 1));
13033                        paramNameSlice = paramName.slice(j, i + 1);
13034                        if (!paramNameSlice.length) {
13035                            paramNameSlice = paramName;
13036                        }
13037                        paramNameSet.push(paramNameSlice);
13038                        j = i + 1;
13039                        batchSize = 0;
13040                    }
13041                }
13042            } else {
13043                paramNameSet = paramName;
13044            }
13045            data.originalFiles = files;
13046            $.each(fileSet || files, function (index, element) {
13047                var newData = $.extend({}, data);
13048                newData.files = fileSet ? element : [element];
13049                newData.paramName = paramNameSet[index];
13050                that._initResponseObject(newData);
13051                that._initProgressObject(newData);
13052                that._addConvenienceMethods(e, newData);
13053                result = that._trigger(
13054                    'add',
13055                    $.Event('add', {delegatedEvent: e}),
13056                    newData
13057                );
13058                return result;
13059            });
13060            return result;
13061        },
13062
13063        _replaceFileInput: function (data) {
13064            var input = data.fileInput,
13065                inputClone = input.clone(true),
13066                restoreFocus = input.is(document.activeElement);
13067            // Add a reference for the new cloned file input to the data argument:
13068            data.fileInputClone = inputClone;
13069            $('<form></form>').append(inputClone)[0].reset();
13070            // Detaching allows to insert the fileInput on another form
13071            // without loosing the file input value:
13072            input.after(inputClone).detach();
13073            // If the fileInput had focus before it was detached,
13074            // restore focus to the inputClone.
13075            if (restoreFocus) {
13076                inputClone.focus();
13077            }
13078            // Avoid memory leaks with the detached file input:
13079            $.cleanData(input.unbind('remove'));
13080            // Replace the original file input element in the fileInput
13081            // elements set with the clone, which has been copied including
13082            // event handlers:
13083            this.options.fileInput = this.options.fileInput.map(function (i, el) {
13084                if (el === input[0]) {
13085                    return inputClone[0];
13086                }
13087                return el;
13088            });
13089            // If the widget has been initialized on the file input itself,
13090            // override this.element with the file input clone:
13091            if (input[0] === this.element[0]) {
13092                this.element = inputClone;
13093            }
13094        },
13095
13096        _handleFileTreeEntry: function (entry, path) {
13097            var that = this,
13098                dfd = $.Deferred(),
13099                errorHandler = function (e) {
13100                    if (e && !e.entry) {
13101                        e.entry = entry;
13102                    }
13103                    // Since $.when returns immediately if one
13104                    // Deferred is rejected, we use resolve instead.
13105                    // This allows valid files and invalid items
13106                    // to be returned together in one set:
13107                    dfd.resolve([e]);
13108                },
13109                successHandler = function (entries) {
13110                    that._handleFileTreeEntries(
13111                        entries,
13112                        path + entry.name + '/'
13113                    ).done(function (files) {
13114                        dfd.resolve(files);
13115                    }).fail(errorHandler);
13116                },
13117                readEntries = function () {
13118                    dirReader.readEntries(function (results) {
13119                        if (!results.length) {
13120                            successHandler(entries);
13121                        } else {
13122                            entries = entries.concat(results);
13123                            readEntries();
13124                        }
13125                    }, errorHandler);
13126                },
13127                dirReader, entries = [];
13128            path = path || '';
13129            if (entry.isFile) {
13130                if (entry._file) {
13131                    // Workaround for Chrome bug #149735
13132                    entry._file.relativePath = path;
13133                    dfd.resolve(entry._file);
13134                } else {
13135                    entry.file(function (file) {
13136                        file.relativePath = path;
13137                        dfd.resolve(file);
13138                    }, errorHandler);
13139                }
13140            } else if (entry.isDirectory) {
13141                dirReader = entry.createReader();
13142                readEntries();
13143            } else {
13144                // Return an empy list for file system items
13145                // other than files or directories:
13146                dfd.resolve([]);
13147            }
13148            return dfd.promise();
13149        },
13150
13151        _handleFileTreeEntries: function (entries, path) {
13152            var that = this;
13153            return $.when.apply(
13154                $,
13155                $.map(entries, function (entry) {
13156                    return that._handleFileTreeEntry(entry, path);
13157                })
13158            ).then(function () {
13159                return Array.prototype.concat.apply(
13160                    [],
13161                    arguments
13162                );
13163            });
13164        },
13165
13166        _getDroppedFiles: function (dataTransfer) {
13167            dataTransfer = dataTransfer || {};
13168            var items = dataTransfer.items;
13169            if (items && items.length && (items[0].webkitGetAsEntry ||
13170                    items[0].getAsEntry)) {
13171                return this._handleFileTreeEntries(
13172                    $.map(items, function (item) {
13173                        var entry;
13174                        if (item.webkitGetAsEntry) {
13175                            entry = item.webkitGetAsEntry();
13176                            if (entry) {
13177                                // Workaround for Chrome bug #149735:
13178                                entry._file = item.getAsFile();
13179                            }
13180                            return entry;
13181                        }
13182                        return item.getAsEntry();
13183                    })
13184                );
13185            }
13186            return $.Deferred().resolve(
13187                $.makeArray(dataTransfer.files)
13188            ).promise();
13189        },
13190
13191        _getSingleFileInputFiles: function (fileInput) {
13192            fileInput = $(fileInput);
13193            var entries = fileInput.prop('webkitEntries') ||
13194                    fileInput.prop('entries'),
13195                files,
13196                value;
13197            if (entries && entries.length) {
13198                return this._handleFileTreeEntries(entries);
13199            }
13200            files = $.makeArray(fileInput.prop('files'));
13201            if (!files.length) {
13202                value = fileInput.prop('value');
13203                if (!value) {
13204                    return $.Deferred().resolve([]).promise();
13205                }
13206                // If the files property is not available, the browser does not
13207                // support the File API and we add a pseudo File object with
13208                // the input value as name with path information removed:
13209                files = [{name: value.replace(/^.*\\/, '')}];
13210            } else if (files[0].name === undefined && files[0].fileName) {
13211                // File normalization for Safari 4 and Firefox 3:
13212                $.each(files, function (index, file) {
13213                    file.name = file.fileName;
13214                    file.size = file.fileSize;
13215                });
13216            }
13217            return $.Deferred().resolve(files).promise();
13218        },
13219
13220        _getFileInputFiles: function (fileInput) {
13221            if (!(fileInput instanceof $) || fileInput.length === 1) {
13222                return this._getSingleFileInputFiles(fileInput);
13223            }
13224            return $.when.apply(
13225                $,
13226                $.map(fileInput, this._getSingleFileInputFiles)
13227            ).then(function () {
13228                return Array.prototype.concat.apply(
13229                    [],
13230                    arguments
13231                );
13232            });
13233        },
13234
13235        _onChange: function (e) {
13236            var that = this,
13237                data = {
13238                    fileInput: $(e.target),
13239                    form: $(e.target.form)
13240                };
13241            this._getFileInputFiles(data.fileInput).always(function (files) {
13242                data.files = files;
13243                if (that.options.replaceFileInput) {
13244                    that._replaceFileInput(data);
13245                }
13246                if (that._trigger(
13247                        'change',
13248                        $.Event('change', {delegatedEvent: e}),
13249                        data
13250                    ) !== false) {
13251                    that._onAdd(e, data);
13252                }
13253            });
13254        },
13255
13256        _onPaste: function (e) {
13257            var items = e.originalEvent && e.originalEvent.clipboardData &&
13258                    e.originalEvent.clipboardData.items,
13259                data = {files: []};
13260            if (items && items.length) {
13261                $.each(items, function (index, item) {
13262                    var file = item.getAsFile && item.getAsFile();
13263                    if (file) {
13264                        data.files.push(file);
13265                    }
13266                });
13267                if (this._trigger(
13268                        'paste',
13269                        $.Event('paste', {delegatedEvent: e}),
13270                        data
13271                    ) !== false) {
13272                    this._onAdd(e, data);
13273                }
13274            }
13275        },
13276
13277        _onDrop: function (e) {
13278            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
13279            var that = this,
13280                dataTransfer = e.dataTransfer,
13281                data = {};
13282            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
13283                e.preventDefault();
13284                this._getDroppedFiles(dataTransfer).always(function (files) {
13285                    data.files = files;
13286                    if (that._trigger(
13287                            'drop',
13288                            $.Event('drop', {delegatedEvent: e}),
13289                            data
13290                        ) !== false) {
13291                        that._onAdd(e, data);
13292                    }
13293                });
13294            }
13295        },
13296
13297        _onDragOver: getDragHandler('dragover'),
13298
13299        _onDragEnter: getDragHandler('dragenter'),
13300
13301        _onDragLeave: getDragHandler('dragleave'),
13302
13303        _initEventHandlers: function () {
13304            if (this._isXHRUpload(this.options)) {
13305                this._on(this.options.dropZone, {
13306                    dragover: this._onDragOver,
13307                    drop: this._onDrop,
13308                    // event.preventDefault() on dragenter is required for IE10+:
13309                    dragenter: this._onDragEnter,
13310                    // dragleave is not required, but added for c
13310ompleteness:
13311                    dragleave: this._onDragLeave
13312                });
13313                this._on(this.options.pasteZone, {
13314                    paste: this._onPaste
13315                });
13316            }
13317            if ($.support.fileInput) {
13318                this._on(this.options.fileInput, {
13319                    change: this._onChange
13320                });
13321            }
13322        },
13323
13324        _destroyEventHandlers: function () {
13325            this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
13326            this._off(this.options.pasteZone, 'paste');
13327            this._off(this.options.fileInput, 'change');
13328        },
13329
13330        _setOption: function (key, value) {
13331            var reinit = $.inArray(key, this._specialOptions) !== -1;
13332            if (reinit) {
13333                this._destroyEventHandlers();
13334            }
13335            this._super(key, value);
13336            if (reinit) {
13337                this._initSpecialOptions();
13338                this._initEventHandlers();
13339            }
13340        },
13341
13342        _initSpecialOptions: function () {
13343            var options = this.options;
13344            if (options.fileInput === undefined) {
13345                options.fileInput = this.element.is('input[type="file"]') ?
13346                        this.element : this.element.find('input[type="file"]');
13347            } else if (!(options.fileInput instanceof $)) {
13348                options.fileInput = $(options.fileInput);
13349            }
13350            if (!(options.dropZone instanceof $)) {
13351                options.dropZone = $(options.dropZone);
13352            }
13353            if (!(options.pasteZone instanceof $)) {
13354                options.pasteZone = $(options.pasteZone);
13355            }
13356        },
13357
13358        _getRegExp: function (str) {
13359            var parts = str.split('/'),
13360                modifiers = parts.pop();
13361            parts.shift();
13362            return new RegExp(parts.join('/'), modifiers);
13363        },
13364
13365        _isRegExpOption: function (key, value) {
13366            return key !== 'url' && $.type(value) === 'string' &&
13367                /^\/.*\/[igm]{0,3}$/.test(value);
13368        },
13369
13370        _initDataAttributes: function () {
13371            var that = this,
13372                options = this.options,
13373                data = this.element.data();
13374            // Initialize options set via HTML5 data-attributes:
13375            $.each(
13376                this.element[0].attributes,
13377                function (index, attr) {
13378                    var key = attr.name.toLowerCase(),
13379                        value;
13380                    if (/^data-/.test(key)) {
13381                        // Convert hyphen-ated key to camelCase:
13382                        key = key.slice(5).replace(/-[a-z]/g, function (str) {
13383                            return str.charAt(1).toUpperCase();
13384                        });
13385                        value = data[key];
13386                        if (that._isRegExpOption(key, value)) {
13387                            value = that._getRegExp(value);
13388                        }
13389                        options[key] = value;
13390                    }
13391                }
13392            );
13393        },
13394
13395        _create: function () {
13396            this._initDataAttributes();
13397            this._initSpecialOptions();
13398            this._slots = [];
13399            this._sequence = this._getXHRPromise(true);
13400            this._sending = this._active = 0;
13401            this._initProgressObject(this);
13402            this._initEventHandlers();
13403        },
13404
13405        // This method is exposed to the widget API and allows to query
13406        // the number of active uploads:
13407        active: function () {
13408            return this._active;
13409        },
13410
13411        // This method is exposed to the widget API and allows to query
13412        // the widget upload progress.
13413        // It returns an object with loaded, total and bitrate properties
13414        // for the running uploads:
13415        progress: function () {
13416            return this._progress;
13417        },
13418
13419        // This method is exposed to the widget API and allows adding files
13420        // using the fileupload API. The data parameter accepts an object which
13421        // must have a files property and can contain additional options:
13422        // .fileupload('add', {files: filesList});
13423        add: function (data) {
13424            var that = this;
13425            if (!data || this.options.disabled) {
13426                return;
13427            }
13428            if (data.fileInput && !data.files) {
13429                this._getFileInputFiles(data.fileInput).always(function (files) {
13430                    data.files = files;
13431                    that._onAdd(null, data);
13432                });
13433            } else {
13434                data.files = $.makeArray(data.files);
13435                this._onAdd(null, data);
13436            }
13437        },
13438
13439        // This method is exposed to the widget API and allows sending files
13440        // using the fileupload API. The data parameter accepts an object which
13441        // must have a files or fileInput property and can contain additional options:
13442        // .fileupload('send', {files: filesList});
13443        // The method returns a Promise object for the file upload call.
13444        send: function (data) {
13445            if (data && !this.options.disabled) {
13446                if (data.fileInput && !data.files) {
13447                    var that = this,
13448                        dfd = $.Deferred(),
13449                        promise = dfd.promise(),
13450                        jqXHR,
13451                        aborted;
13452                    promise.abort = function () {
13453                        aborted = true;
13454                        if (jqXHR) {
13455                            return jqXHR.abort();
13456                        }
13457                        dfd.reject(null, 'abort', 'abort');
13458                        return promise;
13459                    };
13460                    this._getFileInputFiles(data.fileInput).always(
13461                        function (files) {
13462                            if (aborted) {
13463                                return;
13464                            }
13465                            if (!files.length) {
13466                                dfd.reject();
13467                                return;
13468                            }
13469                            data.files = files;
13470                            jqXHR = that._onSend(null, data);
13471                            jqXHR.then(
13472                                function (result, textStatus, jqXHR) {
13473                                    dfd.resolve(result, textStatus, jqXHR);
13474                                },
13475                                function (jqXHR, textStatus, errorThrown) {
13476                                    dfd.reject(jqXHR, textStatus, errorThrown);
13477                                }
13478                            );
13479                        }
13480                    );
13481                    return this._enhancePromise(promise);
13482                }
13483                data.files = $.makeArray(data.files);
13484                if (data.files.length) {
13485                    return this._onSend(null, data);
13486                }
13487            }
13488            return this._getXHRPromise(false, data && data.context);
13489        }
13490
13491    });
13492
13493}));
13494
13495
13496
13497/*! jQuery Validation Plugin - v1.11.0 - 2/4/2013
13498* https://github.com/jzaefferer/jquery-validation
13499* Copyright (c) 2013 Jörn Zaefferer; Licensed MIT */
13500
13501
13502(function($) {
13503
13504$.extend($.fn, {
13505	// http://docs.jquery.com/Plugins/Validation/validate
13506	validate: function( options ) {
13507
13508		// if nothing is selected, return nothing; can't chain anyway
13509		if ( !this.length ) {
13510			if ( options && options.debug && window.console ) {
13511				console.warn( "Nothing selected, can't validate, returning nothing." );
13512			}
13513			return;
13514		}
13515
13516		// check if a validator for this form was already created
13517		var validator = $.data( this[0], "validator" );
13518		if ( validator ) {
13519			return validator;
13520		}
13521
13522		// Add novalidate tag if HTML5.
13523		this.attr( "novalidate", "novalidate" );
13524
13525		validator = new $.validator( options, this[0] );
13526		$.data( this[0], "validator", validator );
13527
13528		if ( validator.settings.onsubmit ) {
13529
13530			this.validateDelegate( ":submit", "click", function( event ) {
13531				if ( validator.settings.submitHandler ) {
13532					validator.submitButton = event.target;
13533				}
13534				// allow suppressing validation by adding a cancel class to the submit button
13535				if ( $(event.target).hasClass("cancel") ) {
13536					validator.cancelSubmit = true;
13537				}
13538			});
13539
13540			// validate the form on submit
13541			this.submit( function( event ) {
13542				if ( validator.settings.debug ) {
13543					// prevent form submit to be able to see console output
13544					event.preventDefault();
13545				}
13546				function handle() {
13547					var hidden;
13548					if ( validator.settings.submitHandler ) {
13549						if ( validator.submitButton ) {
13550							// insert a hidden input as a replacement for the missing submit button
13551							hidden = $("<input type='hidden'/>").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm);
13552						}
13553						validator.settings.submitHandler.call( validator, validator.currentForm, event );
13554						if ( validator.submitButton ) {
13555							// and clean up afterwards; thanks to no-block-scope, hidden can be referenced
13556							hidden.remove();
13557						}
13558						return false;
13559					}
13560					return true;
13561				}
13562
13563				// prevent submit for invalid forms or custom submit handlers
13564				if ( validator.cancelSubmit ) {
13565					validator.cancelSubmit = false;
13566					return handle();
13567				}
13568				if ( validator.form() ) {
13569					if ( validator.pendingRequest ) {
13570						validator.formSubmitted = true;
13571						return false;
13572					}
13573					return handle();
13574				} else {
13575					validator.focusInvalid();
13576					return false;
13577				}
13578			});
13579		}
13580
13581		return validator;
13582	},
13583	// http://docs.jquery.com/Plugins/Validation/valid
13584	valid: function() {
13585		if ( $(this[0]).is("form")) {
13586			return this.validate().form();
13587		} else {
13588			var valid = true;
13589			var validator = $(this[0].form).validate();
13590			this.each(function() {
13591				valid &= validator.element(this);
13592			});
13593			return valid;
13594		}
13595	},
13596	// attributes: space seperated list of attributes to retrieve and remove
13597	removeAttrs: function( attributes ) {
13598		var result = {},
13599			$element = this;
13600		$.each(attributes.split(/\s/), function( index, value ) {
13601			result[value] = $element.attr(value);
13602			$element.removeAttr(value);
13603		});
13604		return result;
13605	},
13606	// http://docs.jquery.com/Plugins/Validation/rules
13607	rules: function( command, argument ) {
13608		var element = this[0];
13609
13610		if ( command ) {
13611			var settings = $.data(element.form, "validator").settings;
13612			var staticRules = settings.rules;
13613			var existingRules = $.validator.staticRules(element);
13614			switch(command) {
13615			case "add":
13616				$.extend(existingRules, $.validator.normalizeRule(argument));
13617				staticRules[element.name] = existingRules;
13618				if ( argument.messages ) {
13619					settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages );
13620				}
13621				break;
13622			case "remove":
13623				if ( !argument ) {
13624					delete staticRules[element.name];
13625					return existingRules;
13626				}
13627				var filtered = {};
13628				$.each(argument.split(/\s/), function( index, method ) {
13629					filtered[method] = existingRules[method];
13630					delete existingRules[method];
13631				});
13632				return filtered;
13633			}
13634		}
13635
13636		var data = $.validator.normalizeRules(
13637		$.extend(
13638			{},
13639			$.validator.classRules(element),
13640			$.validator.attributeRules(element),
13641			$.validator.dataRules(element),
13642			$.validator.staticRules(element)
13643		), element);
13644
13645		// make sure required is at front
13646		if ( data.required ) {
13647			var param = data.required;
13648			delete data.required;
13649			data = $.extend({required: param}, data);
13650		}
13651
13652		return data;
13653	}
13654});
13655
13656// Custom selectors
13657$.extend($.expr[":"], {
13658	// http://docs.jquery.com/Plugins/Validation/blank
13659	blank: function( a ) { return !$.trim("" + a.value); },
13660	// http://docs.jquery.com/Plugins/Validation/filled
13661	filled: function( a ) { return !!$.trim("" + a.value); },
13662	// http://docs.jquery.com/Plugins/Validation/unchecked
13663	unchecked: function( a ) { return !a.checked; }
13664});
13665
13666// constructor for validator
13667$.validator = function( options, form ) {
13668	this.settings = $.extend( true, {}, $.validator.defaults, options );
13669	this.currentForm = form;
13670	this.init();
13671};
13672
13673$.validator.format = function( source, params ) {
13674	if ( arguments.length === 1 ) {
13675		return function() {
13676			var args = $.makeArray(arguments);
13677			args.unshift(source);
13678			return $.validator.format.apply( this, args );
13679		};
13680	}
13681	if ( arguments.length > 2 && params.constructor !== Array  ) {
13682		params = $.makeArray(arguments).slice(1);
13683	}
13684	if ( params.constructor !== Array ) {
13685		params = [ params ];
13686	}
13687	$.each(params, function( i, n ) {
13688		source = source.replace( new RegExp("\\{" + i + "\\}", "g"), function() {
13689			return n;
13690		});
13691	});
13692	return source;
13693};
13694
13695$.extend($.validator, {
13696
13697	defaults: {
13698		messages: {},
13699		groups: {},
13700		rules: {},
13701		errorClass: "error",
13702		validClass: "valid",
13703		errorElement: "label",
13704		focusInvalid: true,
13705		errorContainer: $([]),
13706		errorLabelContainer: $([]),
13707		onsubmit: true,
13708		ignore: ":hidden",
13709		ignoreTitle: false,
13710		onfocusin: function( element, event ) {
13711			this.lastActive = element;
13712
13713			// hide error label and remove error class on focus if enabled
13714			if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
13715				if ( this.settings.unhighlight ) {
13716					this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
13717				}
13718				this.addWrapper(this.errorsFor(element)).hide();
13719			}
13720		},
13721		onfocusout: function( element, event ) {
13722			if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
13723				this.element(element);
13724			}
13725		},
13726		onkeyup: function( element, event ) {
13727			if ( event.which === 9 && this.elementValue(element) === "" ) {
13728				return;
13729			} else if ( element.name in this.submitted || element === this.lastElement ) {
13730				this.element(element);
13731			}
13732		},
13733		onclick: function( element, event ) {
13734			// click on selects, radiobuttons and checkboxes
13735			if ( element.name in this.submitted ) {
13736				this.element(element);
13737			}
13738			// or option elements, check parent select in that case
13739			else if ( element.parentNode.name in this.submitted ) {
13740				this.element(element.parentNode);
13741			}
13742		},
13743		highlight: function( element, errorClass, validClass ) {
13744			if ( element.type === "radio" ) {
13745				this.findByName(element.name).addClass(errorClass).removeClass(validClass);
13746			} else {
13747				$(element).addClass(errorClass).removeClass(validClass);
13748			}
13749		},
13750		unhighlight: function( element, errorClass, validClass ) {
13751			if ( element.type === "radio" ) {
13752				this.findByName(element.name).removeClass(errorClass).addClass(validClass);
13753			} else {
13754				$(element).removeClass(errorClass).addClass(validClass);
13755			}
13756		}
13757	},
13758
13759	// http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
13760	setDefaults: function( settings ) {
13761		$.extend( $.validator.defaults, settings );
13762	},
13763
13764	messages: {
13765		required: "This field is required.",
13766		remote: "Please fix this field.",
13767		email: "Please enter a valid email address.",
13768		url: "Please enter a valid URL.",
13769		date: "Please enter a valid date.",
13770		dateISO: "Please enter a valid date (ISO).",
13771		number: "Please enter a valid number.",
13772		digits: "Please enter only digits.",
13773		creditcard: "Please enter a valid credit card number.",
13774		equalTo: "Please enter the same value again.",
13775		maxlength: $.validator.format("Please enter no more than {0} characters."),
13776		minlength: $.validator.format("Please enter at least {0} characters."),
13777		rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
13778		range: $.validator.format("Please enter a value between {0} and {1}."),
13779		max: $.validator.format("Please enter a value less than or equal to {0}."),
13780		min: $.validator.format("Please enter a value greater than or equal to {0}.")
13781	},
13782
13783	autoCreateRanges: false,
13784
13785	prototype: {
13786
13787		init: function() {
13788			this.labelContainer = $(this.settings.errorLabelContainer);
13789			this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
13790			this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
13791			this.submitted = {};
13792			this.valueCache = {};
13793			this.pendingRequest = 0;
13794			this.pending = {};
13795			this.invalid = {};
13796			this.reset();
13797
13798			var groups = (this.groups = {});
13799			$.each(this.settings.groups, function( key, value ) {
13800				if ( typeof value === "string" ) {
13801					value = value.split(/\s/);
13802				}
13803				$.each(value, function( index, name ) {
13804					groups[name] = key;
13805				});
13806			});
13807			var rules = this.settings.rules;
13808			$.each(rules, function( key, value ) {
13809				rules[key] = $.validator.normalizeRule(value);
13810			});
13811
13812			function delegate(event) {
13813				var validator = $.data(this[0].form, "validator"),
13814					eventType = "on" + event.type.replace(/^validate/, "");
13815				if ( validator.settings[eventType] ) {
13816					validator.settings[eventType].call(validator, this[0], event);
13817				}
13818			}
13819			$(this.currentForm)
13820				.validateDelegate(":text, [type='password'], [type='file'], select, textarea, " +
13821					"[type='number'], [type='search'] ,[type='tel'], [type='url'], " +
13822					"[type='email'], [type='datetime'], [type='date'], [type='month'], " +
13823					"[type='week'], [type='time'], [type='datetime-local'], " +
13824					"[type='range'], [type='color'] ",
13825					"focusin focusout keyup", delegate)
13826				.validateDelegate("[type='radio'], [type='checkbox'], select, option", "click", delegate);
13827
13828			if ( this.settings.invalidHandler ) {
13829				$(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
13830			}
13831		},
13832
13833		// http://docs.jquery.com/Plugins/Validation/Validator/form
13834		form: function() {
13835			this.checkForm();
13836			$.extend(this.submitted, this.errorMap);
13837			this.invalid = $.extend({}, this.errorMap);
13838			if ( !this.valid() ) {
13839				$(this.currentForm).triggerHandler("invalid-form", [this]);
13840			}
13841			this.showErrors();
13842			return this.valid();
13843		},
13844
13845		checkForm: function() {
13846			this.prepareForm();
13847			for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
13848				this.check( elements[i] );
13849			}
13850			return this.valid();
13851		},
13852
13853		// http://docs.jquery.com/Plugins/Validation/Validator/element
13854		element: function( element ) {
13855			element = this.validationTargetFor( this.clean( element ) );
13856			this.lastElement = element;
13857			this.prepareElement( element );
13858			this.currentElements = $(element);
13859			var result = this.check( element ) !== false;
13860			if ( result ) {
13861				delete this.invalid[element.name];
13862			} else {
13863				this.invalid[element.name] = true;
13864			}
13865			if ( !this.numberOfInvalids() ) {
13866				// Hide error containers on last error
13867				this.toHide = this.toHide.add( this.containers );
13868			}
13869			this.showErrors();
13870			return result;
13871		},
13872
13873		// http://docs.jquery.com/Plugins/Validation/Validator/showErrors
13874		showErrors: function( errors ) {
13875			if ( errors ) {
13876				// add items to error list and map
13877				$.extend( this.errorMap, errors );
13878				this.errorList = [];
13879				for ( var name in errors ) {
13880					this.errorList.push({
13881						message: errors[name],
13882						element: this.findByName(name)[0]
13883					});
13884				}
13885				// remove items from success list
13886				this.successList = $.grep( this.successList, function( element ) {
13887					return !(element.name in errors);
13888				});
13889			}
13890			if ( this.settings.showErrors ) {
13891				this.settings.showErrors.call( this, this.errorMap, this.errorList );
13892			} else {
13893				this.defaultShowErrors();
13894			}
13895		},
13896
13897		// http://docs.jquery.com/Plugins/Validation/Validator/resetForm
13898		resetForm: function() {
13899			if ( $.fn.resetForm ) {
13900				$(this.currentForm).resetForm();
13901			}
13902			this.submitted = {};
13903			this.lastElement = null;
13904			this.prepareForm();
13905			this.hideErrors();
13906			this.elements().removeClass( this.settings.errorClass ).removeData( "previousValue" );
13907		},
13908
13909		numberOfInvalids: function() {
13910			return this.objectLength(this.invalid);
13911		},
13912
13913		objectLength: function( obj ) {
13914			var count = 0;
13915			for ( var i in obj ) {
13916				count++;
13917			}
13918			return count;
13919		},
13920
13921		hideErrors: function() {
13922			this.addWrapper( this.toHide ).hide();
13923		},
13924
13925		valid: function() {
13926			return this.size() === 0;
13927		},
13928
13929		size: function() {
13930			return this.errorList.length;
13931		},
13932
13933		focusInvalid: function() {
13934			if ( this.settings.focusInvalid ) {
13935				try {
13936					$(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
13937					.filter(":visible")
13938					.focus()
13939					// manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
13940					.trigger("focusin");
13941				} catch(e) {
13942					// ignore IE throwing errors when focusing hidden elements
13943				}
13944			}
13945		},
13946
13947		findLastActive: function() {
13948			var lastActive = this.lastActive;
13949			return lastActive && $.grep(this.errorList, function( n ) {
13950				return n.element.name === lastActive.name;
13951			}).length === 1 && lastActive;
13952		},
13953
13954		elements: function() {
13955			var validator = this,
13956				rulesCache = {};
13957
13958			// select all valid inputs inside the form (no submit or reset buttons)
13959			return $(this.currentForm)
13960			.find("input, select, textarea")
13961			.not(":submit, :reset, :image, [disabled]")
13962			.not( this.settings.ignore )
13963			.filter(function() {
13964				if ( !this.name && validator.settings.debug && window.console ) {
13965					console.error( "%o has no name assigned", this);
13966				}
13967
13968				// select only the first element for each name, and only those with rules specified
13969				if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) {
13970					return false;
13971				}
13972
13973				rulesCache[this.name] = true;
13974				return true;
13975			});
13976		},
13977
13978		clean: function( selector ) {
13979			return $(selector)[0];
13980		},
13981
13982		errors: function() {
13983			var errorClass = this.settings.errorClass.replace(" ", ".");
13984			return $(this.settings.errorElement + "." + errorClass, this.errorContext);
13985		},
13986
13987		reset: function() {
13988			this.successList = [];
13989			this.errorList = [];
13990			this.errorMap = {};
13991			this.toShow = $([]);
13992			this.toHide = $([]);
13993			this.currentElements = $([]);
13994		},
13995
13996		prepareForm: function() {
13997			this.reset();
13998			this.toHide = this.errors().add( this.containers );
13999		},
14000
14001		prepareElement: function( element ) {
14002			this.reset();
14003			this.toHide = this.errorsFor(element);
14004		},
14005
14006		elementValue: function( element ) {
14007			var type = $(element).attr("type"),
14008				val = $(element).val();
14009
14010			if ( type === "radio" || type === "checkbox" ) {
14011				return $("input[name='" + $(element).attr("name") + "']:checked").val();
14012			}
14013
14014			if ( typeof val === "string" ) {
14015				return val.replace(/\r/g, "");
14016			}
14017			return val;
14018		},
14019
14020		check: function( element ) {
14021			element = this.validationTargetFor( this.clean( element ) );
14022
14023			var rules = $(element).rules();
14024			var dependencyMismatch = false;
14025			var val = this.elementValue(element);
14026			var result;
14027
14028			for (var method in rules ) {
14029				var rule = { method: method, parameters: rules[method] };
14030				try {
14031
14032					result = $.validator.methods[method].call( this, val, element, rule.parameters );
14033
14034					// if a method indicates that the field is optional and therefore valid,
14035					// don't mark it as valid when there are no other rules
14036					if ( result === "dependency-mismatch" ) {
14037						dependencyMismatch = true;
14038						continue;
14039					}
14040					dependencyMismatch = false;
14041
14042					if ( result === "pending" ) {
14043						this.toHide = this.toHide.not( this.errorsFor(element) );
14044						return;
14045					}
14046
14047					if ( !result ) {
14048						this.formatAndAdd( element, rule );
14049						return false;
14050					}
14051				} catch(e) {
14052					if ( this.settings.debug && window.console ) {
14053						console.log( "Exception occured when checking element " + element.id + ", check the '" + rule.method + "' method.", e );
14054					}
14055					throw e;
14056				}
14057			}
14058			if ( dependencyMismatch ) {
14059				return;
14060			}
14061			if ( this.objectLength(rules) ) {
14062				this.successList.push(element);
14063			}
14064			return true;
14065		},
14066
14067		// return the custom message for the given element and validation method
14068		// specified in the element's HTML5 data attribute
14069		customDataMessage: function( element, method ) {
14070			return $(element).data("msg-" + method.toLowerCase()) || (element.attributes && $(element).attr("data-msg-" + method.toLowerCase()));
14071		},
14072
14073		// return the custom message for the given element name and validation method
14074		customMessage: function( name, method ) {
14075			var m = this.settings.messages[name];
14076			return m && (m.constructor === String ? m : m[method]);
14077		},
14078
14079		// return the first defined argument, allowing empty strings
14080		findDefined: function() {
14081			for(var i = 0; i < arguments.length; i++) {
14082				if ( arguments[i] !== undefined ) {
14083					return arguments[i];
14084				}
14085			}
14086			return undefined;
14087		},
14088
14089		defaultMessage: function( element, method ) {
14090			return this.findDefined(
14091				this.customMessage( element.name, method ),
14092				this.customDataMessage( element, method ),
14093				// title is never undefined, so handle empty string as undefined
14094				!this.settings.ignoreTitle && element.title || undefined,
14095				$.validator.messages[method],
14096				"<strong>Warning: No message defined for " + element.name + "</strong>"
14097			);
14098		},
14099
14100		formatAndAdd: function( element, rule ) {
14101			var message = this.defaultMessage( element, rule.method ),
14102				theregex = /\$?\{(\d+)\}/g;
14103			if ( typeof message === "function" ) {
14104				message = message.call(this, rule.parameters, element);
14105			} else if (theregex.test(message)) {
14106				message = $.validator.format(message.replace(theregex, "{$1}"), rule.parameters);
14107			}
14108			this.errorList.push({
14109				message: message,
14110				element: element
14111			});
14112
14113			this.errorMap[element.name] = message;
14114			this.submitted[element.name] = message;
14115		},
14116
14117		addWrapper: function( toToggle ) {
14118			if ( this.settings.wrapper ) {
14119				toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
14120			}
14121			return toToggle;
14122		},
14123
14124		defaultShowErrors: function() {
14125			var i, elements;
14126			for ( i = 0; this.errorList[i]; i++ ) {
14127				var error = this.errorList[i];
14128				if ( this.settings.highlight ) {
14129					this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.val
14129idClass );
14130				}
14131				this.showLabel( error.element, error.message );
14132			}
14133			if ( this.errorList.length ) {
14134				this.toShow = this.toShow.add( this.containers );
14135			}
14136			if ( this.settings.success ) {
14137				for ( i = 0; this.successList[i]; i++ ) {
14138					this.showLabel( this.successList[i] );
14139				}
14140			}
14141			if ( this.settings.unhighlight ) {
14142				for ( i = 0, elements = this.validElements(); elements[i]; i++ ) {
14143					this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
14144				}
14145			}
14146			this.toHide = this.toHide.not( this.toShow );
14147			this.hideErrors();
14148			this.addWrapper( this.toShow ).show();
14149		},
14150
14151		validElements: function() {
14152			return this.currentElements.not(this.invalidElements());
14153		},
14154
14155		invalidElements: function() {
14156			return $(this.errorList).map(function() {
14157				return this.element;
14158			});
14159		},
14160
14161		showLabel: function( element, message ) {
14162			var label = this.errorsFor( element );
14163			if ( label.length ) {
14164				// refresh error/success class
14165				label.removeClass( this.settings.validClass ).addClass( this.settings.errorClass );
14166				// replace message on existing label
14167				label.html(message);
14168			} else {
14169				// create label
14170				label = $("<" + this.settings.errorElement + ">")
14171					.attr("for", this.idOrName(element))
14172					.addClass(this.settings.errorClass)
14173					.html(message || "");
14174				if ( this.settings.wrapper ) {
14175					// make sure the element is visible, even in IE
14176					// actually showing the wrapped element is handled elsewhere
14177					label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
14178				}
14179				if ( !this.labelContainer.append(label).length ) {
14180					if ( this.settings.errorPlacement ) {
14181						this.settings.errorPlacement(label, $(element) );
14182					} else {
14183						label.insertAfter(element);
14184					}
14185				}
14186			}
14187			if ( !message && this.settings.success ) {
14188				label.text("");
14189				if ( typeof this.settings.success === "string" ) {
14190					label.addClass( this.settings.success );
14191				} else {
14192					this.settings.success( label, element );
14193				}
14194			}
14195			this.toShow = this.toShow.add(label);
14196		},
14197
14198		errorsFor: function( element ) {
14199			var name = this.idOrName(element);
14200			return this.errors().filter(function() {
14201				return $(this).attr("for") === name;
14202			});
14203		},
14204
14205		idOrName: function( element ) {
14206			return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
14207		},
14208
14209		validationTargetFor: function( element ) {
14210			// if radio/checkbox, validate first element in group instead
14211			if ( this.checkable(element) ) {
14212				element = this.findByName( element.name ).not(this.settings.ignore)[0];
14213			}
14214			return element;
14215		},
14216
14217		checkable: function( element ) {
14218			return (/radio|checkbox/i).test(element.type);
14219		},
14220
14221		findByName: function( name ) {
14222			return $(this.currentForm).find("[name='" + name + "']");
14223		},
14224
14225		getLength: function( value, element ) {
14226			switch( element.nodeName.toLowerCase() ) {
14227			case "select":
14228				return $("option:selected", element).length;
14229			case "input":
14230				if ( this.checkable( element) ) {
14231					return this.findByName(element.name).filter(":checked").length;
14232				}
14233			}
14234			return value.length;
14235		},
14236
14237		depend: function( param, element ) {
14238			return this.dependTypes[typeof param] ? this.dependTypes[typeof param](param, element) : true;
14239		},
14240
14241		dependTypes: {
14242			"boolean": function( param, element ) {
14243				return param;
14244			},
14245			"string": function( param, element ) {
14246				return !!$(param, element.form).length;
14247			},
14248			"function": function( param, element ) {
14249				return param(element);
14250			}
14251		},
14252
14253		optional: function( element ) {
14254			var val = this.elementValue(element);
14255			return !$.validator.methods.required.call(this, val, element) && "dependency-mismatch";
14256		},
14257
14258		startRequest: function( element ) {
14259			if ( !this.pending[element.name] ) {
14260				this.pendingRequest++;
14261				this.pending[element.name] = true;
14262			}
14263		},
14264
14265		stopRequest: function( element, valid ) {
14266			this.pendingRequest--;
14267			// sometimes synchronization fails, make sure pendingRequest is never < 0
14268			if ( this.pendingRequest < 0 ) {
14269				this.pendingRequest = 0;
14270			}
14271			delete this.pending[element.name];
14272			if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) {
14273				$(this.currentForm).submit();
14274				this.formSubmitted = false;
14275			} else if (!valid && this.pendingRequest === 0 && this.formSubmitted) {
14276				$(this.currentForm).triggerHandler("invalid-form", [this]);
14277				this.formSubmitted = false;
14278			}
14279		},
14280
14281		previousValue: function( element ) {
14282			return $.data(element, "previousValue") || $.data(element, "previousValue", {
14283				old: null,
14284				valid: true,
14285				message: this.defaultMessage( element, "remote" )
14286			});
14287		}
14288
14289	},
14290
14291	classRuleSettings: {
14292		required: {required: true},
14293		email: {email: true},
14294		url: {url: true},
14295		date: {date: true},
14296		dateISO: {dateISO: true},
14297		number: {number: true},
14298		digits: {digits: true},
14299		creditcard: {creditcard: true}
14300	},
14301
14302	addClassRules: function( className, rules ) {
14303		if ( className.constructor === String ) {
14304			this.classRuleSettings[className] = rules;
14305		} else {
14306			$.extend(this.classRuleSettings, className);
14307		}
14308	},
14309
14310	classRules: function( element ) {
14311		var rules = {};
14312		var classes = $(element).attr("class");
14313		if ( classes ) {
14314			$.each(classes.split(" "), function() {
14315				if ( this in $.validator.classRuleSettings ) {
14316					$.extend(rules, $.validator.classRuleSettings[this]);
14317				}
14318			});
14319		}
14320		return rules;
14321	},
14322
14323	attributeRules: function( element ) {
14324		var rules = {};
14325		var $element = $(element);
14326
14327		for (var method in $.validator.methods) {
14328			var value;
14329
14330			// support for <input required> in both html5 and older browsers
14331			if ( method === "required" ) {
14332				value = $element.get(0).getAttribute(method);
14333				// Some browsers return an empty string for the required attribute
14334				// and non-HTML5 browsers might have required="" markup
14335				if ( value === "" ) {
14336					value = true;
14337				}
14338				// force non-HTML5 browsers to return bool
14339				value = !!value;
14340			} else {
14341				value = $element.attr(method);
14342			}
14343
14344			if ( value ) {
14345				rules[method] = value;
14346			} else if ( $element[0].getAttribute("type") === method ) {
14347				rules[method] = true;
14348			}
14349		}
14350
14351		// maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
14352		if ( rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength) ) {
14353			delete rules.maxlength;
14354		}
14355
14356		return rules;
14357	},
14358
14359	dataRules: function( element ) {
14360		var method, value,
14361			rules = {}, $element = $(element);
14362		for (method in $.validator.methods) {
14363			value = $element.data("rule-" + method.toLowerCase());
14364			if ( value !== undefined ) {
14365				rules[method] = value;
14366			}
14367		}
14368		return rules;
14369	},
14370
14371	staticRules: function( element ) {
14372		var rules = {};
14373		var validator = $.data(element.form, "validator");
14374		if ( validator.settings.rules ) {
14375			rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
14376		}
14377		return rules;
14378	},
14379
14380	normalizeRules: function( rules, element ) {
14381		// handle dependency check
14382		$.each(rules, function( prop, val ) {
14383			// ignore rule when param is explicitly false, eg. required:false
14384			if ( val === false ) {
14385				delete rules[prop];
14386				return;
14387			}
14388			if ( val.param || val.depends ) {
14389				var keepRule = true;
14390				switch (typeof val.depends) {
14391				case "string":
14392					keepRule = !!$(val.depends, element.form).length;
14393					break;
14394				case "function":
14395					keepRule = val.depends.call(element, element);
14396					break;
14397				}
14398				if ( keepRule ) {
14399					rules[prop] = val.param !== undefined ? val.param : true;
14400				} else {
14401					delete rules[prop];
14402				}
14403			}
14404		});
14405
14406		// evaluate parameters
14407		$.each(rules, function( rule, parameter ) {
14408			rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
14409		});
14410
14411		// clean number parameters
14412		$.each(['minlength', 'maxlength'], function() {
14413			if ( rules[this] ) {
14414				rules[this] = Number(rules[this]);
14415			}
14416		});
14417		$.each(['rangelength'], function() {
14418			var parts;
14419			if ( rules[this] ) {
14420				if ( $.isArray(rules[this]) ) {
14421					rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
14422				} else if ( typeof rules[this] === "string" ) {
14423					parts = rules[this].split(/[\s,]+/);
14424					rules[this] = [Number(parts[0]), Number(parts[1])];
14425				}
14426			}
14427		});
14428
14429		if ( $.validator.autoCreateRanges ) {
14430			// auto-create ranges
14431			if ( rules.min && rules.max ) {
14432				rules.range = [rules.min, rules.max];
14433				delete rules.min;
14434				delete rules.max;
14435			}
14436			if ( rules.minlength && rules.maxlength ) {
14437				rules.rangelength = [rules.minlength, rules.maxlength];
14438				delete rules.minlength;
14439				delete rules.maxlength;
14440			}
14441		}
14442
14443		return rules;
14444	},
14445
14446	// Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
14447	normalizeRule: function( data ) {
14448		if ( typeof data === "string" ) {
14449			var transformed = {};
14450			$.each(data.split(/\s/), function() {
14451				transformed[this] = true;
14452			});
14453			data = transformed;
14454		}
14455		return data;
14456	},
14457
14458	// http://docs.jquery.com/Plugins/Validation/Validator/addMethod
14459	addMethod: function( name, method, message ) {
14460		$.validator.methods[name] = method;
14461		$.validator.messages[name] = message !== undefined ? message : $.validator.messages[name];
14462		if ( method.length < 3 ) {
14463			$.validator.addClassRules(name, $.validator.normalizeRule(name));
14464		}
14465	},
14466
14467	methods: {
14468
14469		// http://docs.jquery.com/Plugins/Validation/Methods/required
14470		required: function( value, element, param ) {
14471			// check if dependency is met
14472			if ( !this.depend(param, element) ) {
14473				return "dependency-mismatch";
14474			}
14475			if ( element.nodeName.toLowerCase() === "select" ) {
14476				// could be an array for select-multiple or a string, both are fine this way
14477				var val = $(element).val();
14478				return val && val.length > 0;
14479			}
14480			if ( this.checkable(element) ) {
14481				return this.getLength(value, element) > 0;
14482			}
14483			return $.trim(value).length > 0;
14484		},
14485
14486		// http://docs.jquery.com/Plugins/Validation/Methods/remote
14487		remote: function( value, element, param ) {
14488			if ( this.optional(element) ) {
14489				return "dependency-mismatch";
14490			}
14491
14492			var previous = this.previousValue(element);
14493			if (!this.settings.messages[element.name] ) {
14494				this.settings.messages[element.name] = {};
14495			}
14496			previous.originalMessage = this.settings.messages[element.name].remote;
14497			this.settings.messages[element.name].remote = previous.message;
14498
14499			param = typeof param === "string" && {url:param} || param;
14500
14501			if ( previous.old === value ) {
14502				return previous.valid;
14503			}
14504
14505			previous.old = value;
14506			var validator = this;
14507			this.startRequest(element);
14508			var data = {};
14509			data[element.name] = value;
14510			$.ajax($.extend(true, {
14511				url: param,
14512				mode: "abort",
14513				port: "validate" + element.name,
14514				dataType: "json",
14515				data: data,
14516				success: function( response ) {
14517					validator.settings.messages[element.name].remote = previous.originalMessage;
14518					var valid = response === true || response === "true";
14519					if ( valid ) {
14520						var submitted = validator.formSubmitted;
14521						validator.prepareElement(element);
14522						validator.formSubmitted = submitted;
14523						validator.successList.push(element);
14524						delete validator.invalid[element.name];
14525						validator.showErrors();
14526					} else {
14527						var errors = {};
14528						var message = response || validator.defaultMessage( element, "remote" );
14529						errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
14530						validator.invalid[element.name] = true;
14531						validator.showErrors(errors);
14532					}
14533					previous.valid = valid;
14534					validator.stopRequest(element, valid);
14535				}
14536			}, param));
14537			return "pending";
14538		},
14539
14540		// http://docs.jquery.com/Plugins/Validation/Methods/minlength
14541		minlength: function( value, element, param ) {
14542			var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element);
14543			return this.optional(element) || length >= param;
14544		},
14545
14546		// http://docs.jquery.com/Plugins/Validation/Methods/maxlength
14547		maxlength: function( value, element, param ) {
14548			var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element);
14549			return this.optional(element) || length <= param;
14550		},
14551
14552		// http://docs.jquery.com/Plugins/Validation/Methods/rangelength
14553		rangelength: function( value, element, param ) {
14554			var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element);
14555			return this.optional(element) || ( length >= param[0] && length <= param[1] );
14556		},
14557
14558		// http://docs.jquery.com/Plugins/Validation/Methods/min
14559		min: function( value, element, param ) {
14560			return this.optional(element) || value >= param;
14561		},
14562
14563		// http://docs.jquery.com/Plugins/Validation/Methods/max
14564		max: function( value, element, param ) {
14565			return this.optional(element) || value <= param;
14566		},
14567
14568		// http://docs.jquery.com/Plugins/Validation/Methods/range
14569		range: function( value, element, param ) {
14570			return this.optional(element) || ( value >= param[0] && value <= param[1] );
14571		},
14572
14573		// http://docs.jquery.com/Plugins/Validation/Methods/email
14574		email: function( value, element ) {
14575			return this.optional(element) || /^[^@]+@[^\.]+\..+$/.test(value);
14576		},
14577
14578		// http://docs.jquery.com/Plugins/Validation/Methods/url
14579		url: function( value, element ) {
14580			// contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
14581			return this.optional(element) || /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
14582		},
14583
14584		// http://docs.jquery.com/Plugins/Validation/Methods/date
14585		date: function( value, element ) {
14586			return this.optional(element) || !/Invalid|NaN/.test(new Date(value).toString());
14587		},
14588
14589		// http://docs.jquery.com/Plugins/Validation/Methods/dateISO
14590		dateISO: function( value, element ) {
14591			return this.optional(element) || /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value);
14592		},
14593
14594		// http://docs.jquery.com/Plugins/Validation/Methods/number
14595		number: function( value, element ) {
14596			return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value);
14597		},
14598
14599		// http://docs.jquery.com/Plugins/Validation/Methods/digits
14600		digits: function( value, element ) {
14601			return this.optional(element) || /^\d+$/.test(value);
14602		},
14603
14604		// http://docs.jquery.com/Plugins/Validation/Methods/creditcard
14605		// based on http://en.wikipedia.org/wiki/Luhn
14606		creditcard: function( value, element ) {
14607			if ( this.optional(element) ) {
14608				return "dependency-mismatch";
14609			}
14610			// accept only spaces, digits and dashes
14611			if ( /[^0-9 \-]+/.test(value) ) {
14612				return false;
14613			}
14614			var nCheck = 0,
14615				nDigit = 0,
14616				bEven = false;
14617
14618			value = value.replace(/\D/g, "");
14619
14620			for (var n = value.length - 1; n >= 0; n--) {
14621				var cDigit = value.charAt(n);
14622				nDigit = parseInt(cDigit, 10);
14623				if ( bEven ) {
14624					if ( (nDigit *= 2) > 9 ) {
14625						nDigit -= 9;
14626					}
14627				}
14628				nCheck += nDigit;
14629				bEven = !bEven;
14630			}
14631
14632			return (nCheck % 10) === 0;
14633		},
14634
14635		// http://docs.jquery.com/Plugins/Validation/Methods/equalTo
14636		equalTo: function( value, element, param ) {
14637			// bind to the blur event of the target in order to revalidate whenever the target field is updated
14638			// TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
14639			var target = $(param);
14640			if ( this.settings.onfocusout ) {
14641				target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
14642					$(element).valid();
14643				});
14644			}
14645			return value === target.val();
14646		}
14647
14648	}
14649
14650});
14651
14652// deprecated, use $.validator.format instead
14653$.format = $.validator.format;
14654
14655}(jQuery));
14656
14657// ajax mode: abort
14658// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
14659// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
14660(function($) {
14661	var pendingRequests = {};
14662	// Use a prefilter if available (1.5+)
14663	if ( $.ajaxPrefilter ) {
14664		$.ajaxPrefilter(function( settings, _, xhr ) {
14665			var port = settings.port;
14666			if ( settings.mode === "abort" ) {
14667				if ( pendingRequests[port] ) {
14668					pendingRequests[port].abort();
14669				}
14670				pendingRequests[port] = xhr;
14671			}
14672		});
14673	} else {
14674		// Proxy ajax
14675		var ajax = $.ajax;
14676		$.ajax = function( settings ) {
14677			var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode,
14678				port = ( "port" in settings ? settings : $.ajaxSettings ).port;
14679			if ( mode === "abort" ) {
14680				if ( pendingRequests[port] ) {
14681					pendingRequests[port].abort();
14682				}
14683				return (pendingRequests[port] = ajax.apply(this, arguments));
14684			}
14685			return ajax.apply(this, arguments);
14686		};
14687	}
14688}(jQuery));
14689
14690// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
14691// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
14692(function($) {
14693	$.extend($.fn, {
14694		validateDelegate: function( delegate, type, handler ) {
14695			return this.bind(type, function( event ) {
14696				var target = $(event.target);
14697				if ( target.is(delegate) ) {
14698					return handler.apply(target, arguments);
14699				}
14700			});
14701		}
14702	});
14703}(jQuery));
14704/*!
14705* jQuery File Download Plugin v1.4.3 
14706*
14707* http://www.johnculviner.com
14708*
14709* Copyright (c) 2013 - John Culviner
14710*
14711* Licensed under the MIT license:
14712*   http://www.opensource.org/licenses/mit-license.php
14713*
14714* !!!!NOTE!!!!
14715* You must also write a cookie in conjunction with using this plugin as mentioned in the orignal post:
14716* http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/
14717* !!!!NOTE!!!!
14718*/
14719
14720
14721(function($, window){
14722	// i'll just put them here to get evaluated on script load
14723	var htmlSpecialCharsRegEx = /[<>&\r\n"']/gm;
14724	var htmlSpecialCharsPlaceHolders = {
14725				'<': 'lt;',
14726				'>': 'gt;',
14727				'&': 'amp;',
14728				'\r': "#13;",
14729				'\n': "#10;",
14730				'"': 'quot;',
14731				"'": '#39;' /*single quotes just to be safe, IE8 doesn't support &apos;, so use &#39; instead */
14732	};
14733
14734$.extend({
14735    //
14736    //$.fileDownload('/path/to/url/', options)
14737    //  see directly below for possible 'options'
14738    fileDownload: function (fileUrl, options) {
14739
14740        //provide some reasonable defaults to any unspecified options below
14741        var settings = $.extend({
14742
14743            //
14744            //Requires jQuery UI: provide a message to display to the user when the file download is being prepared before the browser's dialog appears
14745            //
14746            preparingMessageHtml: null,
14747
14748            //
14749            //Requires jQuery UI: provide a message to display to the user when a file download fails
14750            //
14751            failMessageHtml: null,
14752
14753            //
14754            //the stock android browser straight up doesn't support file downloads initiated by a non GET: http://code.google.com/p/android/issues/detail?id=1780
14755            //specify a message here to display if a user tries with an android browser
14756            //if jQuery UI is installed this will be a dialog, otherwise it will be an alert
14757            //Set to null to disable the message and attempt to download anyway
14758            //
14759            androidPostUnsupportedMessageHtml: "Unfortunately your Android browser doesn't support this type of file download. Please try again with a different browser.",
14760
14761            //
14762            //Requires jQuery UI: options to pass into jQuery UI Dialog
14763            //
14764            dialogOptions: { modal: true },
14765
14766            //
14767            //a function to call while the dowload is being prepared before the browser's dialog appears
14768            //Args:
14769            //  url - the original url attempted
14770            //
14771            prepareCallback: function (url) { },
14772
14773            //
14774            //a function to call after a file download dialog/ribbon has appeared
14775            //Args:
14776            //  url - the original url attempted
14777            //
14778            successCallback: function (url) { },
14779
14780            //
14781            //a function to call after a file download dialog/ribbon has appeared
14782            //Args:
14783            //  responseHtml    - the html that came back in response to the file download. this won't necessarily come back depending on the browser.
14784            //                      in less than IE9 a cross domain error occurs because 500+ errors cause a cross domain issue due to IE subbing out the
14785            //                      server's error message with a "helpful" IE built in message
14786            //  url             - the original url attempted
14787            //
14788            failCallback: function (responseHtml, url) { },
14789
14790            //
14791            // the HTTP method to use. Defaults to "GET".
14792            //
14793            httpMethod: "GET",
14794
14795            //
14796            // if specified will perform a "httpMethod" request to the specified 'fileUrl' using the specified data.
14797            // data must be an object (which will be $.param seri
14797alized) or already a key=value param string
14798            //
14799            data: null,
14800
14801            //
14802            //a period in milliseconds to poll to determine if a successful file download has occured or not
14803            //
14804            checkInterval: 100,
14805
14806            //
14807            //the cookie name to indicate if a file download has occured
14808            //
14809            cookieName: "fileDownload",
14810
14811            //
14812            //the cookie value for the above name to indicate that a file download has occured
14813            //
14814            cookieValue: "true",
14815
14816            //
14817            //the cookie path for above name value pair
14818            //
14819            cookiePath: "/",
14820
14821            //
14822            //if specified it will be used when attempting to clear the above name value pair
14823            //useful for when downloads are being served on a subdomain (e.g. downloads.example.com)
14824            //	
14825            cookieDomain: null,
14826
14827            //
14828            //the title for the popup second window as a download is processing in the case of a mobile browser
14829            //
14830            popupWindowTitle: "Initiating file download...",
14831
14832            //
14833            //Functionality to encode HTML entities for a POST, need this if data is an object with properties whose values contains strings with quotation marks.
14834            //HTML entity encoding is done by replacing all &,<,>,',",\r,\n characters.
14835            //Note that some browsers will POST the string htmlentity-encoded whilst others will decode it before POSTing.
14836            //It is recommended that on the server, htmlentity decoding is done irrespective.
14837            //
14838            encodeHTMLEntities: true
14839            
14840        }, options);
14841
14842        var deferred = new $.Deferred();
14843
14844        //Setup mobile browser detection: Partial credit: http://detectmobilebrowser.com/
14845        var userAgent = (navigator.userAgent || navigator.vendor || window.opera).toLowerCase();
14846
14847        var isIos;                  //has full support of features in iOS 4.0+, uses a new window to accomplish this.
14848        var isAndroid;              //has full support of GET features in 4.0+ by using a new window. Non-GET is completely unsupported by the browser. See above for specifying a message.
14849        var isOtherMobileBrowser;   //there is no way to reliably guess here so all other mobile devices will GET and POST to the current window.
14850
14851        if (/ip(ad|hone|od)/.test(userAgent)) {
14852
14853            isIos = true;
14854
14855        } else if (userAgent.indexOf('android') !== -1) {
14856
14857            isAndroid = true;
14858
14859        } else {
14860
14861            isOtherMobileBrowser = /avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|playbook|silk|iemobile|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|
14861sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4));
14862
14863        }
14864
14865        var httpMethodUpper = settings.httpMethod.toUpperCase();
14866
14867        if (isAndroid && httpMethodUpper !== "GET" && settings.androidPostUnsupportedMessageHtml) {
14868            //the stock android browser straight up doesn't support file downloads initiated by non GET requests: http://code.google.com/p/android/issues/detail?id=1780
14869
14870            if ($().dialog) {
14871                $("<div>").html(settings.androidPostUnsupportedMessageHtml).dialog(settings.dialogOptions);
14872            } else {
14873                alert(settings.androidPostUnsupportedMessageHtml);
14874            }
14875
14876            return deferred.reject();
14877        }
14878
14879        var $preparingDialog = null;
14880
14881        var internalCallbacks = {
14882
14883            onPrepare: function (url) {
14884
14885                //wire up a jquery dialog to display the preparing message if specified
14886                if (settings.preparingMessageHtml) {
14887
14888                    $preparingDialog = $("<div>").html(settings.preparingMessageHtml).dialog(settings.dialogOptions);
14889
14890                } else if (settings.prepareCallback) {
14891
14892                    settings.prepareCallback(url);
14893
14894                }
14895
14896            },
14897
14898            onSuccess: function (url) {
14899
14900                //remove the perparing message if it was specified
14901                if ($preparingDialog) {
14902                    $preparingDialog.dialog('close');
14903                };
14904
14905                settings.successCallback(url);
14906
14907                deferred.resolve(url);
14908            },
14909
14910            onFail: function (responseHtml, url) {
14911
14912                //remove the perparing message if it was specified
14913                if ($preparingDialog) {
14914                    $preparingDialog.dialog('close');
14915                };
14916
14917                //wire up a jquery dialog to display the fail message if specified
14918                if (settings.failMessageHtml) {
14919                    $("<div>").html(settings.failMessageHtml).dialog(settings.dialogOptions);
14920                }
14921
14922                settings.failCallback(responseHtml, url);
14923                
14924                deferred.reject(responseHtml, url);
14925            }
14926        };
14927
14928        internalCallbacks.onPrepare(fileUrl);
14929
14930        //make settings.data a param string if it exists and isn't already
14931        if (settings.data !== null && typeof settings.data !== "string") {
14932            settings.data = $.param(settings.data);
14933        }
14934
14935
14936        var $iframe,
14937            downloadWindow,
14938            formDoc,
14939            $form;
14940
14941        if (httpMethodUpper === "GET") {
14942
14943            if (settings.data !== null) {
14944                //need to merge any fileUrl params with the data object
14945
14946                var qsStart = fileUrl.indexOf('?');
14947
14948                if (qsStart !== -1) {
14949                    //we have a querystring in the url
14950
14951                    if (fileUrl.substring(fileUrl.length - 1) !== "&") {
14952                        fileUrl = fileUrl + "&";
14953                    }
14954                } else {
14955
14956                    fileUrl = fileUrl + "?";
14957                }
14958
14959                fileUrl = fileUrl + settings.data;
14960            }
14961
14962            if (isIos || isAndroid) {
14963
14964                downloadWindow = window.open(fileUrl);
14965                downloadWindow.document.title = settings.popupWindowTitle;
14966                window.focus();
14967
14968            } else if (isOtherMobileBrowser) {
14969
14970                window.location(fileUrl);
14971
14972            } else {
14973
14974                //create a temporary iframe that is used to request the fileUrl as a GET request
14975                $iframe = $("<iframe>")
14976                    .hide()
14977                    .prop("src", fileUrl)
14978                    .appendTo("body");
14979            }
14980
14981        } else {
14982
14983            var formInnerHtml = "";
14984
14985            if (settings.data !== null) {
14986
14987                $.each(settings.data.replace(/\+/g, ' ').split("&"), function () {
14988
14989                    var kvp = this.split("=");
14990
14991                    var key = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[0])) : decodeURIComponent(kvp[0]);
14992                    if (key) {
14993                        var value = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[1])) : decodeURIComponent(kvp[1]);
14994                    formInnerHtml += '<input type="hidden" name="' + key + '" value="' + value + '" />';
14995                    }
14996                });
14997            }
14998
14999            if (isOtherMobileBrowser) {
15000
15001                $form = $("<form>").appendTo("body");
15002                $form.hide()
15003                    .prop('method', settings.httpMethod)
15004                    .prop('action', fileUrl)
15005                    .html(formInnerHtml);
15006
15007            } else {
15008
15009                if (isIos) {
15010
15011                    downloadWindow = window.open("about:blank");
15012                    downloadWindow.document.title = settings.popupWindowTitle;
15013                    formDoc = downloadWindow.document;
15014                    window.focus();
15015
15016                } else {
15017
15018                    $iframe = $("<iframe style='display: none' src='about:blank'></iframe>").appendTo("body");
15019                    formDoc = getiframeDocument($iframe);
15020                }
15021
15022                formDoc.write("<html><head></head><body><form method='" + settings.httpMethod + "' action='" + fileUrl + "'>" + formInnerHtml + "</form>" + settings.popupWindowTitle + "</body></html>");
15023                $form = $(formDoc).find('form');
15024            }
15025
15026            $form.submit();
15027        }
15028
15029
15030        //check if the file download has completed every checkInterval ms
15031        setTimeout(checkFileDownloadComplete, settings.checkInterval);
15032
15033
15034        function checkFileDownloadComplete() {
15035            //has the cookie been written due to a file download occuring?
15036            if (document.cookie.indexOf(settings.cookieName + "=" + settings.cookieValue) != -1) {
15037
15038                //execute specified callback
15039                internalCallbacks.onSuccess(fileUrl);
15040
15041                //remove cookie
15042                var cookieData = settings.cookieName + "=; path=" + settings.cookiePath + "; expires=" + new Date(0).toUTCString() + ";";
15043                if (settings.cookieDomain) cookieData += " domain=" + settings.cookieDomain + ";";
15044                document.cookie = cookieData;
15045
15046                //remove iframe
15047                cleanUp(false);
15048
15049                return;
15050            }
15051
15052            //has an error occured?
15053            //if neither containers exist below then the file download is occuring on the current window
15054            if (downloadWindow || $iframe) {
15055
15056                //has an error occured?
15057                try {
15058
15059                    var formDoc = downloadWindow ? downloadWindow.document : getiframeDocument($iframe);
15060
15061                    if (formDoc && formDoc.body != null && formDoc.body.innerHTML.length) {
15062
15063                        var isFailure = true;
15064
15065                        if ($form && $form.length) {
15066                            var $contents = $(formDoc.body).contents().first();
15067
15068                            try {
15069                                if ($contents.length && $contents[0] === $form[0]) {
15070                                    isFailure = false;
15071                                }
15072                            } catch (e) {
15073                                if (e && e.number == -2146828218) {
15074                                    // IE 8-10 throw a permission denied after the form reloads on the "$contents[0] === $form[0]" comparison
15075                                    isFailure = true;
15076                                } else {
15077                                    throw e;
15078                                }
15079                            } 
15080                        }
15081
15082                        if (isFailure) {
15083                            // IE 8-10 don't always have the full content available right away, they need a litle bit to finish
15084                            setTimeout(function () {
15085                                internalCallbacks.onFail(formDoc.body.innerHTML, fileUrl);
15086                                cleanUp(true);
15087                            }, 100);
15088                            
15089                            return;
15090                        }
15091                    }
15092                }
15093                catch (err) {
15094
15095                    //500 error less than IE9
15096                    internalCallbacks.onFail('', fileUrl);
15097
15098                    cleanUp(true);
15099
15100                    return;
15101                }
15102            }
15103
15104
15105            //keep checking...
15106            setTimeout(checkFileDownloadComplete, settings.checkInterval);
15107        }
15108
15109        //gets an iframes document in a cross browser compatible manner
15110        function getiframeDocument($iframe) {
15111            var iframeDoc = $iframe[0].contentWindow || $iframe[0].contentDocument;
15112            if (iframeDoc.document) {
15113                iframeDoc = iframeDoc.document;
15114            }
15115            return iframeDoc;
15116        }
15117
15118        function cleanUp(isFailure) {
15119
15120            setTimeout(function() {
15121
15122                if (downloadWindow) {
15123
15124                    if (isAndroid) {
15125                        downloadWindow.close();
15126                    }
15127
15128                    if (isIos) {
15129                        if (downloadWindow.focus) {
15130                            downloadWindow.focus(); //ios safari bug doesn't allow a window to be closed unless it is focused
15131                            if (isFailure) {
15132                                downloadWindow.close();
15133                            }
15134                        }
15135                    }
15136                }
15137                
15138                //iframe cleanup appears to randomly cause the download to fail
15139                //not doing it seems better than failure...
15140                //if ($iframe) {
15141                //    $iframe.remove();
15142                //}
15143
15144            }, 0);
15145        }
15146
15147
15148        function htmlSpecialCharsEntityEncode(str) {
15149            return str.replace(htmlSpecialCharsRegEx, function(match) {
15150                return '&' + htmlSpecialCharsPlaceHolders[match];
15151        	});
15152        }
15153        var promise = deferred.promise();
15154        promise.abort = function() {
15155            cleanUp();
15156            $iframe.remove();
15157        };
15158        return promise;
15159    }
15160});
15161
15162})(jQuery, this);
15163// I18n.js
15164// =======
15165//
15166// This small library provides the Rails I18n API on the Javascript.
15167// You don't actually have to use Rails (or even Ruby) to use I18n.js.
15168// Just make sure you export all translations in an object like this:
15169//
15170//     I18n.translations.en = {
15171//       hello: "Hello World"
15172//     };
15173//
15174// See tests for specific formatting like numbers and dates.
15175//
15176
15177// Using UMD pattern from
15178// https://github.com/umdjs/umd#regular-module
15179// `returnExports.js` version
15180;(function (root, factory) {
15181  if (typeof define === 'function' && define.amd) {
15182    // AMD. Register as an anonymous module.
15183    define("i18n", function(){ return factory(root);});
15184  } else if (typeof module === 'object' && module.exports) {
15185    // Node. Does not work with strict CommonJS, but
15186    // only CommonJS-like environments that support module.exports,
15187    // like Node.
15188    module.exports = factory(root);
15189  } else {
15190    // Browser globals (root is window)
15191    root.I18n = factory(root);
15192  }
15193}(this, function(global) {
15194  "use strict";
15195
15196  // Use previously defined object if exists in current scope
15197  var I18n = global && global.I18n || {};
15198
15199  // Just cache the Array#slice function.
15200  var slice = Array.prototype.slice;
15201
15202  // Apply number padding.
15203  var padding = function(number) {
15204    return ("0" + number.toString()).substr(-2);
15205  };
15206
15207  // Improved toFixed number rounding function with support for unprecise floating points
15208  // JavaScript's standard toFixed function does not round certain 
15208numbers correctly (for example 0.105 with precision 2).
15209  var toFixed = function(number, precision) {
15210    return decimalAdjust('round', number, -precision).toFixed(precision);
15211  };
15212
15213  // Is a given variable an object?
15214  // Borrowed from Underscore.js
15215  var isObject = function(obj) {
15216    var type = typeof obj;
15217    return type === 'function' || type === 'object'
15218  };
15219
15220  var isFunction = function(func) {
15221    var type = typeof func;
15222    return type === 'function'
15223  };
15224
15225  // Check if value is different than undefined and null;
15226  var isSet = function(value) {
15227    return typeof(value) !== 'undefined' && value !== null;
15228  };
15229
15230  // Is a given value an array?
15231  // Borrowed from Underscore.js
15232  var isArray = function(val) {
15233    if (Array.isArray) {
15234      return Array.isArray(val);
15235    }
15236    return Object.prototype.toString.call(val) === '[object Array]';
15237  };
15238
15239  var isString = function(val) {
15240    return typeof val === 'string' || Object.prototype.toString.call(val) === '[object String]';
15241  };
15242
15243  var isNumber = function(val) {
15244    return typeof val === 'number' || Object.prototype.toString.call(val) === '[object Number]';
15245  };
15246
15247  var isBoolean = function(val) {
15248    return val === true || val === false;
15249  };
15250
15251  var isNull = function(val) {
15252    return val === null;
15253  };
15254
15255  var decimalAdjust = function(type, value, exp) {
15256    // If the exp is undefined or zero...
15257    if (typeof exp === 'undefined' || +exp === 0) {
15258      return Math[type](value);
15259    }
15260    value = +value;
15261    exp = +exp;
15262    // If the value is not a number or the exp is not an integer...
15263    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
15264      return NaN;
15265    }
15266    // Shift
15267    value = value.toString().split('e');
15268    value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
15269    // Shift back
15270    value = value.toString().split('e');
15271    return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
15272  };
15273
15274  var lazyEvaluate = function(message, scope) {
15275    if (isFunction(message)) {
15276      return message(scope);
15277    } else {
15278      return message;
15279    }
15280  };
15281
15282  var merge = function (dest, obj) {
15283    var key, value;
15284    for (key in obj) if (obj.hasOwnProperty(key)) {
15285      value = obj[key];
15286      if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value) || isNull(value)) {
15287        dest[key] = value;
15288      } else {
15289        if (dest[key] == null) dest[key] = {};
15290        merge(dest[key], value);
15291      }
15292    }
15293    return dest;
15294  };
15295
15296  // Set default days/months translations.
15297  var DATE = {
15298      day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
15299    , abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
15300    , month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
15300November", "December"]
15301    , abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
15302    , meridian: ["AM", "PM"]
15303  };
15304
15305  // Set default number format.
15306  var NUMBER_FORMAT = {
15307      precision: 3
15308    , separator: "."
15309    , delimiter: ","
15310    , strip_insignificant_zeros: false
15311  };
15312
15313  // Set default currency format.
15314  var CURRENCY_FORMAT = {
15315      unit: "$"
15316    , precision: 2
15317    , format: "%u%n"
15318    , sign_first: true
15319    , delimiter: ","
15320    , separator: "."
15321  };
15322
15323  // Set default percentage format.
15324  var PERCENTAGE_FORMAT = {
15325      unit: "%"
15326    , precision: 3
15327    , format: "%n%u"
15328    , separator: "."
15329    , delimiter: ""
15330  };
15331
15332  // Set default size units.
15333  var SIZE_UNITS = [null, "kb", "mb", "gb", "tb"];
15334
15335  // Other default options
15336  var DEFAULT_OPTIONS = {
15337    // Set default locale. This locale will be used when fallback is enabled and
15338    // the translation doesn't exist in a particular locale.
15339      defaultLocale: "en"
15340    // Set the current locale to `en`.
15341    , locale: "en"
15342    // Set the translation key separator.
15343    , defaultSeparator: "."
15344    // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
15345    , placeholder: /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm
15346    // Set if engine should fallback to the default locale when a translation
15347    // is missing.
15348    , fallbacks: false
15349    // Set the default translation object.
15350    , translations: {}
15351    // Set missing translation behavior. 'message' will display a message
15352    // that the translation is missing, 'guess' will try to guess the string
15353    , missingBehaviour: 'message'
15354    // if you use missingBehaviour with 'message', but want to know that the
15355    // string is actually missing for testing purposes, you can prefix the
15356    // guessed string by setting the value here. By default, no prefix!
15357    , missingTranslationPrefix: ''
15358  };
15359
15360  // Set default locale. This locale will be used when fallback is enabled and
15361  // the translation doesn't exist in a particular locale.
15362  I18n.reset = function() {
15363    var key;
15364    for (key in DEFAULT_OPTIONS) {
15365      this[key] = DEFAULT_OPTIONS[key];
15366    }
15367  };
15368
15369  // Much like `reset`, but only assign options if not already assigned
15370  I18n.initializeOptions = function() {
15371    var key;
15372    for (key in DEFAULT_OPTIONS) if (!isSet(this[key])) {
15373      this[key] = DEFAULT_OPTIONS[key];
15374    }
15375  };
15376  I18n.initializeOptions();
15377
15378  // Return a list of all locales that must be tried before returning the
15379  // missing translation message. By default, this will consider the inline option,
15380  // current locale and fallback locale.
15381  //
15382  //     I18n.locales.get("de-DE");
15383  //     // ["de-DE", "de", "en"]
15384  //
15385  // You can define custom rules for any locale. Just make sure you return a array
15386  // containing all locales.
15387  //
15388  //     // Default the Wookie locale to English.
15389  //     I18n.locales["wk"] = function(locale) {
15390  //       return ["en"];
15391  //     };
15392  //
15393  I18n.locales = {};
15394
15395  // Retrieve locales based on inline locale, current locale or default to
15396  // I18n's detection.
15397  I18n.locales.get = function(locale) {
15398    var result = this[locale] || this[I18n.locale] || this["default"];
15399
15400    if (isFunction(result)) {
15401      result = result(locale);
15402    }
15403
15404    if (isArray(result) === false) {
15405      result = [result];
15406    }
15407
15408    return result;
15409  };
15410
15411  // The default locale list.
15412  I18n.locales["default"] = function(locale) {
15413    var locales = []
15414      , list = []
15415    ;
15416
15417    // Handle the inline locale option that can be provided to
15418    // the `I18n.t` options.
15419    if (locale) {
15420      locales.push(locale);
15421    }
15422
15423    // Add the current locale to the list.
15424    if (!locale && I18n.locale) {
15425      locales.push(I18n.locale);
15426    }
15427
15428    // Add the default locale if fallback strategy is enabled.
15429    if (I18n.fallbacks && I18n.defaultLocale) {
15430      locales.push(I18n.defaultLocale);
15431    }
15432
15433    // Locale code format 1:
15434    // According to RFC4646 (https://www.ietf.org/rfc/rfc4646.txt)
15435    // language codes for Traditional Chinese should be `zh-Hant`
15436    //
15437    // But due to backward compatibility
15438    // We use older version of IETF language tag
15439    // @see https://www.w3.org/TR/html401/struct/dirlang.html
15440    // @see https://en.wikipedia.org/wiki/IETF_language_tag
15441    //
15442    // Format: `language-code = primary-code ( "-" subcode )*`
15443    //
15444    // primary-code uses ISO639-1
15445    // @see https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
15446    // @see https://www.iso.org/iso/home/standards/language_codes.htm
15447    //
15448    // subcode uses ISO 3166-1 alpha-2
15449    // @see https://en.wikipedia.org/wiki/ISO_3166
15450    // @see https://www.iso.org/iso/country_codes.htm
15451    //
15452    // @note
15453    //   subcode can be in upper case or lower case
15454    //   defining it in upper case is a convention only
15455
15456
15457    // Locale code format 2:
15458    // Format: `code = primary-code ( "-" region-code )*`
15459    // primary-code uses ISO 639-1
15460    // script-code uses ISO 15924
15461    // region-code uses ISO 3166-1 alpha-2
15462    // Example: zh-Hant-TW, en-HK, zh-Hant-CN
15463    //
15464    // It is similar to RFC4646 (or actually the same),
15465    // but seems to be limited to language, script, region
15466
15467    // Compute each locale with its country code.
15468    // So this will return an array containing
15469    // `de-DE` and `de`
15470    // or
15471    // `zh-hans-tw`, `zh-hans`, `zh`
15472    // locales.
15473    locales.forEach(function(locale) {
15474      var localeParts = locale.split("-");
15475      var firstFallback = null;
15476      var secondFallback = null;
15477      if (localeParts.length === 3) {
15478        firstFallback = [
15479          localeParts[0],
15480          localeParts[1]
15481        ].join("-");
15482        secondFallback = localeParts[0];
15483      }
15484      else if (localeParts.length === 2) {
15485        firstFallback = localeParts[0];
15486      }
15487
15488      if (list.indexOf(locale) === -1) {
15489        list.push(locale);
15490      }
15491
15492      if (! I18n.fallbacks) {
15493        return;
15494      }
15495
15496      [
15497        firstFallback,
15498        secondFallback
15499      ].forEach(function(nullableFallbackLocale) {
15500        // We don't want null values
15501        if (typeof nullableFallbackLocale === "undefined") { return; }
15502        if (nullableFallbackLocale === null) { return; }
15503        // We don't want duplicate values
15504        //
15505        // Comparing with `locale` first is faster than
15506        // checking whether value's presence in the list
15507        if (nullableFallbackLocale === locale) { return; }
15508        if (list.indexOf(nullableFallbackLocale) !== -1) { return; }
15509
15510        list.push(nullableFallbackLocale);
15511      });
15512    });
15513
15514    // No locales set? English it is.
15515    if (!locales.length) {
15516      locales.push("en");
15517    }
15518
15519    return list;
15520  };
15521
15522  // Hold pluralization rules.
15523  I18n.pluralization = {};
15524
15525  // Return the pluralizer for a specific locale.
15526  // If no specify locale is found, then I18n's default will be used.
15527  I18n.pluralization.get = function(locale) {
15528    return this[locale] || this[I18n.locale] || this["default"];
15529  };
15530
15531  // The default pluralizer rule.
15532  // It detects the `zero`, `one`, and `other` scopes.
15533  I18n.pluralization["default"] = function(count) {
15534    switch (count) {
15535      case 0: return ["zero", "other"];
15536      case 1: return ["one"];
15537      default: return ["other"];
15538    }
15539  };
15540
15541  // Return current locale. If no locale has been set, then
15542  // the current locale will be the default locale.
15543  I18n.currentLocale = function() {
15544    return this.locale || this.defaultLocale;
15545  };
15546
15547  // Check if value is different than undefined and null;
15548  I18n.isSet = isSet;
15549
15550  // Find and process the translation using the provided scope and options.
15551  // This is used internally by some functions and should not be used as an
15552  // public API.
15553  I18n.lookup = function(scope, options) {
15554    options = options || {};
15555
15556    var locales = this.locales.get(options.locale).slice()
15557      , locale
15558      , scopes
15559      , fullScope
15560      , translations
15561    ;
15562
15563    fullScope = this.getFullScope(scope, options);
15564
15565    while (locales.length) {
15566      locale = locales.shift();
15567      scopes = fullScope.split(options.separator || this.defaultSeparator);
15568      translations = this.translations[locale];
15569
15570      if (!translations) {
15571        continue;
15572      }
15573      while (scopes.length) {
15574        translations = translations[scopes.shift()];
15575
15576        if (translations === undefined || translations === null) {
15577          break;
15578        }
15579      }
15580
15581      if (translations !== undefined && translations !== null) {
15582        return translations;
15583      }
15584    }
15585
15586    if (isSet(options.defaultValue)) {
15587      return lazyEvaluate(options.defaultValue, scope);
15588    }
15589  };
15590
15591  // lookup pluralization rule key into translations
15592  I18n.pluralizationLookupWithoutFallback = function(count, locale, translations) {
15593    var pluralizer = this.pluralization.get(locale)
15594      , pluralizerKeys = pluralizer(count)
15595      , pluralizerKey
15596      , message;
15597
15598    if (translations && isObject(translations)) {
15599      while (pluralizerKeys.length) {
15600        pluralizerKey = pluralizerKeys.shift();
15601        if (isSet(translations[pluralizerKey])) {
15602          message = translations[pluralizerKey];
15603          break;
15604        }
15605      }
15606    }
15607
15608    return message;
15609  };
15610
15611  // Lookup dedicated to pluralization
15612  I18n.pluralizationLookup = function(count, scope, options) {
15613    options = options || {};
15614    var locales = this.locales.get(options.locale).slice()
15615      , locale
15616      , scopes
15617      , translations
15618      , message
15619    ;
15620    scope = this.getFullScope(scope, options);
15621
15622    while (locales.length) {
15623      locale = locales.shift();
15624      scopes = scope.split(options.separator || this.defaultSeparator);
15625      translations = this.translations[locale];
15626
15627      if (!translations) {
15628        continue;
15629      }
15630
15631      while (scopes.length) {
15632        translations = translations[scopes.shift()];
15633        if (!isObject(translations)) {
15634          break;
15635        }
15636        if (scopes.length === 0) {
15637          message = this.pluralizationLookupWithoutFallback(count, locale, translations);
15638        }
15639      }
15640      if (typeof message !== "undefined" && message !== null) {
15641        break;
15642      }
15643    }
15644
15645    if (typeof message === "undefined" || message === null) {
15646      if (isSet(options.defaultValue)) {
15647        if (isObject(options.defaultValue)) {
15648          message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue);
15649        } else {
15650          message = options.defaultValue;
15651        }
15652        translations = options.defaultValue;
15653      }
15654    }
15655
15656    return { message: message, translations: translations };
15657  };
15658
15659  // Rails changed the way the meridian is stored.
15660  // It started with `date.meridian` returning an array,
15661  // then it switched to `time.am` and `time.pm`.
15662  // This function abstracts this difference and returns
15663  // the correct meridian or the default value when none is provided.
15664  I18n.meridian = function() {
15665    var time = this.lookup("time");
15666    var date = this.lookup("date");
15667
15668    if (time && time.am && time.pm) {
15669      return [time.am, time.pm];
15670    } else if (date && date.meridian) {
15671      return date.meridian;
15672    } else {
15673      return DATE.meridian;
15674    }
15675  };
15676
15677  // Merge serveral hash options, checking if value is set before
15678  // overwriting any value. The precedence is from left to right.
15679  //
15680  //     I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
15681  //     #=> {name: "John Doe", role: "user"}
15682  //
15683  I18n.prepareOptions = function() {
15684    var args = slice.call(arguments)
15685      , options = {}
15686      , subject
15687    ;
15688
15689    while (args.length) {
15690      subject = args.shift();
15691
15692      if (typeof(subject) != "object") {
15693        continue;
15694      }
15695
15696      for (var attr in subject) {
15697        if (!subject.hasOwnProperty(attr)) {
15698          continue;
15699        }
15700
15701        if (isSet(options[attr])) {
15702          continue;
15703        }
15704
15705        options[attr] = subject[attr];
15706      }
15707    }
15708
15709    return options;
15710  };
15711
15712  // Generate a list of translation options for default fallbacks.
15713  // `defaultValue` is also deleted from options as it is returned as part of
15714  // the translationOptions array.
15715  I18n.createTranslationOptions = function(scope, options) {
15716    var translationOptions = [{scope: scope}];
15717
15718    // Defaults should be an array of hashes containing either
15719    // fallback scopes or messages
15720    if (isSet(options.defaults)) {
15721      translationOptions = translationOptions.concat(options.defaults);
15722    }
15723
15724    // Maintain support for defaultValue. Since it is always a message
15725    // insert it in to the translation options as such.
15726    if (isSet(options.defaultValue)) {
15727      translationOptions.push({ message: options.defaultValue });
15728    }
15729
15730    return translationOptions;
15731  };
15732
15733  // Translate the given scope with the provided options.
15734  I18n.translate = function(scope, options) {
15735    options = options || {};
15736
15737    var translationOptions = this.createTranslationOptions(scope, options);
15738
15739    var translation;
15740    var usedScope = scope;
15741
15742    var optionsWithoutDefault = this.prepareOptions(options)
15743    delete optionsWithoutDefault.defaultValue
15744
15745    // Iterate through the translation options until a translation
15746    // or message is found.
15747    var translationFound =
15748      translationOptions.some(function(translationOption) {
15749        if (isSet(translationOption.scope)) {
15750          usedScope = translationOption.scope;
15751          translation = this.lookup(usedScope, optionsWithoutDefault);
15752        } else if (isSet(translationOption.message)) {
15753          translation = lazyEvaluate(translationOption.message, scope);
15754        }
15755
15756        if (translation !== undefined && translation !== null) {
15757          return true;
15758        }
15759      }, this);
15760
15761    if (!translationFound) {
15762      return this.missingTranslation(scope, options);
15763    }
15764
15765    if (typeof(translation) === "string") {
15766      translation = this.interpolate(translation, options);
15767    } else if (isArray(translation)) {
15768      translation = translation.map(function(t) {
15769        return (typeof(t) === "string" ? this.interpolate(t, options) : t);
15770      }, this);
15771    } else if (isObject(translation) && isSet(options.count)) {
15772      translation = this.pluralize(options.count, usedScope, options);
15773    }
15774
15775    return translation;
15776  };
15777
15778  // This function interpolates the all variables in the given message.
15779  I18n.interpolate = function(message, options) {
15780    if (message == null) {
15781      return message;
15782    }
15783
15784    options = options || {};
15785    var matches = message.match(this.placeholder)
15786      , placeholder
15787      , value
15788      , name
15789      , regex
15790    ;
15791
15792    if (!matches) {
15793      return message;
15794    }
15795
15796    while (matches.length) {
15797      placeholder = matches.shift();
15798      name = placeholder.replace(this.placeholder, "$1");
15799
15800      if (isSet(options[name])) {
15801        value = options[name].toString().replace(/\$/gm, "_#$#_");
15802      } else if (name in options) {
15803        value = this.nullPlaceholder(placeholder, message, options);
15804      } else {
15805        value = this.missingPlaceholder(placeholder, message, options);
15806      }
15807
15808      regex = new RegExp(placeholder.replace(/{/gm, "\\{").replace(/}/gm, "\\}"));
15809      message = message.replace(regex, value);
15810    }
15811
15812    return message.replace(/_#\$#_/g, "$");
15813  };
15814
15815  // Pluralize the given scope using the `count` value.
15816  // The pluralized translation may have other placeholders,
15817  // which will be retrieved from `options`.
15818  I18n.pluralize = function(count, scope, options) {
15819    options = this.prepareOptions({count: String(count)}, options)
15820    var pluralizer, result;
15821
15822    result = this.pluralizationLookup(count, scope, options);
15823    if (typeof result.translations === "undefined" || result.translations == null) {
15824      return this.missingTranslation(scope, options);
15825    }
15826
15827    if (typeof result.message !== "undefined" && result.message != null) {
15828      return this.interpolate(result.message, options);
15829    }
15830    else {
15831      pluralizer = this.pluralization.get(options.locale);
15832      return this.missingTranslation(scope + '.' + pluralizer(count)[0], options);
15833    }
15834  };
15835
15836  // Return a missing translation message for the given parameters.
15837  I18n.missingTranslation = function(scope, options) {
15838    //guess intended string
15839    if(this.missingBehaviour === 'guess'){
15840      //get only the last portion of the scope
15841      var s = scope.split('.').slice(-1)[0];
15842      //replace underscore with space && camelcase with space and lowercase letter
15843      return (this.missingTranslationPrefix.length > 0 ? this.missingTranslationPrefix : '') +
15844          s.replace(/_/g,' ').replace(/([a-z])([A-Z])/g,
15845          function(match, p1, p2) {return p1 + ' ' + p2.toLowerCase()} );
15846    }
15847
15848    var localeForTranslation = (options != null && options.locale != null) ? options.locale : this.currentLocale();
15849    var fullScope           = this.getFullScope(scope, options);
15850    var fullScopeWithLocale = [localeForTranslation, fullScope].join(options.separator || this.defaultSeparator);
15851
15852    return '[missing "' + fullScopeWithLocale + '" translation]';
15853  };
15854
15855  // Return a missing placeholder message for given parameters
15856  I18n.missingPlaceholder = function(placeholder, message, options) {
15857    return "[missing " + placeholder + " value]";
15858  };
15859
15860  I18n.nullPlaceholder = function() {
15861    return I18n.missingPlaceholder.apply(I18n, arguments);
15862  };
15863
15864  // Format number using localization rules.
15865  // The options will be retrieved from the `number.format` scope.
15866  // If this isn't present, then the following options will be used:
15867  //
15868  // - `precision`: `3`
15869  // - `separator`: `"."`
15870  // - `delimiter`: `","`
15871  // - `strip_insignificant_zeros`: `false`
15872  //
15873  // You can also override these options by providing the `options` argument.
15874  //
15875  I18n.toNumber = function(number, options) {
15876    options = this.prepareOptions(
15877        options
15878      , this.lookup("number.format")
15879      , NUMBER_FORMAT
15880    );
15881
15882    var negative = number < 0
15883      , string = toFixed(Math.abs(number), options.precision).toString()
15884      , parts = string.split(".")
15885      , precision
15886      , buffer = []
15887      , formattedNumber
15888      , format = options.format || "%n"
15889      , sign = negative ? "-" : ""
15890    ;
15891
15892    number = parts[0];
15893    precision = parts[1];
15894
15895    while (number.length > 0) {
15896      buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
15897      number = number.substr(0, number.length -3);
15898    }
15899
15900    formattedNumber = buffer.join(options.delimiter);
15901
15902    if (options.strip_insignificant_zeros && precision) {
15903      precision = precision.replace(/0+$/, "");
15904    }
15905
15906    if (options.precision > 0 && precision) {
15907      formattedNumber += options.separator + precision;
15908    }
15909
15910    if (options.sign_first) {
15911      format = "%s" + format;
15912    }
15913    else {
15914      format = format.replace("%n", "%s%n");
15915    }
15916
15917    formattedNumber = format
15918      .replace("%u", options.unit)
15919      .replace("%n", formattedNumber)
15920      .replace("%s", sign)
15921    ;
15922
15923    return formattedNumber;
15924  };
15925
15926  // Format currency with localization rules.
15927  // The options will be retrieved from the `number.currency.format` and
15928  // `number.format` scopes, in that order.
15929  //
15930  // Any missing option will be retrieved from the `I18n.toNumber` defaults and
15931  // the following options:
15932  //
15933  // - `unit`: `"$"`
15934  // - `precision`: `2`
15935  // - `format`: `"%u%n"`
15936  // - `delimiter`: `","`
15937  // - `separator`: `"."`
15938  //
15939  // You can also override these options by providing the `options` argument.
15940  //
15941  I18n.toCurrency = function(number, options) {
15942    options = this.prepareOptions(
15943        options
15944      , this.lookup("number.currency.format", options)
15945      , this.lookup("number.format", options)
15946      , CURRENCY_FORMAT
15947    );
15948
15949    return this.toNumber(number, options);
15950  };
15951
15952  // Localize several values.
15953  // You can provide the following scopes: `currency`, `number`, or `percentage`.
15954  // If you provide a scope that matches the `/^(date|time)/` regular expression
15955  // then the `value` will be converted by using the `I18n.toTime` function.
15956  //
15957  // It will default to the value's `toString` function.
15958  //
15959  I18n.localize = function(scope, value, options) {
15960    options || (options = {});
15961
15962    switch (scope) {
15963      case "currency":
15964        return this.toCurrency(value, options);
15965      case "number":
15966        scope = this.lookup("number.format", options);
15967        return this.toNumber(value, scope);
15968      case "percentage":
15969        return this.toPercentage(value, options);
15970      default:
15971        var localizedValue;
15972
15973        if (scope.match(/^(date|time)/)) {
15974          localizedValue = this.toTime(scope, value, options);
15975        } else {
15976          localizedValue = value.toString();
15977        }
15978
15979        return this.interpolate(localizedValue, options);
15980    }
15981  };
15982
15983  // Parse a given `date` string into a JavaScript Date object.
15984  // This function is time zone aware.
15985  //
15986  // The following string formats are recognized:
15987  //
15988  //    yyyy-mm-dd
15989  //    yyyy-mm-dd[ T]hh:mm::ss
15990  //    yyyy-mm-dd[ T]hh:mm::ss
15991  //    yyyy-mm-dd[ T]hh:mm::ssZ
15992  //    yyyy-mm-dd[ T]hh:mm::ss+0000
15993  //    yyyy-mm-dd[ T]hh:mm::ss+00:00
15994  //    yyyy-mm-dd[ T]hh:mm::ss.123Z
15995  //
15996  I18n.parseDate = function(date) {
15997    var matches, convertedDate, fraction;
15998    // A date input of `null` or `undefined` will be returned as-is
15999    if (date == null) {
16000      return date;
16001    }
16002    // we have a date, so just return it.
16003    if (typeof(date) === "object") {
16004      return date;
16005    }
16006
16007    matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/);
16008
16009    if (matches) {
16010      for (var i = 1; i <= 6; i++) {
16011        matches[i] = parseInt(matches[i], 10) || 0;
16012      }
16013
16014      // month starts on 0
16015      matches[2] -= 1;
16016
16017      fraction = matches[7] ? 1000 * ("0" + matches[7]) : null;
16018
16019      if (matches[8]) {
16020        convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction));
16021      } else {
16022        convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction);
16023      }
16024    } else if (typeof(date) == "number") {
16025      // UNIX timestamp
16026      convertedDate = new Date();
16027      convertedDate.setTime(date);
16028    } else if (date.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)) {
16029      // This format `Wed Jul 20 13:03:39 +0000 2011` is parsed by
16030      // webkit/firefox, but not by IE, so we must parse it manually.
16031      convertedDate = new Date();
16032      convertedDate.setTime(Date.parse([
16033        RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$6, RegExp.$4, RegExp.$5
16034      ].join(" ")));
16035    } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
16036      // a valid javascript format with timezone info
16037      convertedDate = new Date();
16038      convertedDate.setTime(Date.parse(date));
16039    } else {
16040      // an arbitrary javascript string
16041      convertedDate = new Date();
16042      convertedDate.setTime(Date.parse(date));
16043    }
16044
16045    return convertedDate;
16046  };
16047
16048  // Formats time according to the directives in the given format string.
16049  // The directives begins with a percent (%) character. Any text not listed as a
16050  // directive will be passed through to the output string.
16051  //
16052  // The accepted formats are:
16053  //
16054  //     %a     - The abbreviated weekday name (Sun)
16055  //     %A     - The full weekday name (Sunday)
16056  //     %b     - The abbreviated month name (Jan)
16057  //     %B     - The full month name (January)
16058  //     %c     - The preferred local date and time representation
16059  //     %d     - Day of the month (01..31)
16060  //     %-d    - Day of the month (1..31)
16061  //     %H     - Hour of the day, 24-hour clock (00..23)
16062  //     %-H/%k - Hour of the day, 24-hour clock (0..23)
16063  //     %I     - Hour of the day, 12-hour clock (01..12)
16064  //     %-I/%l - Hour of the day, 12-hour clock (1..12)
16065  //     %m     - Month of the year (01..12)
16066  //     %-m    - Month of the year (1..12)
16067  //     %M     - Minute of the hour (00..59)
16068  //     %-M    - Minute of the hour (0..59)
16069  //     %p     - Meridian indicator (AM  or  PM)
16070  //     %P     - Meridian indicator (am  or  pm)
16071  //     %S     - Second of the minute (00..60)
16072  //     %-S    - Second of the minute (0..60)
16073  //     %w     - Day of the week (Sunday is 0, 0..6)
16074  //     %y     - Year without a century (00..99)
16075  //     %-y    - Year without a century (0..99)
16076  //     %Y     - Year with century
16077  //     %z/%Z  - Timezone offset (+0545)
16078  //
16079  I18n.strftime = function(date, format, options) {
16080    var options = this.lookup("date", options)
16081      , meridianOptions = I18n.meridian()
16082    ;
16083
16084    if (!options) {
16085      options = {};
16086    }
16087
16088    options = this.prepareOptions(options, DATE);
16089
16090    if (isNaN(date.getTime())) {
16091      throw new Error('I18n.strftime() requires a valid date object, but received an invali
16091d date.');
16092    }
16093
16094    var weekDay = date.getDay()
16095      , day = date.getDate()
16096      , year = date.getFullYear()
16097      , month = date.getMonth() + 1
16098      , hour = date.getHours()
16099      , hour12 = hour
16100      , meridian = hour > 11 ? 1 : 0
16101      , secs = date.getSeconds()
16102      , mins = date.getMinutes()
16103      , offset = date.getTimezoneOffset()
16104      , absOffsetHours = Math.floor(Math.abs(offset / 60))
16105      , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60)
16106      , timezoneoffset = (offset > 0 ? "-" : "+") +
16107          (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) +
16108          (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes)
16109    ;
16110
16111    if (hour12 > 12) {
16112      hour12 = hour12 - 12;
16113    } else if (hour12 === 0) {
16114      hour12 = 12;
16115    }
16116
16117    format = format.replace("%a", options.abbr_day_names[weekDay]);
16118    format = format.replace("%A", options.day_names[weekDay]);
16119    format = format.replace("%b", options.abbr_month_names[month]);
16120    format = format.replace("%B", options.month_names[month]);
16121    format = format.replace("%d", padding(day));
16122    format = format.replace("%e", day);
16123    format = format.replace("%-d", day);
16124    format = format.replace("%H", padding(hour));
16125    format = format.replace("%-H", hour);
16126    format = format.replace("%k", hour);
16127    format = format.replace("%I", padding(hour12));
16128    format = format.replace("%-I", hour12);
16129    format = format.replace("%l", hour12);
16130    format = format.replace("%m", padding(month));
16131    format = format.replace("%-m", month);
16132    format = format.replace("%M", padding(mins));
16133    format = format.replace("%-M", mins);
16134    format = format.replace("%p", meridianOptions[meridian]);
16135    format = format.replace("%P", meridianOptions[meridian].toLowerCase());
16136    format = format.replace("%S", padding(secs));
16137    format = format.replace("%-S", secs);
16138    format = format.replace("%w", weekDay);
16139    format = format.replace("%y", padding(year));
16140    format = format.replace("%-y", padding(year).replace(/^0+/, ""));
16141    format = format.replace("%Y", year);
16142    format = format.replace("%z", timezoneoffset);
16143    format = format.replace("%Z", timezoneoffset);
16144
16145    return format;
16146  };
16147
16148  // Convert the given dateString into a formatted date.
16149  I18n.toTime = function(scope, dateString, options) {
16150    var date = this.parseDate(dateString)
16151      , format = this.lookup(scope, options)
16152    ;
16153
16154    // A date input of `null` or `undefined` will be returned as-is
16155    if (date == null) {
16156      return date;
16157    }
16158
16159    var date_string = date.toString()
16160    if (date_string.match(/invalid/i)) {
16161      return date_string;
16162    }
16163
16164    if (!format) {
16165      return date_string;
16166    }
16167
16168    return this.strftime(date, format, options);
16169  };
16170
16171  // Convert a number into a formatted percentage value.
16172  I18n.toPercentage = function(number, options) {
16173    options = this.prepareOptions(
16174        options
16175      , this.lookup("number.percentage.format", options)
16176      , this.lookup("number.format", options)
16177      , PERCENTAGE_FORMAT
16178    );
16179
16180    return this.toNumber(number, options);
16181  };
16182
16183  // Convert a number into a readable size representation.
16184  I18n.toHumanSize = function(number, options) {
16185    var kb = 1024
16186      , size = number
16187      , iterations = 0
16188      , unit
16189      , precision
16190      , fullScope
16191    ;
16192
16193    while (size >= kb && iterations < 4) {
16194      size = size / kb;
16195      iterations += 1;
16196    }
16197
16198    if (iterations === 0) {
16199      fullScope = this.getFullScope("number.human.storage_units.units.byte", options);
16200      unit = this.t(fullScope, {count: size});
16201      precision = 0;
16202    } else {
16203      fullScope = this.getFullScope("number.human.storage_units.units." + SIZE_UNITS[iterations], options);
16204      unit = this.t(fullScope);
16205      precision = (size - Math.floor(size) === 0) ? 0 : 1;
16206    }
16207
16208    options = this.prepareOptions(
16209        options
16210      , {unit: unit, precision: precision, format: "%n%u", delimiter: ""}
16211    );
16212
16213    return this.toNumber(size, options);
16214  };
16215
16216  I18n.getFullScope = function(scope, options) {
16217    options = options || {};
16218
16219    // Deal with the scope as an array.
16220    if (isArray(scope)) {
16221      scope = scope.join(options.separator || this.defaultSeparator);
16222    }
16223
16224    // Deal with the scope option provided through the second argument.
16225    //
16226    //    I18n.t('hello', {scope: 'greetings'});
16227    //
16228    if (options.scope) {
16229      scope = [options.scope, scope].join(options.separator || this.defaultSeparator);
16230    }
16231
16232    return scope;
16233  };
16234  /**
16235   * Merge obj1 with obj2 (shallow merge), without modifying inputs
16236   * @param {Object} obj1
16237   * @param {Object} obj2
16238   * @returns {Object} Merged values of obj1 and obj2
16239   *
16240   * In order to support ES3, `Object.prototype.hasOwnProperty.call` is used
16241   * Idea is from:
16242   * https://stackoverflow.com/questions/8157700/object-has-no-hasownproperty-method-i-e-its-undefined-ie8
16243   */
16244  I18n.extend = function ( obj1, obj2 ) {
16245    if (typeof(obj1) === "undefined" && typeof(obj2) === "undefined") {
16246      return {};
16247    }
16248    return merge(obj1, obj2);
16249  };
16250
16251  // Set aliases, so we can save some typing.
16252  I18n.t = I18n.translate.bind(I18n);
16253  I18n.l = I18n.localize.bind(I18n);
16254  I18n.p = I18n.pluralize.bind(I18n);
16255
16256  return I18n;
16257}));
16258// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
16259if (!Array.prototype.indexOf) {
16260    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
16261        "use strict";
16262        if (this == null) {
16263            throw new TypeError();
16264        }
16265        var t = Object(this);
16266        var len = t.length >>> 0;
16267        if (len === 0) {
16268            return -1;
16269        }
16270        var n = 0;
16271        if (arguments.length > 1) {
16272            n = Number(arguments[1]);
16273            if (n != n) { // shortcut for verifying if it's NaN
16274                n = 0;
16275            } else if (n != 0 && n != Infinity && n != -Infinity) {
16276                n = (n > 0 || -1) * Math.floor(Math.abs(n));
16277            }
16278        }
16279        if (n >= len) {
16280            return -1;
16281        }
16282        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
16283        for (; k < len; k++) {
16284            if (k in t && t[k] === searchElement) {
16285                return k;
16286            }
16287        }
16288        return -1;
16289    }
16290}
16291
16292// Production steps of ECMA-262, Edition 5, 15.4.4.18
16293// Reference: https://es5.github.com/#x15.4.4.18
16294// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
16295if ( !Array.prototype.forEach ) {
16296
16297  Array.prototype.forEach = function forEach( callback, thisArg ) {
16298
16299    var T, k;
16300
16301    if ( this == null ) {
16302      throw new TypeError( "this is null or not defined" );
16303    }
16304
16305    // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
16306    var O = Object(this);
16307
16308    // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
16309    // 3. Let len be ToUint32(lenValue).
16310    var len = O.length >>> 0; // Hack to convert O.length to a UInt32
16311
16312    // 4. If IsCallable(callback) is false, throw a TypeError exception.
16313    // See: https://es5.github.com/#x9.11
16314    if ( {}.toString.call(callback) !== "[object Function]" ) {
16315      throw new TypeError( callback + " is not a function" );
16316    }
16317
16318    // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
16319    if ( thisArg ) {
16320      T = thisArg;
16321    }
16322
16323    // 6. Let k be 0
16324    k = 0;
16325
16326    // 7. Repeat, while k < len
16327    while( k < len ) {
16328
16329      var kValue;
16330
16331      // a. Let Pk be ToString(k).
16332      //   This is implicit for LHS operands of the in operator
16333      // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
16334      //   This step can be combined with c
16335      // c. If kPresent is true, then
16336      if ( Object.prototype.hasOwnProperty.call(O, k) ) {
16337
16338        // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
16339        kValue = O[ k ];
16340
16341        // ii. Call the Call internal method of callback with T as the this value and
16342        // argument list containing kValue, k, and O.
16343        callback.call( T, kValue, k, O );
16344      }
16345      // d. Increase k by 1.
16346      k++;
16347    }
16348    // 8. return undefined
16349  };
16350}
16351
16352// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
16353if (!Array.prototype.some)
16354{
16355  Array.prototype.some = function(fun /*, thisArg */)
16356  {
16357    'use strict';
16358
16359    if (this === void 0 || this === null)
16360      throw new TypeError();
16361
16362    var t = Object(this);
16363    var len = t.length >>> 0;
16364    if (typeof fun !== 'function')
16365      throw new TypeError();
16366
16367    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
16368    for (var i = 0; i < len; i++)
16369    {
16370      if (i in t && fun.call(thisArg, t[i], i, t))
16371        return true;
16372    }
16373
16374    return false;
16375  };
16376}
16377
16378// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
16379if (!Array.prototype.map) {
16380
16381  Array.prototype.map = function(callback/*, thisArg*/) {
16382
16383    var T, A, k;
16384
16385    if (this == null) {
16386      throw new TypeError('this is null or not defined');
16387    }
16388
16389    // 1. Let O be the result of calling ToObject passing the |this| 
16390    //    value as the argument.
16391    var O = Object(this);
16392
16393    // 2. Let lenValue be the result of calling the Get internal 
16394    //    method of O with the argument "length".
16395    // 3. Let len be ToUint32(lenValue).
16396    var len = O.length >>> 0;
16397
16398    // 4. If IsCallable(callback) is false, throw a TypeError exception.
16399    // See: https://es5.github.com/#x9.11
16400    if (typeof callback !== 'function') {
16401      throw new TypeError(callback + ' is not a function');
16402    }
16403
16404    // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
16405    if (arguments.length > 1) {
16406      T = arguments[1];
16407    }
16408
16409    // 6. Let A be a new array created as if by the expression new Array(len) 
16410    //    where Array is the standard built-in constructor with that name and 
16411    //    len is the value of len.
16412    A = new Array(len);
16413
16414    // 7. Let k be 0
16415    k = 0;
16416
16417    // 8. Repeat, while k < len
16418    while (k < len) {
16419
16420      var kValue, mappedValue;
16421
16422      // a. Let Pk be ToString(k).
16423      //   This is implicit for LHS operands of the in operator
16424      // b. Let kPresent be the result of calling the HasProperty internal 
16425      //    method of O with argument Pk.
16426      //   This step can be combined with c
16427      // c. If kPresent is true, then
16428      if (k in O) {
16429
16430        // i. Let kValue be the result of calling the Get internal 
16431        //    method of O with argument Pk.
16432        kValue = O[k];
16433
16434        // ii. Let mappedValue be the result of calling the Call internal 
16435        //     method of callback with T as the this value and argument 
16436        //     list containing kValue, k, and O.
16437        mappedValue = callback.call(T, kValue, k, O);
16438
16439        // iii. Call the DefineOwnProperty internal method of A with arguments
16440        // Pk, Property Descriptor
16441        // { Value: mappedValue,
16442        //   Writable: true,
16443        //   Enumerable: true,
16444        //   Configurable: true },
16445        // and false.
16446
16447        // In browsers that support Object.defineProperty, use the following:
16448        // Object.defineProperty(A, k, {
16449        //   value: mappedValue,
16450        //   writable: true,
16451        //   enumerable: true,
16452        //   configurable: true
16453        // });
16454
16455        // For best browser support, use the following:
16456        A[k] = mappedValue;
16457      }
16458      // d. Increase k by 1.
16459      k++;
16460    }
16461
16462    // 9. return A
16463    return A;
16464  };
16465}
16466
16467// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
16468if (!Function.prototype.bind) (function(){
16469  var ArrayPrototypeSlice = Array.prototype.slice;
16470  Function.prototype.bind = function(otherThis) {
16471    if (typeof this !== 'function') {
16472      // closest thing possible to the ECMAScript 5
16473      // internal IsCallable function
16474      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
16475    }
16476
16477    var baseArgs= ArrayPrototypeSlice .call(arguments, 1),
16478        baseArgsLength = baseArgs.length,
16479        fToBind = this,
16480        fNOP    = function() {},
16481        fBound  = function() {
16482          baseArgs.length = baseArgsLength; // reset to default base arguments
16483          baseArgs.push.apply(baseArgs, arguments);
16484          return fToBind.apply(
16485                 fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
16486          );
16487        };
16488
16489    if (this.prototype) {
16490      // Function.prototype doesn't have a prototype property
16491      fNOP.prototype = this.prototype; 
16492    }
16493    fBound.prototype = new fNOP();
16494
16495    return fBound;
16496  };
16497})();
16498
16499// Using UMD pattern from
16500// https://github.com/umdjs/umd#regular-module
16501// `returnExports.js` version
16502;(function (root, factory) {
16503  if (typeof define === 'function' && define.amd) {
16504    // AMD. Register as an anonymous module.
16505    define(["i18n"], factory);
16506  } else if (typeof module === 'object' && module.exports) {
16507    // Node. Does not work with strict CommonJS, but
16508    // only CommonJS-like environments that support module.exports,
16509    // like Node.
16510    factory(require("i18n"));
16511  } else {
16512    // Browser globals (root is window)
16513    factory(root.I18n);
16514  }
16515}(this, function(I18n) {
16516  "use strict";
16517
16518  I18n.translations = {"de":{"activerecord":{"attributes":{"content_report":{"categories":{"adult":"Erwachsene oder gewalttätige Inhalte","ai_generated":"KI-generierte Inhalte","copyright":"Copyright-Verletzung"}}},"errors":{"messages":{"embedded_script":"Diese Datei wird nicht unterstützt und kann nicht hochgeladen werden. Bitte versuche eine andere Datei."}}},"api":{"profile":{"actions":{"contact":"Kontakt","follow":"Folgen","follow_tooltip":"Folgen Sie diesem Designer, um über seine Inhalte informiert zu werden und Lieblingsdesigner leichter in den Suchergebnissen zu filtern.","hide":"Ausblenden","hire_me":"Stellen Sie mich ein","invite_client":"Projekt starten","mute_tooltip":"Blenden Sie die Modelle dieses Designers in de
16518n Suchergebnissen aus.","take_over":"Übernehmen","unfollow":"Entfolgen","unhide":"Einblenden","view_all_models":"Alle Modelle des Designers ansehen"},"private_project_review":"Privates Projekt"}},"browser":{"3d_models":"3D Modelle","3d_print":"3D Druck","active_filters":{"polygons":"%{range} Polygonen"},"adult_content":"Anzeigen Alter +","animated":"animierte","breadcrumb":{"browse_by_type":"Nach Typ durchsuchen","collection":"3D-Pakete","free":"frei","free_models":"Kostenlose 3D Modelle","free_print_models":"Kostenlose 3D-Drucken Modelle","low_poly":"Low-poly","low_poly_models":"VR / AR / Low-Poly 3D Modelle","low_poly_print_models":"Low-Poly 3D-Druckdateien","model_3d":"3D-Modell","model_3dp":"3D Druck modell","models":"3D Modelle","models_3d":"3D Modelle","models_3dp":"3D Druck modelle","models_variant_0":"3D-Modelle","models_variant_1":"3D-Assets","models_variant_2":"VR-Modelle","models_variant_3":"CG-Modelle","models_variant_4":"3D-Modellshop","models_variant_5":"3D-Objekte","print_models":"3D-Druckdateien","print_models_variant_0":"3D-Druckmodelle","print_models_variant_1":"3D-Druckermodell","print_models_variant_10":"3D-Modelle zum Drucken","print_models_variant_11":"3D-Modelle zum Ausdrucken","print_models_variant_12":"3D-Dateien für den Druck","print_models_variant_13":"Modelle für 3D-Drucker","print_models_variant_14":"Modelle für den 3D-Druck","print_models_variant_15":"3D-Druckmodelle","print_models_variant_16":"Druckbare 3D-Dateien","print_models_variant_2":"3D-Modelle zum Drucken","print_models_variant_3":"3D-Druckmodelle","print_models_variant_4":"3D-Druckdateien","print_models_variant_5":"3D-Druckdateien","print_models_variant_6":"3D-Druckerdateien","print_models_variant_7":"Druckbare 3D-Modelle","print_models_variant_8":"Druckbare 3D-Modelle","print_models_variant_9":"Gedruckte 3D-Modelle"},"buyer_engagement_program":"Rabatte","categories":"Kategorien","category":{"meta":{"3d_models":"%{count} %{category} 3D Modelle zum Download zur Verfügung in jedem Dateiformat, einschließlich FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Download %{count} %{category} 3D Druck Modelle, zum Herunterladen in STL, OBJ und anderen Dateiformaten für das Drucken mit FDM, SLS und anderem 3D Drucker .%{page}","animated_3d_models":"%{count} %{category} 3D Modelle zum Download in jedem Dateiformat, einschließlich FBX, OBJ, MAX, 3DS, C4D.%{page} verfügbar animiert","free_3d_models":"Herunterladen %{count} %{category} kostenlose 3D Modelle, erhältlich in MAX, OBJ, FBX, 3DS, C4D Dateiformate, bereit für VR / AR, Animation, Spiele und andere 3D-Projekte.%{page}","free_3d_print_models":"Herunterladen %{count} %{category} kostenlos 3D Druck-Modelle bereit für FDM, SLA, SLS und anderen 3D Drucker , verfügbare Formate STL, OBJ, 3DM und vieles mehr.%{page}","lowpoly_3d_models":"Download %{count} VR / AR / Low-Poly-%{category} 3D Modelle bereit für die Virtual Reality, Spiele und andere Echtzeit-Anwendungen, in FBX, OBJ, MAX, 3DS, C4D-Dateiformaten.%{page}","rigged_3d_models":"%{count} %{category} 3D Modelle zum Download in jedem Dateiformat, einschließlich FBX, OBJ, MAX, 3DS, C4D.%{page} verfügbar in Ordnung gebracht"}},"collection":"3D-Paket","discounts":"Rabatte","ext":{"meta":{"3d_models":"%{rounded_count} 3D-Modelle im %{format_upper}-Format herunterladen. Kostenlose und Premium-%{format_lower}-Dateien.","3d_print_models":"Herunterladen %{count} %{ext} Dateien - %{software_name} 3D Druck Modelle bereit für VR / AR, 3D-Design, Animation, Spiele und Echtzeit-Anwendungen %{page}."}},"filter":"Filter","filter_button":"Filter anwenden","filters":{"adult_content_only":"Nur Inhalte für Erwachsene","cgt_standard_only":"CGT-Standard","collections_only":"Nur Pakete","content_filters":"Inhaltsfilter","designer":"Designer","exclude_3d_print":"3D-Druck ausschließen","exclude_adult_content":"Inhalte für Erwachsene ausschließen","exclude_ai_generated":"KI-generierte Inhalte ausschließen","exclude_editorial_license":"Redaktionelle Lizenz ausschließen","exclude_free":"Kostenlose ausschließen","file_format":"Dateiformate","following":"Gefolgt","free":"Kostenlos","has_marmoset":"Mit 3D-Viewer","has_video":"Mit Video","include_ai_generated":"KI-generierte Inhalte einbeziehen","label":"Filter","poly_count":"Polygonanzahl","print_ready":"3D-Druck","stl_files":"STL-Dateien","subscription":"Abonnement","type":"Typ"},"formats":"Formate","free":"Frei","free_models":"Gratis-Models Nur","free_to_premium":{"main":"%{category} 3D-Modelle","postfix":"in der Premium-Liste.","prefix":"Benötigen Sie etwas Besonderes? Sie können bessere Qualität finden"},"gallery":{"preview":"Vorschau"},"game_ready":"Spiel bereit","high_poly":"High-Poly","items_page_title":"3D Modelle","license":{"exclude_branded":"Ausschließen Marken","exclude_editorial":"Editorial ausschließen","prompt":"Lizenz","tooltip_more":"Weiterlesen","tooltip_text":" nicht zeigen Modelle, die nur in nicht-kommerziellen, pädagogische Zwecke genutzt werden können.","tooltip_title":"Editorial ausschließen:"},"low-poly":"Low-Poly","low_poly":"Low-poly","meta":{"3d_models":"Über %{rounded_count} professionelle 3D-Modelle herunterladen oder kaufen. Kostenlose und Premium-3D-Assets für Games, Film, VFX und 3D-Druck. FBX, OBJ, BLEND, GLB u.v.m.","3d_print_models":"Über %{rounded_count} 3D-Druck-Vorlagen herunterladen. Kostenlose und Premium-STL-Dateien für den 3D-Drucker: Figuren, Miniaturen, Deko, Werkzeug und mehr.","animated_3d_models":"%{rounded_count} animierte 3D-Modelle mit Animationen herunterladen. Game-ready Charaktere, Tiere und Objekte in FBX, BLEND, GLB.","free_3d_models":"%{rounded_count} kostenlose 3D-Modelle zum Herunterladen. Lizenzfreie 3D-Objekte und Assets in FBX, OBJ, BLEND, GLB – für Games, Film und AR/VR.","free_3d_print_models":"%{rounded_count}
16518 kostenlose 3D-Drucker-Vorlagen. Gratis STL-Dateien und 3D-Druck-Modelle zum Downloaden.","lowpoly_3d_models":"%{rounded_count} Low-Poly-3D-Modelle für Games, AR/VR und Echtzeit-Rendering herunterladen. Kostenlos und Premium, game-ready.","metaverse":"Geben Sie die Metaverse mit unserer Sammlung von 3D-Modellen mit hohem und niedrigem Poly ein, animiert und verfügbare Dateiformat.","omniverse":"Bauen Sie Ihre virtuelle Welt mit unseren Omniverse-fähigen 3D-Modellen auf. Wählen Sie aus animierten, manipulierten und PBR -Modellen mit hoher und niedriger Polygonzahl.","page_prefix":"Page %{page_number} mit","pbr_3d_models":"%{rounded_count} PBR-3D-Modelle mit physikalisch basierten Texturen. Game-ready Assets für Unity, Unreal und Echtzeit-Engines.","rigged_3d_models":"%{rounded_count} geriggte 3D-Modelle mit Skelett/Bones, bereit zur Animation. Für Blender, Maya, 3ds Max, Unity und Unreal.","snap-ar":"Mit unseren 3D -Modellen können Sie Millionen von Snapchatters verwenden, um Snap AR zu erstellen, um unterhaltsame Augmented Reality -Erlebnisse zu schaffen.","spark-ar":"Erhellen Sie Ihr Facebook und Ihr Instagram mit einer Vielzahl von Funken -AR -3D -Modellen, die manipuliert, animiert und verwendet werden können.","stream_3d_print_models":"Blättern Sie durch eine Vielzahl von Strom Kategorien 3D Druck modell einige der außergewöhnlichsten 3D druckbare Motive von professionellen 3D-Schöpfer zu entdecken.","textures":"%{count} Textures 3D-Assets und Materialien zum Download in verschiedenen Formaten, einschließlich .tga .sbsar .jpeg .png tiff und andere. %{page}","tiktok":"Machen Sie Ihre Videoeffekte mit Tiktok 3D -Modellen beeindruckender. Unsere Bibliothek kann alle Ihre kreativen Bedürfnisse erfüllen und wird ständig aktualisiert.","unity":"Finden Sie 3D -Modelle für Ihr Unity -Projekt in jedem Dateiformat und jeder Poly -Anzahl. Alles, was Sie brauchen, um eine einzigartige virtuelle Umgebung zu schaffen.","unreal":"Erstellen Sie beeindruckende Visualisierungen mit unwirklichen Engine -kompatiblen 3D -Modellen in jedem Dateiformat. Unsere Bibliothek verfügt über niedrig- und hochpoly-Modelle aus jeder Kategorie."},"optimized":"Low-Poly / PBR","page_title":{"animated_3d_models":"Animierte 3D-Modelle – Kostenlos \u0026 Premium","format_page":"%{format_upper} 3D-Modelle – %{format_lower}-Dateien kostenlos \u0026 Premium","free_3d_models":"Kostenlose 3D-Modelle herunterladen – %{rounded_count} Modelle","free_3d_print_models":"Kostenlose 3D-Druck Vorlagen – Gratis STL-Dateien","lowpoly_3d_models":"Low Poly 3D-Modelle – Kostenlos \u0026 Premium","pbr_3d_models":"PBR 3D-Modelle – Kostenlos \u0026 Premium","rigged_3d_models":"Rigged 3D-Modelle – Kostenlos \u0026 Premium herunterladen","software_page":"%{software} 3D-Modelle – %{ext_file}-Dateien herunterladen","tag":{"3d_merged_premium":"%{tag_cap} 3D-Modelle – Kostenlose \u0026 Premium-Downloads","3d_merged_premium_print":"%{tag_cap} STL-Dateien – 3D-Druck Vorlagen","3d_models":"%{tag_cap} 3D-Modelle – Kostenlose \u0026 Premium-Downloads","3d_print_models":"%{tag_cap} STL-Dateien – 3D-Druck Vorlagen"}},"pbr":"PBR","per_page":"Pro Seite","polygons_options":{"custom":"Benutzerdefinierten Bereich","gt_250k":"250k +","lt_5k":"Bis zu 5k","more_than_250000":"250K+","prompt":"Poly Graf","range_0_5000":"Bis zu 5K","range_100000_250000":"100 - 250K","range_10000_50000":"10 - 50K","range_100k_250k":"100k bis 250k","range_10k_50k":"10k auf 50k","range_50000_100000":"50 - 100K","range_5000_10000":"5 - 10K","range_50k_100k":"50k auf 100k","range_5k_10k":"5k bis 10k"},"price":"Preis: $%{from}-%{to}","price_range":"Preis","print_ready":"3D Druck bereit","reset_button":"Filter zurücksetzen","rigged":"Manipulierten","rounded_count":{"delimiter":".","exact":"%{n}","million":"%{n} Mio.","separator":",","thousand":"%{n}+"},"sale_off":"Im Angebot","search_placeholder":"Suche Schlüsselwörter","show_all_formats":"Alle Formate","simulation_ready":"Simulationsbereit","software":{"meta":{"3d_models":"%{rounded_count} 3D-Modelle für %{software} herunterladen. Kostenlose und Premium-%{ext_file}-Dateien, direkt importierbar."}},"sort_by":"Sortiere nach","sort_by_options":{"best_match":"Bester Treffer","highest_price":"Höherer Preis","lowest_price":"Niedrigerer Preis","newest":"Neueste","oldest":"Älteste","sales":"Meistverkauft","trending":"Trending"},"subcategory":{"meta":{"3d_models":"%{count} %{category} 3D Modelle zum Download zur Verfügung in jedem Dateiformat, einschließlich FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Download %{count} %{category} 3D Druck Modelle, zum Herunterladen in STL, OBJ und anderen Dateiformaten für das Drucken mit FDM, SLS und anderem 3D Drucker .%{page}","animated_3d_models":"%{count} %{category} 3D Modelle zum Download in jedem Dateiformat, einschließlich FBX, OBJ, MAX, 3DS, C4D.%{page} verfügbar animiert","free_3d_models":"Herunterladen %{count} %{category} kostenlose 3D Modelle, erhältlich in MAX, OBJ, FBX, 3DS, C4D Dateiformate, bereit für VR / AR, Animation, Spiele und andere 3D-Projekte.%{page}","free_3d_print_models":"Herunterladen %{count} %{category} kostenlos 3D Druck-Modelle bereit für FDM, SLA, SLS und anderen 3D Drucker , verfügbare Formate STL, OBJ, 3DM und vieles mehr.%{page}","lowpoly_3d_models":"Laden Sie VR / AR / Low Poly %{tag} 3D Modelle bereit für die Virtual Reality, Spiele und andere Echtzeit-Anwendungen, in FBX, OBJ, MAX, 3DS, C4D-Dateiformaten.%{page}","rigged_3d_models":"%{count} %{category} 3D Modelle zum Download in jedem Dateiformat, einschließlich FBX, OBJ, MAX, 3DS, C4D.%{page} verfügbar in Ordnung gebracht"}}
16518,"subtitle":{"aircraft":"%{count} Flugzeuge 3D Modelle stehen zum Download auf unserem Marktplatz zur Verfügung. CG %{airplanes_link} passt in Simulationen, Visualisierungen, Anzeigen und Videos. %{drone_link} Körper und Teile werden die Fans von kleinen fliegenden Fahrzeuge erfreuen. Und das %{rigged_link} Modelle sind bereit, in der Game-Engines importiert werden und für die Animation oder VR / AR-Projekte verwendet werden. Fliegen Spiele, Simulatoren und Videos sowohl regelmäßiger CG und Low-Poly-Typen können mit 3D Modellen in den meisten gängigen Formaten wie FBX, OBJ, MAX, 3DS, C4D gefüllt werden.","animals":"%{count} von CG 3D-Tiere - %{cats_link} %{horses_link} %{bears_link} %{rhinos_link} %{eagles_link} %{lions_link}, fiktiv %{dragons_link} und mehr - sind hier verfügbar zum Download bereit. Die Sammlung von 3D-Fauna wird auf die Bedürfnisse von Werbeanzeigen oder statischer Szenen befriedigen. Wir haben auch %{rigged_link} Modelle für Spiele, Animation, Videoproduktion und VR / AR-Projekte. Verwenden Sie Filter in den meisten Industrie akzeptierten Dateiformate in Ordnung gebracht, animiert, Low-Poly oder freie 3D Modelle zu finden, einschließlich FBX, OBJ, MAX, 3DS, C4D und andere.","animated":"Laden Sie %{rounded_count} animierte 3D-Modelle mit fertigen Bewegungsabläufen herunter – Charaktere, Tiere und Objekte für Games, Film und Echtzeit-Projekte.","architectural":"%{count} von architektonischen 3D Modelle zum Download zur Verfügung enthält Elemente und Details der Architektur, Konstruktion und Engineering-Design. Zum Kauf oder ein kostenloses 3D-Modell herunterladen, durchsuchen Sie die Kategorien oben. Sie können Modelle für Animationen oder Spiele verwenden - einige von ihnen sind in Ordnung gebracht und Low-Poly-VR / AR Projektanforderungen zu dienen. 3D Modelle sind in Dateiformate wie FBX, OBJ, MAX, 3DS, C4D verfügbar.","car":"%{count} Auto 3D Modelle zum Download zur Verfügung. Low-Poly-Versionen von denen zu Game-Designer oder VR / AR-Anwendungen Entwickler, andere attraktiv sein - zu Filmemachern, Animateure oder Werbetreibende. Von klassischen und antiken Autos auf das Rennen und Konzept diejenigen in Filmen zu sehen, gibt es eine große Auswahl an 3D-Auto-Modelle im Wesentlichen jedes Format (FBX, OBJ, MAX, 3DS, C4D, etc.) Sie benötigen. Verwenden Sie animierte und kostenlose 3D Modelle Prototyp und shorten Entwicklungszeit.","character":"%{count} Zeichen 3D Modelle zum Download zur Verfügung, so dass diese Kategorie Surfen Sie 3D Modelle von %{human_link} finden, Menschen, mythische Figuren, Science-Fiction %{robots_link}, Kleidung und andere zu Zeichen bezogene Dinge. Von %{heart_link} und %{hand_link}, um detaillierte realistisches %{male_link} oder %{female_link} Modellen. Einige der Modelle sind in Ordnung gebracht und leicht zu Ihrem Spiel, VR / AR-Projekt, Video oder Animation hinzugefügt werden könnten. Sowohl hohe und niedrige Poly Poly Modelle können in einer Reihe von Formaten, einschließlich FBX, OBJ, MAX, 3DS, C4d finden.","custom":"Hier können Sie kostenlos %{tag} 3D Modelle kaufen oder herunterladen. %{tag_heading_subtitle}","electronics":"%{count} Elektronik 3D Modelle zum Download zur Verfügung. Dies ist eine Kategorie für Architekten, Innenarchitekten, Level-Designer, Szene Entscheidungsträgern und anderen Kreativen. 3D Modelle von%{smartphones_link}, %{laptops_link}, %{cameras_link}, %{tvs_link} und wird dazu beitragen, genau das moderne Leben Besessenheit mit Geräten und Technologie zu erfassen. Es gibt 3D Modelle in Dateiformate wie FBX, OBJ, MAX, 3DS, C4D. Laden Sie kostenlos oder kaufen Premium-Modelle und die Nutzung für Animation, Spiele und VR / AR-Projekte.","exterior":"%{count} von architektonischen Außen 3D Modelle zum Download zur Verfügung Bereich von Fassaden, städtische Gebäude, Landschaftsobjekte, Wohnhäuser zu Industriebauten, Infrastrukturbauten und Skalenendwertkontrolle %{city_link} Modelle. Zum Kauf oder ein kostenloses 3D-Modell herunterladen, durchsuchen Sie die Kategorien oben. Verwenden Sie Filter in Ordnung gebracht zu finden, animiert, Low-Poly, freie 3D Modelle oder sortieren nach Format benötigt, einschließlich FBX, OBJ, MAX, 3DS, C4D. Einige der Vermögenswerte sind Low-Poly und könnten Sie die Umwelt in Ihrem Spiel oder VR / AR-Anwendung helfen abzuschließen.","food":"%{count} Lebensmittel 3D Modelle sind für download..Food dient auch realistische Szenen des häuslichen Lebens zu schaffen. 3D-Obst, Gemüse, Getränke und Speisen sind alle hier, um die komplette Werbung für Restaurants, AR-Anwendungen für gesündere Ernährung und Visualisierungen Architekten und Innenarchitekten arbeiten. Ob Sie sich für 3D suchen %{pizza_link} und %{ketchup_link} Modelle oder ein 3D-Modell einer Hochzeit %{cake_link}, sind sie hier. Eine Reihe von Low-Poly-Versionen sind für die im Spiel, VR / AR-Entwicklung zur Verfügung. 3D Modelle können in FBX, OBJ, MAX, 3DS, C4D Formaten zu finden.","formats":"Laden Sie %{rounded_count} 3D-Modelle im %{format_upper}
16518-Format herunter – kostenlos und Premium. Jede Datei auf dieser Seite ist nativ als %{format_upper} verfügbar, ohne Konvertierung nutzbar.","free_general":"Laden Sie %{rounded_count} kostenlose 3D-Modelle herunter – 3D-Modelle kostenlos für private und kommerzielle Projekte. Gratis 3D-Objekte in FBX, OBJ, BLEND und GLB, ohne versteckte Kosten.","free_no_tag":"%{model_count} kostenlose 3D %{tag} Modelle bereit für 3D Druck. Download %{tag} freie 3D Druck modelle, Dateiformate zur Verfügung, einschließlich STL, OBJ, 3DM, 3DS, MAX. 3D Modelle sind bereit, mit FDM, SLA, SLS und anderen Arten von 3D-Druckern verwendet werden.","free_printable":"Über %{rounded_count} kostenlose 3D-Druck-Vorlagen zum Herunterladen: gratis STL-Dateien und 3D-Druckvorlagen für Ihren 3D-Drucker – von der Community getestet, von Deko bis Werkstatt-Helfern.","free_tag":"Free 3D %{tag} Modelle zum Download zur Verfügung. Erhältlich in vielen Dateiformaten wie MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB. Finden Sie professionelles %{tag_link} für alle 3D-Design-Projekte wie Virtual Reality (VR), Augmented Reality (AR), Spiele, 3D-Visualisierung oder Animation.","furniture":"%{count} Möbel 3D Modelle zum Download zur Verfügung gehören Tische, Stühle, Sofas, Betten und andere Möbel in einer Vielzahl von Texturen (Holz, Metall, etc.) und Arten (%{retro_link}, %{modern_link} usw. ). Diese Kategorie ist für jedermann mit Architektur und Innenarchitektur sowie Werbetreibende und Animateure beteiligt gemeint. Echtzeit-Assets können Spiel und VR / AR-Anwendungsentwickler zu kompletten Projekten helfen. Verwenden Sie Filter in Ordnung gebracht, animiert, Low-Poly oder freie 3D Modelle zu finden. Erhältlich in jedem Dateiformat einschließlich FBX, OBJ, MAX, 3DS, C4D.","general":"Entdecken Sie über %{rounded_count} professionelle 3D-Modelle für Games, Film, VFX, AR/VR und 3D-Druck. 3D-Objekte und 3D-Dateien kostenlos herunterladen oder als Premium-Modelle kaufen – in FBX, OBJ, BLEND, GLB und über 100 weiteren Formaten.","high_poly":"Kaufen oder herunterladen Animated Professionelle 3D Modelle bereit, in CG-Projekten verwendet werden, Film- und Videoproduktion, Animation, Visualisierung und andere. Vermögenswerte sind zum Download in vielen branchenüblichen Formaten, einschließlich MAX, OBJ, FBX, 3DS, UE4 und andere zur Verfügung. Wenn Sie auf der Suche nach High-Poly-Qualität 3D-Assets sind, dann haben wir eine führende digitale Kunstbibliothek für alle Ihre Bedürfnisse.\n","household":"%{count} Haushalt 3D Modelle zum Download zur Verfügung können verwendet werden, um Details zu den Zimmern zu geben. Vielleicht haben Sie ein Abendessen auf dem Tisch setzen wollen, Pfeffer und Salz Container komplette Küche Interieur haben oder etwas setzen {lights_link} und %{flowers_link} neben dem Bett luxuriöse Atmosphäre zu schaffen. Wenn Sie ein Innenarchitekt, Animator, VR / AR-Anwendung oder Spiel Schöpfer sind, ist diese Kategorie für Sie. Verwenden Sie Filter in Ordnung gebracht, animiert, Low-Poly oder freie 3D Modelle zu finden. Erhältlich in jedem Dateiformat einschließlich FBX, OBJ, MAX, 3DS, C4D.","industrial":"%{count} industrielle 3D Modelle zum Download zur Verfügung - Baumaterialien, %{engineering_link} Werkzeuge, Maschinen und %{containers_link} - wird beiden Details liefern und interaktive, manipulierten Elemente lebendigere Umgebungen zu schaffen. Dies ist eine perfekte Kategorie für Simulationen / VR / AR oder Entwickler des Aufbau %{construction_link} Site-Ebene im Spiel. Die 3D Modelle werden Baufachleute präzise Visualisierung arbeiten mit unterschiedlicher Software dienen als Modelle in Formaten wie FBX, OBJ, MAX, 3DS, C4D verfügbar sind.","interior":"%{count} Interieur 3D Modelle zum Download zur Verfügung und beinhalten Elemente und in der Architektur, Konstruktion und Engineering, Innenarchitektur verwendeten Details. Zum Kauf Prämie oder ein kostenloses 3D-Modell herunterladen, durchsuchen Sie die oben genannten Kategorien. Sie Modelle für Animationen oder Spiele nutzen können - einige von ihnen sind in Ordnung gebracht, animiert und Low-Poly. Erhältlich in einer Vielzahl von Dateiformaten, darunter FBX, OBJ, MAX, 3DS, C4D.","low_poly":"Laden Sie %{rounded_count} Low-Poly-3D-Modelle herunter – leichtgewichtige, game-ready Assets für Games, AR/VR und mobile Projekte.","military":"%{count} militärische 3D Modelle von Waffen, Waffen, %{swords_link}
16518 und Artillerie sowohl moderner als auch historischen durch Streitkräfte und Selbstverteidigung verwendet werden, sind zum Download zur Verfügung. Sie können Modelle für Spiele oder VR / AR Inhalte verwenden - einige von ihnen sind Polygone optimiert und bereit zu einem Spiel-Engine hinzugefügt werden. In Ordnung gebracht und animierte Modelle können Ihre Arbeit weiter erleichtern. Sie werden sicherlich relevanten Vermögenswerte finden, da es viele 3D Modelle in jedem Dateiformat sind, könnten Sie (FBX, OBJ, MAX, 3DS, C4D, etc.) benötigen.","not_custom":"Laden Sie %{rounded_count} %{tag_cap}-3D-Modelle herunter. Ob 3D-Modell %{tag_cap} für Games oder hochauflösend fürs Rendering – kostenlos und Premium, in FBX, OBJ, BLEND und GLB.","optimized":"Modelle, die weniger Polygone haben, sind am besten in Echtzeit-Anwendungen verwendet, die eine schnelle Verarbeitung erfordern, wie Virtual Reality (VR), Augmented Reality (AR), Mixed Reality (MR), Quer Realität (XR) und Spiele, vor allem mobile Spiele. Wähle aus einer Sammlung von in Ordnung gebracht und animierten Modelle, um sie leicht in Ihrer Echtzeit-Anwendungen verwenden. Erhalten Sie 3D Assets für Umgebungen, Pick Requisiten, Objekte oder kaufen komplette 3D-Modellsammlungen, Bundles und Packungen mit allem was das Spiel benötigen. Sparen Entwicklungszeit und -kosten, make Prototyp Erfahrungen oder die Verwendung von 3D Modellen als Platzhalter in Ihrem Projekt. Physikalisch basierten Rendering oder PBR bezeichnet Modelle, die genau und konsistent in allen Lichtverhältnissen infolge der Texturierung gerendert werden Arbeitsabläufe in der Erstellungsprozess verwendet.\n","pbr":"Laden Sie %{rounded_count} PBR-3D-Modelle mit physikalisch basierten Texturen (PBR-Texturen) herunter – konsistente, realistische Materialien für Unity, Unreal und andere Echtzeit-Engines.","plant":"%{count} Pflanze 3D Modelle zum Download zur Verfügung enthalten Flora Organismen, die als Hauspflanzen und in der Natur, wie Blumen, %{trees_link}, Gras, Kletterpflanzen, Pflanzen, Kräuter, Stauden, Keimlinge, Sprossen, Sträucher, rutschen wachsen, Sprossen, Reben, Unkraut. Zum Kauf Prämie oder ein kostenloses 3D-Modell herunterladen Durchsuchen Sie die Kategorien oben, um die beste Lösung unter den Modellen zu finden. Verwenden Sie Filter in Ordnung gebracht zu finden, animiert, Low-Poly (für den Einsatz in Spielen und VR / AR-Anwendungen) Modelle oder benötigten Formate, um herauszufiltern (Modelle in FBX, OBJ, MAX, 3DS, C4D und mehr stehen zur Verfügung).","printable":"Laden Sie über %{rounded_count}
16518 3D-Druck-Vorlagen herunter – geprüfte 3D-Drucker-Vorlagen und 3D-Druck-Dateien für FDM- und Resin-Drucker. Von Figuren und Miniaturen bis zu Deko, Werkzeug und Ersatzteilen.","printable_merged_tag":"Laden Sie %{rounded_count} %{tag_cap}-STL-Dateien für den 3D-Druck herunter. Kostenlose und Premium-3D-Druck-Vorlagen für FDM- und Resin-Drucker.","printable_tag":"Laden Sie %{rounded_count} %{tag_cap}-STL-Dateien für den 3D-Druck herunter. Kostenlose und Premium-3D-Druck-Vorlagen für FDM- und Resin-Drucker.","rigged":"Laden Sie %{rounded_count} Rigged-3D-Modelle (geriggte Modelle mit Skelett) herunter – animationsbereite Charaktere, Tiere und Kreaturen für Blender, Maya, 3ds Max, Unity und Unreal.","scanned":"%{count} gescannten 3D Modelle verfügbar sind für den Kauf oder kostenlosen Download. 3D-gescannter Modelle sind bereit, für die Animation, Spiele und VR / AR-Projekte. Sie können wie Felsen und natürliche Oberflächen, Menschen und andere reale Objekte hier gefunden 3D Modelle von Objekten in der Natur finden ... und es kann nicht mehr realistisch als diese. Verwenden Sie Filter in Ordnung gebracht, animiert, Low-Poly oder freie 3D Modelle zu finden. Erhältlich in jedem Dateiformat einschließlich FBX, OBJ, MAX, 3DS, C4D.","science":"Laborgeräte, mikroskopische Körper und subatomaren Welt:%{count} Wissenschaft 3D Modelle zum Download zur Verfügung in dieser Kategorie reicht von Medizin, Biologie, Physik, Chemie Feldern und anderen Objekten der realen Welt der Wissenschaft und Technologie. Suchen Sie nicht weiter, %{cell_link}, %{skull_link} und %{skeleton_link} 3D Modelle finden Sie hier! Kaufen oder kostenlos 3D-Assets herunterladen, durchsuchen Sie die Kategorien oben. Sie können in Ordnung gebracht Modelle für Animationen oder Low-Poly diejenigen, für Spiele und VR / AR-Projekte verwenden. Beide kostenlos und Premium-3D Modelle können in einer Reihe von Dateiformaten einschließlich FBX, OBJ, MAX, 3DS, C4D finden.","scripts_plugins":"%{count} 3D-Skripte / Plugins zum Download zur Verfügung. 3D-Skripte / Plugins können auf verschiedene Software hinzugefügt werden, und verwendet, um Ihre Arbeit Pipeline effizienter zu machen. Filtern nach Formaten Werkzeuge passend Beliebten Software zu finden.\n","software_page":"Laden Sie %{rounded_count} 3D-Modelle für %{software} herunter – kostenlos und Premium. Native %{ext_file}-Dateien lassen sich direkt öffnen, weitere Formate einfach importieren.","space":"%{count} Raum 3D Modelle stehen zum Download auf CGTrader zur Verfügung. Der Raum ist eine Kategorie voller Low-Poly, Spiel-ready Modelle von Science-Fiction- %{spaceships_link}. Es gibt auch %{asteroids_link}, Planeten (einschließlich Mond und Mars zu simulieren Kolonisation) und realistische Raum %{shuttles_link} für den wissenschaftlich geneigt. Die Modelle können auf jeden Fall im VR-Raum Simulator-Spielen, AR-Anwendungen und Animationen verwendet werden, um die Welt außerhalb des Planeten %{earth_link} zu demonstrieren. Filter sind hier, um Ihnen helfen, in Ordnung gebracht finden, animiert, Low-Poly-Vermögenswerte oder Modelle in bestimmten Formaten, einschließlich FBX, OBJ, MAX, 3DS, C4D.","sports":"%{count} Sport 3D Modelle zur Verfügung Nest zusammen Ausrüstung, die für Freizeitaktivitäten, Unterhaltung, Sport, Freizeit, angenehmen Zeitvertreib, Spaß sowie Spiele, Kinderspielzeug und die vollständige Palette von Musikinstrumenten verwendet wird. Zum Kauf oder ein kostenloses 3D-Modell in jedem Dateiformat herunterladen benötigt, einschließlich FBX, OBJ, MAX, 3DS, C4D, durchsuchen Sie die Kategorien oben. Sie können Modelle für die Animation, Spiele, VR / AR-Projekte verwenden - viele von ihnen sind in Ordnung gebracht, Low-Poly oder bereits animiert.","test":{"airplane":"Luftfahrt-Enthusiasten, aufhorchen, es ist noch nie so einfach das perfekte 3D Druck Flugzeugmodell für Ihr Brettspiel oder Modellsammlung zu erhalten. Suchen Sie in %{model_count} 3D druckbare Flugzeugmodelle und finden ein Modell, das Sie gerade benötigen, um auf diesem Regal. Entdecken Sie echte Flugzeugmodelle oder aufbereitetem diejenigen. Suchen Sie nach neuesten Designs oder etwas, das zählt ab dem Zeitpunkt der Gebrüder Wright zurück. Es ist auch ein perfektes Geschenk für jedes Kind, das ein Pilot sein träumt, wenn er aufwächst. Download und Kauf eines 3D Druck modell, drucken Sie es selbst oder das Modell zu einem unserer 3D-Druckdienstleister einreichen, um es gedruckt und in kürzester Zeit zu Ihnen versendet!\n","anatomy":"%{model_count} 3D-Anatomie-Modelle zum Download auf unserem Marktplatz. Entdecken Sie die Anatomie des Menschen mit hoher - Poly Knochen, Muskeln, Organmodelle. Perfekt für jede pädagogische oder wisse
16518nschaftliche Projekt. Verwenden Sie druckbare 3D Modelle für eine verbesserte Ausbildung Erfahrung. Erstellen Sie beeindruckende Animationen und Videospiele mit unseren\n umfangreiche Bibliothek von manipulierten Modellen. Nutzen Sie unsere erweiterte Filtersystem leicht Low-Poly zu finden, animiert, PBR,\n manipulierten Modelle oder alles, was passt am besten Ihren Bedürfnissen. Wählen Sie aus einer großen Menge Dateiformate, einschließlich der\n die meisten populären wie FBX, OBJ, MAX, 3DS, C4D.\n","castle":"Ihre eigene erstaunliche 3D Druck Schloss ist ein Klick entfernt! Suchen Sie verschiedene 3D-Schloss-Modelle für 3D Druck bereit. Erstellen Sie eine einzigartige Fantasy-Festung für D \u0026 D-Sitzung oder ein anderes mittelalterlichen rpg Tabletop-Spiel. Diese mittelalterlichen Festungen sind ein erstaunliches Geschenk für jedes Brettspiel-Enthusiasten oder eine großartige Ergänzung zu Ihrer eigenen Sammlung. Finden Sie das perfekte 3D-Modell für Bastler oder einen von einer Art Spielzeug für Kinder zu malen. Download und Kauf von Ihnen gewählten 3D-Schloss-Design, drucken Sie es mit Ihrem eigenen Drucker oder senden Sie das Modell zu einem unserer 3D Druck-Service-Provider haben es gedruckt und versendet zu Ihnen.\n","dice":"Das Glück wird nicht abgelaufen ist mit 3D-Würfel gedruckt! Wenn Sie für Ihre Brettspiel Nacht authentisches Spiel Würfel wollen, schauen Sie nicht mehr. Hier können Sie zwischen %{model_count} kostenlose 3D Würfel Modelle bereit für den 3D Druck wählen. Wenn das Spiel vorbei ist, diese schönen Modelle als Zubehör verwenden. Download der kostenlosen 3D-Druckvorschau Würfel Modelle, Dateiformate, einschließlich STL, verfügbar OBJ, 3DM, 3DS, MAX. Unser 3D Modelle sind bereit, mit FDM, SLA, SLS und anderen Arten von 3D-Druckern verwendet werden. Machen Sie Ihr Spiel ein wenig interessanter und erhalten Sie Ihre 3D Druck Würfel!\n","drone":"3D Druck und Ihre eigene Drohne bauen oder Teile für den Ersatz finden. 3D Druck-Drohnen sind leicht nach einer Bruchlandung zu montieren und zu reparieren. Entdecken Sie ein Modell mit der perfekten Kamera Einbaulage oder Anfrage für minimale Änderungen. Perfekt für Spaß Freizeitaktivitäten, Luftaufnahmen, Videografie oder Mapping. Finden und kaufen Sie eine einzigartige 3D-Drohne Modell konzipiert, dass passt auf alle Ihre Bedürfnisse. Drucken Sie es selbst oder das Modell zu einem unserer 3D-Druckdienstleister vorlegen, um es gedruckt und versendet zu Ihnen in kürzester Zeit! Holen Sie Ihre eigene 3D-gedruckten Drohne und durchstreift den weiten blauen Himmel!\n","pokemon":"Pikachu, ich wählen Sie! Wählen Sie aus Lasten der Tasche Monster bereit für die 3D Druck. Große 3D gedruckt pokemon Sammlung zu erweitern. Suchen und stolpern das beste, ausführlichsten pokemon Design für Ihr nächstes Tabletop-Spiel mit Freunden. Kauf und laden Sie Ihre 3D-pokemon, drucken Sie es mit Ihrem eigenen 3D Drucker  laden oder das Modell zu einem unserer 3D Druck-Service-Provider haben es gedruckt und versendet zu Ihnen. Immerhin ist dies eine der coolsten Sachen 3D Druck!\n","ring":"3D Druck-Schmuck ist das neue Schwarz in der Branche, sind als gedruckte 3D-Ringe! Suchen Sie eine enorme Menge an %{model_count} freier 3D-bedruckbare Ringmodelle. Stöbern Sie in einzigartigen Designs und für Sie nur etwas gemacht finden. Ich kann nichts für sich selbst im Laden finden? Oder vielleicht möchten Sie ein Zubehörteil genau wie die, Ihren Lieblingsfilm Charakter hat? Wählen Sie aus einer großen Menge Dateiformate wie STL, OBJ, 3DM, 3DS, MAX. 3D Modelle sind bereit, mit FDM, SLA, SLS und anderen Arten von 3D-Druckern verwendet werden, um durch unsere Auswahl durchsuchen und auf Ihrem neuen Lieblings-Ring versuchen.\n","shoes":"Große Marken sind an Bord mit 3D-Schuhe gedruckt und jetzt können Sie die Erfahrung als auch teilen. Durchsuchen und frei bedruckbare Schuh-Design - von den Turnschuhen zu modischen Fersen gibt es eine Menge von ready-to-Print-Modellen warten auf Sie sie auf versuchen. Machen Sie das Ergebnis perfekt durch Auswahl der Materialien, die Anpassung Weichheit und Größe. Wählen Sie aus 146 3D freie Schuhmodelle und laden Sie sie in verfügbaren Dateiformate wie STL, OBJ, 3DM, 3DS, MAX. Designs sind bereit, mit FDM, SLA, SLS und anderen Arten von 3D-Druckern verwendet werden. Holen Sie sich Ihre 3D Druck-Schuhe heute!\n","textures":"Suchen Sie in einer großen Menge von %{model_count} CG Texturen für Downloads zur Verfügung, einschließlich schön hergestellten Materialien. Blättern Sie durch die Kategorie und findet nahtlosen Stoff, Holz, Metall, Kunststoff, Ziegel Texturen zu helfen, Ihr Projekt schneller Finish auf. Nutzen Sie unsere erweiterte Suche, um manipulierte und animierte Texturen für Spiele oder Video-Erstellung zu finden. Unsere Materialien stehen zum Download in verschiedenen Formaten einschließlich TGA, SBSAR, JPG, PNG, TIFF und anderen, so verschwenden Sie keine Zeit und ready-to-use CG Texturen für fast jedes Projekt vorstellbar zu finden.\n"},"various":"%{count} verschiedene 3D Modelle, die ihren Platz in anderen Kategorien nicht leicht zum Download zur Verfügung finden. %{rocks_link}, %{clouds_link}, %{logos_link}, %{cigarette_link} und andere nicht kategorisiert 3D Modelle, dass Sie Ihr Spiel, Animation oder VR / AR-Projekt sind hier zu vervollständigen benötigen. Verwenden Sie Filter, durch den verfügbaren Formaten wie FBX, OBJ, MAX, 3DS, C4D in Ordnung gebracht, animiert, Low-Poly oder freie 3D Modelle oder aussortieren zu finden.","vehicle":"%{count} Fahrzeug 3D Modelle zum Download bereit sind LKW, %{ambulances_link}, Busse, %{bikes_link} und moto - Diese Kategorie ist für die Menschen, die ihre Straßen der Stadt wollen mit Fahrzeugen, ob realistisch oder Sci-Fi gefüllt werden. Diejenigen, Simulationsspiele oder VR / AR-Anwendungen, Kreativen in der Videoproduktion oder sogar entwerfen Plakate werden höchstwahrscheinlich die meisten gängigen Dateiformaten finden wie FBX, OBJ, MAX, 3DS, C4D verwendbar. Andere Landtransportfahrzeuge wie Zug oder %{tractor_link} 3D Modelle können auch in dieser riesigen Sammlung zu finden. Verwenden Sie Filter in Ordnung gebracht zu finden, animierte, Low-Poly oder schnellen 3D Modelle frei.","
16518watercraft":"%{count} Wasserfahrzeuge 3D Modelle zum Download verfügbar sind %{boats_link}, %{yachts_link}, %{ships_link} und andere Wasser gehen Fahrzeuge aller Art. Die manipulierten Low-Poly 3D Modelle werden gut daran tun in Videospielen, VR / AR-Projekten sowie Animationen, während CG-Modelle hier für Ihre Szenen und andere Anwendungen, wo visuelle Treue ist wichtiger als polycount. Alle Modelle sind in jedem Dateiformat zur Verfügung, einschließlich, aber nicht beschränkt auf FBX, OBJ, MAX, 3DS, C4D."},"tag":{"meta":{"3d_merged_premium":"%{rounded_count} %{tag_cap}-3D-Modelle herunterladen, kostenlos und Premium. In FBX, OBJ, BLEND, GLB und mehr – für Games, Film, AR/VR und 3D-Druck.","3d_merged_premium_print":"%{rounded_count} %{tag_cap}-STL-Dateien und 3D-Druck-Vorlagen herunterladen, kostenlos und Premium – getestet für FDM- und Resin-Drucker.","3d_models":"%{rounded_count} %{tag_cap}-3D-Modelle herunterladen, kostenlos und Premium. In FBX, OBJ, BLEND, GLB und mehr – für Games, Film, AR/VR und 3D-Druck.","3d_print_models":"%{rounded_count} %{tag_cap}-STL-Dateien und 3D-Druck-Vorlagen herunterladen, kostenlos und Premium – getestet für FDM- und Resin-Drucker.","animated_3d_models":"%{count} %{tag} 3D Modelle zum Download in jedem Dateiformat, einschließlich FBX, OBJ, MAX, 3DS, C4D.%{page} verfügbar animiert","free_3d_models":"Herunterladen %{count} %{tag} freie 3D Modelle, erhältlich in MAX, OBJ, FBX, 3DS, C4D Dateiformaten, bereit für VR / AR, Animation, Spiele und andere 3D-Projekte.%{page}","free_3d_print_models":"Herunterladen %{count} %{tag} freie 3D Druck Modelle bereit für FDM, SLA, SLS und anderen 3D Drucker , verfügbare Formate STL, OBJ, 3DM und vieles mehr.%{page}","lowpoly_3d_models":"Download %{count} VR / AR / Low-Poly-%{tag} 3D Modelle bereit für die Virtual Reality, Spiele und andere Echtzeit-Anwendungen, in FBX, OBJ, MAX, 3DS, C4D-Dateiformaten.%{page}","rigged_3d_models":"%{count} %{tag} 3D Modelle zum Download in jedem Dateiformat, einschließlich FBX, OBJ, MAX, 3DS, C4D.%{page} verfügbar in Ordnung gebracht"}},"title":{"download":"| herunterladen","files":"3D-Dateien","formats":"%{format_upper} 3D-Modelle","free":"Kostenlose 3D-Modelle","free_printing":"Kostenlose 3D-Druck Vorlagen","free_tag":"Freies %{selected_tag} 3D Modelle","general":"Professionelle 3D-Modelle","high_poly":"%{selected_filter}3D Modelle","landing":{"animated_3d_models":"Animierte 3D-Modelle","lowpoly_3d_models":"Low Poly 3D-Modelle","pbr_3d_models":"PBR 3D-Modelle","rigged_3d_models":"Rigged 3D-Modelle"},"low_poly":"%{selected_filter}3D Modelle","newest_cg":"Neueste 3D Modelle","newest_print":"Neueste 3D Druck Modelle","page_prefix":"Page %{page_number} von ","pbr":"PBR 3D Modelle","printing":"3D-Druck Vorlagen \u0026 STL-Dateien","sales_cg":"Bestseller 3D Modelle","sales_print":"Bestseller 3D Druck modelle","software_page":"%{software} 3D-Modelle","tag_heading":{"cg":"Freies %{tag} 3D Printing Modelle","filter":"%{selected_filter} %{tag} 3D Modelle","general":"%{tag_cap} 3D-Modelle","merged_premium_print_tag":"%{tag_cap} STL-Dateien \u0026 3D-Druck Vorlagen","merged_premium_tag":"%{tag_cap} 3D-Modelle","printable":"%{tag_cap} STL-Dateien \u0026 3D-Druck Vorlagen"},"trending_cg":"Trendige 3D Modelle","trending_print":"Trendige 3D Druck modelle"}},"cart":{"request_invoice_failed":"Ups. Etwas ist schiefgelaufen.","request_invoice_successful":"Rechnungsanforderung erfolgreich gesendet.","shareable_cart_created":"Warenkorb erfolgreich geteilt.","shareable_cart_failed":"Ups. Etwas ist schiefgelaufen.","subscription_covered_items_removed":"Aus Ihrem Warenkorb entfernt: %{titles}. Bereits in Ihrem Abonnement enthalten.","subscription_covered_order_refused":"Ihr Abonnement enthält jetzt %{titles}, daher haben wir Ihnen nichts berechnet. Laden Sie die Modelle stattdessen über Ihr Abonnement herunter."},"categories":{"cg":{"header_title_html":"Professionelle 3D-Modelle","text":"Finden Sie genau die richtigen 3D-Inhalte für Ihre Bedürfnisse, einschließlich AR/VR, Gaming, Werbung, Unterhaltung und mehr"},"head-title":{"cg":"3D-Modelle – %{rounded_count} Modelle kostenlos \u0026 Premium","cg-models":{"sort_by":{"newest":"Neueste 3D-Modelle","top_selling":"Meistverkaufte 3D-Modelle","trending":"Trendige 3D-Modelle"}},"print":"3D-Druck Vorlagen – STL-Dateien kostenlos \u0026 Premium","print-models":{"sort_by":{"newest":"Neueste 3D-Druckdateien","top_selling":"Meistverkaufte 3D-Druckdateien","trending":"Trendige 3D-Druckdateien"}}
16518,"product-type":{"plural":{"capital":{"cg":"3D Modelle","cg-merge-premium":"3D-Modelle – Kostenlose und Premium-Downloads","print":"3D-Druckdateien und Modelle","print-merge-premium":"%{tag} 3D-Druckdateien – kostenlose und Premium-STL-Modelle"},"lower":{"cg":"3D Modelle","print":"3D-Druckdateien und Modelle"}},"singular":{"capital":{"cg":"3D Modell","print":"3D-Druckmodell"},"lower":{"cg":"3D Modell","print":"3D Druckmodell"}}}},"index":{"browse_all":"Alle Modelle durchsuchen","info-section__description_1_html":"Kaufen oder kostenlos herunterladen: professionelle 3D-Modelle, die sofort in CG-Projekten, Film- und Videoproduktion, Animation, Visualisierungen, Spielen, VR/AR und mehr verwendet werden können. Assets sind in vielen \u003ca href='https://www.cgtrader.com/blog/3d-file-formats' target='_blank' rel='noopener noreferrer'\u003ebranchenüblichen Formaten\u003c/a\u003e verfügbar, darunter MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB und andere. Wenn Sie nach High-Poly- oder Echtzeit-3D-Assets suchen, haben wir eine führende digitale Kunstbibliothek für alle Ihre Bedürfnisse.\n","info-section__description_1_printable_html":"Professionelle 3D-Dateien, Drucke und Modelle, die zu Hause mit Desktop-FDM- oder industriellen SLS-3D-Druckern gedruckt werden können, verfügbar in vielen Industrieformaten, darunter OBJ, \u003ca href=\"https://www.cgtrader.com/3d-print-models/stl-files\"\u003eSTL\u003c/a\u003e, WRL. Wenn Sie ein 3D-Druck-Hobbyist, Maker, Pädagoge oder ein Unternehmen sind, das Prototypen durch 3D-Druck erstellt, werden unsere Wohnaccessoires, trendigen Gadget-Zubehörteile, auffälligen Modeartikel, einzigartigen Schmuckstücke, mechanischen Teile und eine Vielzahl von Miniaturen Ihnen sicherlich einen Inspirationsschub geben.","info-section__heading_1_html":"3D-Modell \u003cstrong\u003eKategorien\u003c/strong\u003e erkunden","info-section__heading_1_printable_html":"Entdecken Sie 3D Druck modell \u003cstrong\u003e Kategorien \u003c/strong\u003e","info-section__heading_2_html":"3D-Modelle-\u003cstrong\u003eMarktplatz\u003c/strong\u003e","info-section__heading_2_printable_html":"Was \u003cstrong\u003enoch?\u003c/strong\u003e"},"marketplace":{"3d-printing":{"link_text":"3D-Druckmodelle durchsuchen","text":"Finden Sie 3D-Druckmodelle für Ihren 3D-Drucker.","title":"3D-Druckmodelle"},"blender_text":"Entdecken Sie Tausende von Blender-3D-Modellen im BLEND-Format.","browse":"Durchsuche","cinema-4d_text":"Entdecken Sie Tausende hochwertiger Cinema-4D-Modelle, die im C4D-Dateiformat verfügbar sind.","discounted":{"link_text":"Durchsuchen Ermäßigte 3D Modelle","text":"Kaufen Sie professionelle 3D Modelle mit einem Abschlag.","title":"Ermäßigte 3D Modelle"},"low-poly":{"link_text":"Low-Poly-3D-Modelle durchsuchen","text":"Low-Poly-3D-Modelle, bereit für Videospiele, VR- \u0026 AR-Echtzeitanwendungen.","title":"Low-Poly-3D-Modelle"},"pbr":{"link_text":"PBR-3D-Modelle durchsuchen","text":"Physically Based Rendering 3D-Modelle.","title":"PBR-3D-Modell"},"scripts_apps":{"link_text":"Alle durchsuchen","text":"3D-Skripte und Plugins, um Ihre 3D-Modellierungs-Pipeline effizienter zu gestalten.","title":"Skripte \u0026 Apps"},"sketchup_text":"Durchsuchen Sie die große Auswahl an Sketchup-3D-Modellen für Architektur-, Innen- und Videospieldesigns.","textures":{"link_text":"Texturen durchsuchen","text":"Einsatzbereite Texturen, um Zeit bei der Vorbereitung Ihrer 3D-Modelle zu sparen.","title":"Texturen"},"zbrush_text":"Finden Sie 3D-Assets im ZTL-Dateiformat für Ihre digitalen Sculpting-, 3D-Modellierungs-, Texturierungs- und Malprojekte."},"meta-description":{"cg":"Über %{rounded_count} professionelle 3D-Modelle herunterladen oder kaufen. Kostenlose und Premium-3D-Assets für Games, Film, VFX und 3D-Druck. FBX, OBJ, BLEND, GLB u.v.m.","print":"Über %{rounded_count} 3D-Druck-Vorlagen herunterladen. Kostenlose und Premium-STL-Dateien für den 3D-Drucker: Figuren, Miniaturen, Deko, Werkzeug und mehr."},"more":{"designers":{"link_text":"3D-Designer-Liste durchsuchen","text":"Durchsuchen Sie Tausende von 3D-Designer-Portfolios und finden Sie genau das, was Sie für Ihre Anwendung benötigen.","title":"Beste 3D-Designer finden"},"projects":{"link_text":"Mehr über 3D-Projekte erfahren","text":"Qualifizierte freiberufliche 3D-Künstler beauftragen und alle Ihre individuellen Modellierungsherausforderungen lösen.","t
16518itle":"CGTrader-3D-Projekte"},"visualization":{"link_text":"Mehr erfahren","text":"Verwandeln Sie Ihre Produkte in fotorealistische 3D-Modelle für E-Commerce und andere Geschäftsanwendungen.","title":"3D-Visualisierung für E-Commerce"}},"navbar":{"aircraft":"Flugzeug","animal":"Tiere","animals":"Tiere","architectural":"Architektonisch","art":"Kunst","car":"Auto","character":"Charakter","exterior":"Außen","fashion":"Mode","food":"Essen","furniture":"Möbel","gadgets":"Gadgets","games-toys":"Spiele und Spielzeug","hobby-diy":"Hobby \u0026 Basteln","house":"Haus","household":"Haushalt","industrial":"Industriell","interior":"Innenraum","jewelry":"Schmuck","military":"Militär","miniatures":"Miniaturen","plant":"Pflanze","science":"Wissenschaft","space":"Platz","vehicle":"Fahrzeug","watercraft":"Wasserfahrzeug"},"print_models":{"hero_title_html":"3D-Druck Vorlagen \u0026 STL-Dateien","subtitle":"Finden Sie genau die richtigen 3D-Druckinhalte für Ihre Bedürfnisse","title_html":"Professionelle \u003cstrong\u003e3D-Modelle für den Druck\u003c/strong\u003e kaufen"},"profile":{"collection":"3D-Pakete","free":"Kostenlose 3D-Modelle","high_poly":"High-Poly 3D Modelle","optimized":"Low-Poly / Pbr 3D Modelle","printable":"3D Druck modelle"},"seo":{"cg":{"aircraft":"Diese Kategorie umfasst 3D-Flugzeuge. CG-Flugzeuge eignen sich für Simulationen,\nVisualisierungen, Werbung und Videos. Drohnenkörper und -teile erfreuen\nFans kleiner Fluggeräte. Und die geriggten Modelle sind bereit, in Spiel-Engines\nimportiert zu werden.\n","animals":"Die Sammlung von 3D-Fauna – Katzen, Pferde, Bären, Nashörner, Adler, Löwen\nund mehr – ist hier zu finden. Die CG-Tiere erfüllen die Anforderungen\nvon Werbung oder statischen Szenen. Wir haben auch geriggte Tiermodelle\nfür Spiele.\n","architectural":"Treppen, Kamine, 3D-Modelle sowie Dekorationen wie Rahmen, Kissen oder\nLampen finden Sie in der umfangreichen Bibliothek architektonischer Detailmodelle.\nInnen- und Außenbereiche können genutzt werden, um digitale Küchen, Schlafzimmer,\nSchlossräume oder Dungeons zu bauen – egal ob Architekt, Ingenieur oder\nIndie-Entwickler.\n","car":"Die Kategorie Auto enthält Automobil-3D-Modelle, deren Low-Poly-Versionen\nfür Spieledesigner attraktiv sind, andere für Filmemacher oder Werbetreibende.\nVon klassischen Autos über Renn- und Sportwagen bis hin zu Konzeptfahrzeugen\ngibt es eine große Vielfalt an 3D-Automodellen.\n","character":"In dieser Kategorie finden Sie 3D-Modelle von Menschen, Personen, Sci-Fi,\nKleidung und mehr. Vom Herz und von der Hand bis hin zu detaillierten realistischen\nmännlichen oder weiblichen Modellen. Zahlreiche Modelle sind geriggt und\nkönnen leicht in Ihr Spiel oder Ihre Animation integriert werden.\n","electronics":"Elektronik ist eine Kategorie für Architekten, Innenarchitekten, Level-Designer, Szene Entscheidungsträgern und anderen Kreativen. 3D Modelle von {\"slug\": \"Smartphone\",\"title\": \"Smartphones\"}, {\"slug\": \"Laptop\",\"title\": \"Laptops\"}, {\"slug\": \"Kamera\", \"title\":‚Kameras‘}, {\"slug\":‚tv‘,‚title‘:‚TV‘} und wird dazu beitragen, genau die moderne Leben Besessenheit mit Geräten und Technologie zu erfassen.\n","engineering-parts":"Fenster, Pipelines, Schiebetürmechanismen und andere Bits, die für CAD-Projekte notwendig sind und auch DIY Pläne fallen unter diese Kategorie. Hier können Sie 3D Modelle von den globalen Herstellern wie ANVER, Dynatec und FCi finden.\n","exterior":"Durchsuchen Sie architektonische Außenbereiche von Fassaden, städtischen\nGebäuden bis hin zu Landschaftsobjekten, Wohnhäusern, Industrieanlagen\nund vollständigen 3D-Stadtmodellen. Einige Assets sind Low Poly und können\nIhnen helfen, Ihr Spiel oder Ihre VR/AR-Anwendung zu vervollständigen.\n","food":"Lebensmittel helfen, realistische Szenen des häuslichen Lebens zu erschaffen.\n3D-Früchte, Gemüse, Getränke und Mahlzeiten sind alle hier. Werbung für\nRestaurants, AR-Anwendungen für gesünderes Essen. Ob Sie ein 3D-Pizza- oder\nHochzeitstortenmodell gesucht haben – hier finden Sie es.\n","furniture":"3D-Tische, Stühle, Sofas, Betten in verschiedenen Texturen (Holz, Metall\nusw.) und Stilen (Retro, Modern usw.) sind verfügbar. Diese Kategorie richtet\nsich an alle, die mit Architektur und Inneneinrichtung sowie Werbung zu\ntun haben.\
16518n","furniture-set":"Wenn Sie gleich brauchen antiken Stuhl und Sofa schauen, oder vollständigen Satz von Geschirr, ist dies ein Ort wie diese Kategorie collect Sätze von 3D-Möbelmodellen aus Küche und Wohnzimmer zu Büro und Speisesaal Möbel-Sets zu sein. Dies hilft, die innere oder Level-Design-Spezialist Build Zimmer mit Anpasselemente.\n","household":"Haushaltsgegenstände in 3D werden verwendet, um Räumen Details zu verleihen.\nSie können ein 3D-Geschirrset auf den Tisch stellen oder Pfeffer- und Salzstreuer\nverwenden, um die Kücheneinrichtung zu vervollständigen, vielleicht Lichter\nund Blumen neben das Bett stellen, um ein luxuriöses Ambiente zu schaffen.\n","industrial":"Baumaterialien, Ingenieurwerkzeuge, Maschinen und Container, die sowohl\nUmgebungsdetails als auch interaktive, geriggte Elemente bieten. Perfekt\nfür Entwickler von Simulationsspielen oder Personen, die Baustellen-Level\nerstellen.\n","interior":"Diese Kategorie enthält realistische 3D-Elemente und Details der Architektur,\
16518ndes Bauwesens und des Innenraumdesigns. Sie können diese 3D-Modelle für\nAnimationen oder Spiele verwenden – einige sind geriggt und Low Poly.\n","military":"In dieser Kategorie können Sie 3D-Modelle von Waffen, Rüstungen, Schwertern\nund Artillerie – sowohl modern als auch historisch – kaufen, die von Militärkräften\noder zur Selbstverteidigung genutzt werden. Sie können die Modelle für Spiele\nverwenden – einige sind polygonoptimiert und für Spiel-Engines bereit.\n","plant":"Diese Kategorie enthält 3D-Modelle von Flora-Organismen wie Blumen, Bäumen,\nGras, Kletterpflanzen, Grünpflanzen, Kräutern, Stauden, Setzlingen, Trieben,\nSträuchern, Stecklingen, Sprossen, Reben und Unkraut. Um ein 3D-Modell zu\nkaufen oder kostenlos herunterzuladen, durchsuchen Sie die Kategorien unten.\n","scanned":"Modellierer erstellen, um diese Modelle von 3D-Scan-Technologien wie RealSense verwenden. Baumstümpfe, Statuen, auch Männer und Frauen sind hier sehr beliebt. Das Produkt ist bereit, in Szenen und Animationen verwendet wird, beispiellosen Realismus bieten.\n","science":"Diese Kategorie enthält 3D Modelle im Zusammenhang mit Medizin, Biologie, Physik, Chemie und Technik, wie Laborgeräte, mikroskopische Körper und subatomaren Welt. Suchen Sie nicht weiter, {\"slug\": \"Zelle\"}, {\"slug\": \"skull\"} und {\"slug\": \"Skelett\"} 3D Modelle finden Sie hier.\n","scripts-plugins":"Scripts und Plug-Ins bieten Ihnen Software-Verknüpfungen. Modellierung, Rendering, Texturieren - all dies einfacher gemacht werden kann Plug-In verwenden, die für alle Arten von Software wie Maya und V-Ray kommt.\n","space":"Weltraum ist eine Kategorie voller Low-Poly-, spielbereiter Modelle von\nSci-Fi-Raumschiffen, Asteroiden, Planeten (einschließlich Erde und realistischer\nSpace Shuttles für wissenschaftlich Interessierte). 3D-Weltraummodelle sind\nnützlich für VR-Weltraumsimulatorspiele.\n","sport-hobby":"Diese Kategorie encompases alles, was bezieht {\"slug\": \"Feld\"} Veranstaltungen, Sport und Hobbys im Allgemeinen. Von {\"slug\": \"Fußball\"} und Skipisten zu {\"slug\": \"Gitarre\"} und {\"slug\": \"Piano\"}, sind diese nützlichen 3D Modelle für Anzeigen, Szenen und Animationen. Low-Poly-Versionen gibt es für Sportvideospiele.\n","textures":"Durchsuchen Sie eine Bibliothek wunderschön erstellter 3D-Materialien und\nfinden Sie die am besten geeigneten, einsatzbereiten Texturen, um spielbereite\noder fotorealistische Modelle vorzubereiten, Ihre Asset-Bibliothek zu erweitern\nund Ihre 3D-Arbeiten zu verbessern.\n","various":"Dies sind die 3D Modelle, die nicht leicht in andere Kategorien gepasst haben, so dass wir eine erstellt, die sie alle akzeptieren würde. {\"title\": \"Rock\", \"slug\": \"Felsen\"}, {\"slug\": \"Wolken\"}, {\"title\": \"Logo\", \"slug\": \"Logo\"}, {\"title\" : \"Regenschirme\", \"slug\": \"Regenschirm\"} und andere nicht kategorisiert 3D Modelle sind hier.\n","vehicle":"Lkw, Krankenwagen, Busse, Züge, Traktoren, Fahrräder und Motorräder — diese\nKategorie ist für Menschen gedacht, die ihre Stadtstraßen mit realistischen\nund Sci-Fi-Fahrzeugen füllen wollen, und für diejenigen, die Simulationsspiele\nmachen oder Poster gestalten.\n","watercraft":"Wasserfahrzeuge umfassen Boote, Yachten, Schiffe und andere Wasserfahrzeuge.\nDie geriggten 3D-Modelle eignen sich gut für Videospiele und Animationen,\nwährend CG-Modelle für Szenen oder andere Anwendungen gedacht sind, bei\ndenen visuelle Treue wichtiger ist als Polygonanzahl.\n"},"forwarded":{"cg":{"aircraft":"Diese Kategorie umfasst 3D-Flugzeuge. CG {\"slug\": \"aircraft\",\"title\": \"Flugzeuge\"} paßt in Simulationen, Visualisierungen, Anzeigen und Videos. {\"slug\": \"drone\",\"title\": \"Drone\"} Körper und Teile werden die Fans von kleinen fliegenden Fahrzeuge erfreuen. Und die {\"slug\": \"aircraft\",\"title\": \"in Ordnung gebracht Modelle\", \"active_filter\": \"rigged\"} sind bereit, in der Game-Engines importiert werden.\n","animals":"Die Sammlung von 3D-Fauna - {\"slug\": \"cat\",\"title\": \"Katzen\"}, {\"slug\": \"horse\",\"title\": \"Pferde\"}, {\"slug\": \"bear\",\"title\": \"Trägt\"}, {\"slug\": \"rhino\", \"Titel\": \"rhinos\"}, {\"slug\": \"eagle\", \"Titel\": \"Eagles\"}, {\"slug\": \"lion\",\"title\": \"Löwen\"} und mehr - ist hier zu finden. Die CG 3D Tiere werden auf die Bedürfnisse von Werbeanzeigen oder statischer Szenen befriedigen. Wir haben auch {\"slug\": \"animal\",\"title\": \"in Ordnung gebracht Tieres\", \"active_filter\": \"rigged\"} Modelle für Spiele.\n","architectural":"{\"slug\": \"stairs\",\"title\": \"Treppe\"}, {\"slug\": \"fireplace\",\"title\": \"Kamin\"}, 3D Modelle sowie Dekorationen wie {\"slug\": \"frame\",\"title\": \"Frame\"}
16518, {\"slug\": \"pillow\",\"title\": \"Kissen\"} oder {\"slug\": \"lamp\",\"title\":\"Lampe\"} können in dem umfangreichen architektonischen Details Car enthält Automobil 3D Modelle gefunden werden, {\"slug\": \"car\",\"title\": \"niedrige poly\", \"active_filter\": \"low_poly\"} Modellbibliothek. Innen- und Außenbereich können sowohl digitale als Küchen und Schlafzimmer sowie Schlossräume und Dungeons zu bauen verwendet werden, ob Sie ein Architekt, Ingenieur oder Indie-Spiele-Entwickler sind.\n","car":"Auto enthält Automobil 3D Modelle, \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e Low-Poly \u003c/a\u003e Versionen davon zu Game-Designern attraktiv sein werden, andere - zu Filmemachern oder Werbekunden. Von Oldtimern zu Renn- und Draisinen auf das Konzept diejenigen, gibt es eine große Auswahl an 3D-Auto-Modelle.\n","character":"Menschen, Science-Fiction, Kleidung und andere: In dieser Kategorie finden Sie 3D Modelle von { \"slug\": \"human\",\"title\": \"mensch\" } finden. Von {\"slug\": \"heart\",\"title\": \"Herz\"} und {\"slug\": \"hand\",\"title\": \"Hand\"}, um detaillierte realistische {\"slug\": \"male\",\"title\": \"Männlich\"} oder {\"slug\": \"female\",\"title\": \"Weiblich\"} Modelle. Einige der Modelle sind in Ordnung gebracht und könnten leicht zu Ihrem Spiel oder eine Animation hinzugefügt werden.\n","electronics":"Elektronik ist eine Kategorie für Architekten, Innenarchitekten, Level-Designer, Szene Entscheidungsträgern und anderen Kreativen. 3D Modelle von {\"slug\": \"smartphone\",\"title\": \"Smartphones\"}, {\"slug\": \"laptop\",\"title\": \"Laptops\"}, {\"slug\": \"camera\", \"title\":\"Kameras\"}, {\"slug\":\"tv\",\"title\":\"TV\"} und wird dazu beitragen, genau die moderne Leben Besessenheit mit Geräten und Technologie zu erfassen.\n","engineering-parts":"Fenster, Pipelines, Schiebetürmechanismen und andere Bits, die für CAD-Projekte notwendig sind und auch DIY Pläne fallen unter diese Kategorie. Hier können Sie 3D Modelle von den globalen Herstellern wie ANVER, Dynatec und FCi finden.\n","exterior":"Durchsuchen Architektur Außen von Fassaden, städtischen Gebäuden zu Landschaftsobjekte, Wohnhäuser, Industriebauten und Skalenendwert 3D {\"slug\": \"city\",\"title\": \"Stadt\"} Modelle. Einige der Vermögenswerte sind Low-Poly und könnten Sie Ihr Spiel oder VR / AR-Anwendung vervollständigen helfen.\n","food":"Essen hilft realistische Szenen des häuslichen Lebens zu schaffen. 3D-Obst, Gemüse, Getränke und Speisen sind alle hier. Werbung für Restaurants, AR-Anwendungen für gesündere Ernährung. Egal, ob Sie waren auf der Suche nach 3D {\"slug\": \"pizza\",\"title\": \"Pizza\"} oder einer Hochzeit {\"slug\": \"cake\",\"title\": \"Kuchen\"} Modell, sind sie hier.\n","furniture":"3D-Tische, Stühle, Sofas, Betten in einer Vielzahl von Texturen (Holz, Metall, etc.) und Stilen ({\"slug\": \"retro\",\"title\": \"Retro\"}, {\"slug\": \"modern\",\"title\": \"Modern\"}, etc.) sind on- Hand. Diese Kategorie ist für jedermann mit Architektur und Innenarchitektur sowie Werbetreibende beteiligten gemeint.\n","furniture-set":"Wenn Sie gleich brauchen antiken Stuhl und Sofa schauen, oder vollständigen Satz von Geschirr, ist dies ein Ort wie diese Kategorie collect Sätze von 3D-Möbelmodellen aus Küche und Wohnzimmer zu Büro und Speisesaal Möbel-Sets zu sein. Dies hilft, die innere oder Level-Design-Spezialist Build Zimmer mit Anpasselemente.\n","household":"Haushalt Artikel 3D Modelle werden verwendet, Details zu den Zimmern zu geben. Sie können einen 3D-Besteckset auf dem Tisch legen, oder Pfeffer und Salz Container komplette Küche Interieur haben, vielleicht etwas setzen {\"slug\": \"light\",\"title\": \"Lichter\"} und {\"slug\": \"flowers\",\"title\": \"Blumen\"} in der Nähe des Bettes luxuriöse Atmosphäre zu schaffen.\n","industrial":"Baustoffe, {\"slug\": \"engineering\",\"title\": \"Ingenieurwesen\"} Werkzeuge, Maschinen und {\"slug\": \"container\",\"title\": \"Container\"}, die sowohl Umweltdaten und interaktive liefern, manipulierten Elemente. Dies ist ideal für Simulationsspiel-Entwickler oder Menschen bauen {\"slug\": \"construction\",\"title\": \"Bau\"} Hofebene.\n","interior":"Diese Kategorie enthält realistische 3D-Elemente und Details der Architektur, Bau- und Ingenieurinnenarchitektur. Sie können diese 3D Modelle für Animationen oder Spiele verwenden - einige von ihnen sind in Ordnung gebracht und Low-Poly.\n","military":"Durch Streitkräfte und Selbstverteidigung und Artillerie sowohl moderne als auch historische In dieser Kategorie können Sie 3D Modelle von Waffen, Waffen, { \"Schwert\": \"Schwerter\", \"slug\": \"sword\"} kaufen. Sie können Modelle für Spiele verwenden - einige von ihnen Polygone sind optimiert und bereit für Game-Engines.\n","plant":"Diese Kategorie enthält 3D Modelle von Flora Organismen wie Blumen, {\"slug\": \"tree\",\"title\": \"Bäume\"}, Gras, Kletterpflanzen, Pflanzen, Kräuter, Stauden, Keimlinge, Sprossen, Sträucher, rutscht, Sprossen, Reben, Unkraut. Zum Kauf oder ein kostenloses 3D-Modell herunterladen, durchsuchen Sie die Kategorien unten.\n","scanned":"Modellierer erstellen, um diese Modelle von 3D-Scan-Technologien wie RealSense verwenden. Baumstümpfe, Statuen, auch Männer und Frauen sind hier sehr beliebt. Das Produkt ist bereit, in Szenen und Animationen verwendet wird, beispiellosen Realismus bieten.\n","scie
16518nce":"Diese Kategorie enthält 3D Modelle im Zusammenhang mit Medizin, Biologie, Physik, Chemie und Technik, wie Laborgeräte, mikroskopische Körper und subatomaren Welt. Suchen Sie nicht weiter, {\"slug\": \"cell\",\"title\": \"Zelle\"}, {\"slug\": \"skull\", \"title\":\"schädel\"} und {\"slug\": \"skeleton\",\"title\": \"Skelett\"} 3D Modelle finden Sie hier.\n","scripts-plugins":"Scripts und Plug-Ins bieten Ihnen Software-Verknüpfungen. Modellierung, Rendering, Texturieren - all dies einfacher gemacht werden kann Plug-In verwenden, die für alle Arten von Software wie Maya und V-Ray kommt.\n","space":"Der Raum ist eine Kategorie voller Low-Poly, Spiel-ready Modelle von Science-Fiction-{\"slug\": \"spaceship\",\"title\": \"Raumschiffe\"}, {\"slug\": \"asteroid\",\"title\": \"Asteroiden \"}, Planeten (einschließlich {\"slug\": \"earth\",\"title\": \"Erde\"} und realistischen Raum {\"slug\": \"shuttle\",\"title\": \"Shuttles\"} für die wissenschaftlich geneigt 3D Raummodelle sind für VR Weltraum-Simulator-Spiele geeignet.\n","sport-hobby":"Diese Kategorie encompases alles, was bezieht {\"slug\": \"field\",\"title\": \"Feld\"} Veranstaltungen, Sport und Hobbys im Allgemeinen. Von {\"slug\": \"football\",\"title\": \"Fußball\"} und Skipisten zu {\"slug\": \"guitar\",\"title\": \"Gitarre\"} und {\"slug\": \"piano\",\"title\": \"Piano\"}, sind diese nützlichen 3D Modelle für Anzeigen, Szenen und Animationen. Low-Poly-Versionen gibt es für Sportvideospiele.\n","textures":"Durchsuchen einer Bibliothek von schön gemacht 3D-Materialien und finden Sie die am besten geeignet, ready-to-use-Texturen \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e Spiel-ready \u003c/a\u003e oder fotorealistische Modelle, aktualisieren Sie Ihre Asset-Bibliothek herzustellen und 3D-Arbeiten verbessern.\n","various":"Dies sind die 3D Modelle, die nicht leicht in andere Kategorien gepasst haben, so dass wir eine erstellt, die sie alle akzeptieren würde. {\"title\": \"Rock\", \"slug\": \"rocks\"}, {\"slug\": \"clouds\",\"title\": \"Wolken\"}, {\"title\": \"Logo\", \"slug\": \"logo\"}, {\"title\" : \"Regenschirme\", \"slug\": \"umbrella\"} und andere nicht kategorisiert 3D Modelle sind hier.\n","vehicle":"Trucks, {\"title\": \"Krankenwagen\", \"slug\": \"ambulance\"}, Busse, Züge, Traktoren, {\"title\": \"Fahrräder\", \"slug\": \"bike\"} und Motorräder - Diese Kategorie ist für die Menschen, die ihre Straßen der Stadt wollen mit realistischen und sci-fi-Fahrzeuge, und für diejenigen, die Simulationsspiele oder Formating Poster gefüllt werden.\n","watercraft":"Wasserfahrzeug Abdeckungen {\"title\": \"Boote\", \"slug\": \"boat\"}, {\"title\": \"Yachten\", \"slug\": \"yacht\"}, {\"title\": \"Schiffe\", \"slug\": \"ship\"} und andere Wasserfahrzeuge. Die manipulierten 3D Modelle werden gut daran tun in Videospiele, Animation während CG-Modelle gibt es für Ihre Szenen oder andere Anwendungen, bei denen visuelle Treue ist wichtiger als polycount.\n"},"printable":{"art":"Dies ist, wo der künstlerische 3D Druck gehen. Suche nach Phantasie Skulptur Design klassische Werke und Statuen historischer Persönlichkeiten neu zu erstellen, oder Figuren inspiriert, vielleicht sogar 3D-druckbare Logos? Das ist die Kategorie.\n","fashion":"Schöne und stilvolle Dinge, die Sie tragen können, sind in dieser Kategorie. Sehen Sie, wenn Sie eine Anwendung für Phantasie Raum Alter finden {\"slug\": \"footwear\",\"title\": \"Schuhe\"}, Neuheit Gläser und Cosplay {\"slug\": \"accessories\",\"title\": \"Zubehör\"} hier gefunden.\n","gadgets":"Alle {\"title\": \"iPhone\", \"slug\": \"iphone\"}, {\"title\": \"iPad\", \"slug\": \"ipad\"} (und andere intelligente Geräte) Fälle können in Gadget-Kategorie gefunden werden. Von der einfachen 3D-Add-ons, um komplexe Teile und \"Hacks\" für Ihre Artikel, dies ist die Kategorie für die Kreativen, und Liebhaber der Technik.\n","games-toys":"Spiele und Spielzeug sind die Kategorie druckbare 3D Alien oder Monster Miniaturen bedeckt, winzige {\"title\": \"Panzer\", \"slug\": \"tank\"}, flippig {\"slug\": \"chess\",\"title\": \"Schach\"} Stücke, Filmfigur Helme, Hobby Utensilien und Zubehör. Dies ist die To-Go Kategorie für alle Dinge geeky.\n","hobby-diy":"Wenn Sie unsere nächsten großen Erfinder und wollen Ihr DIY-Projekt erstellen oder einige Ersatzteile für Ihren {\"slug\": \"drone\",\"title\": \"Drohne\"} bekommen, ist dies der Ort zu sein. Darüber hinaus bietet es eine Menge Dinge für die Menschen, die ihre eigenen Figuren oder Plastikpistole Repliken machen wollen.\
16518n","house":"Allerlei Krimskrams sind hier zu finden. {\"title\": \"Lampen\", \"slug\": \"lamp\"}, künstlerisch {\"title\": \"Schalen\", \"slug\": \"bowl\"}, und alle Arten von {\"title\": \"Inhaber\", \"slug\": \"holder\"}, ob Sie nicht eine richtige Stelle für Ihr Telefon finden können, halten, um von Zahnstochern, Stapeln von Stiften oder Schmuck auf Ihrem Schreibtisch.\n","jewelry":"Schmuck ist, wo Sie gehen Armbänder und Anhänger oder Ringe mit Diamanten zu bekommen. Während 3D Drucker  nicht gut genug ist Diamanten zu machen - noch - man kann immer noch einen Metallring als Basis für Ihre IT-Geek ring ausdrucken.\n","miniatures":"3D druckbare {\"title\": \"Zeichen\", \"slug\": \"character\"}, berühmt Leute, {\"title\": \"Sehenswürdigkeiten\", \"slug\": \"landmark\"}, Tiere, {\"title\": \"Männlich\", \"slug\": \"male\"}, {\"slug\": \"female\"}, {\"title\": \"Monster\", \"slug\": \"monster\"}, {\"title\": \"Aliens\",\"slug\": \"alien\"} - es ist alles hier. Egal, ob Sie Utility Vehicles sind Druck auf den Tisch legen oder Miniaturen für Tabletop-Spiele zu schaffen, ist es Ihre Kategorie.\n","science":"Was kann die Wissenschaft eingestuft werden? Nun, Modelle von Skelett {\"slug\": \"bones\",\"title\": \"Knochen\"}, Ärzte {\"slug\": \"tools\",\"title\": \"Werkzeuge\"} und Astrophysik bezogene 3D druckbare Modelle, die in vielen Studienprozesse verwendet werden können.\n"}},"printable":{"art":"Dies ist, wo der künstlerische 3D Druck gehen. Suche nach Phantasie Skulptur Design klassische Werke und Statuen historischer Persönlichkeiten neu zu erstellen, oder Figuren inspiriert, vielleicht sogar 3D-druckbare Logos? Das ist die Kategorie.\n","fashion":"Schöne und stilvolle Dinge, die Sie tragen können, sind in dieser Kategorie. Sehen Sie, wenn Sie eine Anwendung für Phantasie Raum Alter finden {\"slug\": \"Schuhe\"}, Neuheit Gläser und Cosplay {\"slug\": \"Zubehör\"} hier gefunden.\n","gadgets":"Alle {\"title\": \"iPhone\", \"slug\": \"iphone\"}, {\"title\": \"iPad\", \"slug\": \"ipad\"} (und andere intelligente Geräte) Fälle können in Gadget-Kategorie gefunden werden. Von der einfachen 3D-Add-ons, um komplexe Teile und \"Hacks\" für Ihre Artikel, dies ist die Kategorie für die Kreativen, und Liebhaber der Technik.\n","games-toys":"Spiele und Spielzeug sind die Kategorie druckbare 3D Alien oder Monster Miniaturen bedeckt, winzige {\"title\": \"Panzer\", \"slug\": \"Tank\"}, flippig {\"slug\": \"Schach\"} Stücke, Filmfigur Helme, Hobby Utensilien und Zubehör. Dies ist die To-Go Kategorie für alle Dinge geeky.\n","hobby-diy":"Wenn Sie unsere nächsten großen Erfinder und wollen Ihr DIY-Projekt erstellen oder einige Ersatzteile für Ihren {\"slug\": \"Drohne\"} bekommen, ist dies der Ort zu sein. Darüber hinaus bietet es eine Menge Dinge für die Menschen, die ihre eigenen Figuren oder Plastikpistole Repliken machen wollen.\n","house":"Allerlei Krimskrams sind hier zu finden. {\"title\": \"Lampen\", \"slug\": \"Lampe\"}, künstlerisch {\"title\": \"Schalen\", \"slug\": \"Schüssel\"}, und alle Arten von {\"title\": \"Inhaber\", \"slug\": \"Inhaber\"}, ob Sie nicht eine richtige Stelle für Ihr Telefon finden können, halten, um von Zahnstochern, Stapeln von Stiften oder Schmuck auf Ihrem Schreibtisch.\n","jewelry":"Schmuck ist, wo Sie gehen Armbänder und Anhänger oder Ringe mit Diamanten zu bekommen. Während 3D Drucker  nicht gut genug ist Diamanten zu machen - noch - man kann immer noch einen Metallring als Basis für Ihre IT-Geek ring ausdrucken.\n","miniatures":"3D druckbare {\"title\": \"Zeichen\", \"slug\": \"Zeichen\"}, berühmt Leute, {\"title\": \"Sehenswürdigkeiten\", \"slug\": \"Wahrzeichen\"}, Tiere, {\"title\": \"Male \", \"slug\": \"männlich\"}, {\"slug\": \"weiblich\"}, {\"title\": \"Monster\", \"slug\": \"Monster\"}, {\"title\": \"Aliens\",\"slug\": \"alien\"} - es ist alles hier. Egal, ob Sie Utility Vehicles sind Druck auf den Tisch legen oder Miniaturen für Tabletop-Spiele zu schaffen, ist es Ihre Kategorie.\n","science":"Was kann die Wissenschaft eingestuft werden? Nun, Modelle von Skelett {\"slug\": \"Knochen\"}, Ärzte {\"slug\": \"Tools\"} und Astrophysik bezogene 3D druckbare Modelle, die in vielen Studienprozesse verwendet werden können.\n"},"titles":{"aircraft":"Flugzeug-3D-Modelle","animals":"Tier-3D-Modelle","architectural":"Architektur-3D-Modelle","car":"Auto-3D-Modelle","character":"Charakter-3D-Modelle","exterior":"Außen-3D-Modelle","food":"Lebensmittel-3D-Modelle","furniture":"Möbel-3D-Modelle","household":"Haushalts-3D-Modelle","industrial":"Industrielle 3D-Modelle","interior":"Innenraum-3D-Modelle","military":"Militär-3D-Modelle","plant":"Pflanzen-3D-Modelle","space":"Weltraum-3D-Modelle","textures":"CG-Texturen-3D-Modelle","vehicle":"Fahrzeug-3D-Modelle","watercraft":"Wasserfahrzeug-3D-Modelle"}},"title":{"cg":" 3D Modelle","print":" 3D-Druckdateien"}}
16518,"competition":{"digital-art-competition":{"categories":{"page_description":"in dem durch die Übermittlung Ihrer Werke Charakter Illustration, Character Konzept Design, Umwelt Illustration, Umwelt Konzept Design, Objektgestaltung und Objekt Konzept Design Kategorien CGTrader Digitalen Kunst-Wettbewerb teilnehmen. Wenn Sie mehrere große Werke haben, die verschiedenen Kategorien passen, können Sie sie in die entsprechenden Kategorien einzureichen. Wählen Sie eine Kategorie, die Preise für diese Kategorie zu sehen."}}},"conversations":{"action_button_confirmation":"Möchten Sie diese Anfrage wirklich erneut öffnen?","reply_posted":"Antwort wurde veröffentlicht.","with_yourself":"darf nicht mit dem Absender identisch sein."},"date":{"month_names":[null,"Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"]},"devise":{"failure":{"too_many_requests":"Sie haben bereits eine E-Mail zum Zurücksetzen des Passworts angefordert. Bitte warten Sie einige Minuten, bevor Sie es erneut versuchen."}},"disabled_cookies_modal":{"button":"Stornieren","text_1":"Sie werden Millionen von großen 3D-Modelle zu erforschen, fügen Sie sie in den Warenkorb gelegt oder kaufen nicht in der Lage sein.","text_2":"Wenn Sie völlig CGTrader erleben wollen, empfehlen wir 
16518Cookies auf dieser Website ermöglicht.","title":"Es scheint, dass Sie Cookies in Ihrem Browser deaktiviert haben"},"emails":{"error_notification":"Abonnement nicht gefunden.","success_subscribed_notification":"Sie haben sich erfolgreich angemeldet.","success_unsubscribed_notification":"Sie haben sich erfolgreich abgemeldet."},"errors":{"messages":{"invalid_vat":"ist keine gültige %{country_adjective} USt-IdNr.","vies_down":"Die USt-IdNr. kann nicht überprüft werden: der VIES-Webservice ist nicht verfügbar. Bitte später noch einmal versuchen."}},"file_size_validator":{"errors":{"invalid_carrierwave":"Es wurde ein CarrierWave::Uploader::Base-Objekt erwartet","invalid_key":":%{key} muss eine nicht negative ganze Zahl sein","range_format":":in und :within müssen ein Bereich (Range) sein","range_unspecified":"Bereich nicht angegeben. Geben Sie die Option :within, :maximum, :minimum oder :is an."}},"file_type":{"default_error":"Kein Dateipfad angegeben."},"footer":{"bottom-row":{"privacy":"Datenschutz","terms-link":"Allgemeine Geschäftsbedingungen"},"links":{"buyLinks":[{"text":"3D-Modelle kaufen","link":"/de/3d-modelle"},{"text":"Für Unternehmen","link":"/discover/business-account"},{"text":"Kostenlose 3D-Datensätze","link":"/discover/datasets"},{"text":"3D-Designer beauftragen","link":"/3d-modeling-jobs"},{"text":"Kostenlose 3D-Modelle","link":"/de/frei-3d-modelle"},{"text":"Kostenlose 3D-Druckmodelle","link":"/de/frei-3d-druck-modelle"}],"communityLinks":[{"text":"Community","link":""},{"text":"Top-Designer","link":"/designers"},{"text":"Challenges","link":"/challenges"},{"text":"Forum","link":"/forum"}],"companyLinks":[{"text":"Unternehmen","link":"/discover/about-us"},{"text":"Blog","link":"/blog"},{"text":"Karriere","link":"/pages/careers"},{"text":"Hilfecenter","link":"https://help.cgtrader.com"}],"sellLinks":[{"text":"3D-Modelle verkaufen","link":"/discover/sell-3d-models"},{"text":"Steigern Sie Ihre Verkäufe","link":"discover/sell-more"},{"text":"3D-Markteinblicke","link":"/discover/3d-market-trends"},{"text":"Top 3D-Modellsuchen","link":"/discover/3d-models-top-searches"},{"text":"Top 3D-Drucksuchen","link":"/discover/3d-print-models-top-searches"}]},"subscribe":{"button-active":"Abonnieren","button-disabled":"Gezeichnet","header":"Abonnieren Sie unsere Nachrichten:","placeholder":"Geben sie ihre E-Mail Adresse ein","terms-link":"Nutzungsbedingungen","text":"Mit der Anmeldung bestätigen Sie, dass Sie gelesen haben und akzeptieren Sie unsere"}},"general":{"description":"CGTrader ist ein Online-Marktplatz, auf dem 3D-Künstler und Designer können, verkaufen oder teilen Sie 3D Modelle kaufen. Es wurde gegründet und wird von einem Team von 3D-Designern und Ingenieuren entwickelt. Unser Ziel ist es 3D-Aktienmarkt demokratisieren und die Art und Weise ändern, wie wir 3D Modelle online zu verkaufen. Indem Autoren maximale Lizenzgebühren verdienen, sind wir bestrebt, ihre Motivation und erhöhen die Qualität der 3D Modelle zu erhöhen.","navigation":{"buy_3d_models":"Kaufen 3D Modelle","sell_3d_models":"Verkaufen Sie 3D Modelle"},"search_placeholder":"Suche in mehr als 2 Millionen Modellen","site-search__field":"%{model_count} 3D-Modelle durchsuchen","site_title":"3D-Modell-Speicher","site_title_postfix":" | CGTrader"},"generator":{"landing_pages":{"three_d_model":{"defaults":{"closing_cta":{"heading":"Lassen Sie KI Ihre Träume verwirklichen","subtitle":"Verwandeln Sie einen Prompt oder ein Referenzbild in ein produktionsfertiges 3D-Modell – verfeinern, retexturieren und exportieren Sie es in dem Format, das Sie benötigen."},"faq":{"heading":"Häufig gestellte Fragen","items":{"commercial":{"answer":"Die kommerzielle Nutzung Ihres 3D-%{object}-Modells ist mit einem kostenpflichtigen CGTrader AI-Plan möglich. Bitte lesen Sie die CGTrader Allgemeinen Geschäftsbedingungen für die geltenden Lizenzbedingungen für KI-generierte Modelle.","question":"Kann ich das generierte %{object}-Modell für kommerzielle Zwecke nutzen?"},"formats":{"answer":"Die 3D-Modelle von %{object} sind in den Formaten GLB, FBX, OBJ, STL oder USDZ verfügbar.","question":"In welchem Dateiformat erhalte ich mein %{object}-Modell?"},"no_skills":{"answer":"Keine Kenntnisse in der 3D-Modellierung erforderlich. Die gesamte Modellierung erfolgt über den %{object}-Modell-Generator. Das System verwaltet Geometrie, Proportionen und Struktur basierend auf Ihrem Prompt oder hochgeladenen Bild. Auch Anfänger können in wenigen Minuten ein umfassendes 3D-%{object}-Modell erstellen.","question":"Sind Kenntnisse in der 3D-Modellierung für einen %{object}-Modell-Generator erforderlich?"},"what_is":{"answer":"Der CGTrader 3D-%{object}-Modell-Generator, ein KI-basiertes Tool, erstellt ein dreidimensionales %{object}-Modell aus schriftlichen Anweisungen oder Referenzbildern. Er analysiert Ihre Idee und erstellt ein herunterladbares Modell für Design, Prototypen und Visualisierung. Unsere kostenpflichtigen Abonnements bieten höhere monatliche Limits.","question":"Was ist ein 3D-%{object}-Modell-Generator?"},"why_cgtrader":{"answer":"CGTrader bietet eine reibungslose Erfahrung, schnelle Verarbeitungszeiten und qualitativ hochwertige 3D-Ausgaben. Dieses Tool eignet sich für Profis und Anfänger, die eine zuverlässige Lösung für die Erstellung von 3D-%{object}-Modellen suchen.","question":"Warum den KI-%{object}-Generator von CGTrader nutzen?"}}},"h1":"KI-%{object}-3D-Modell-Generator","hero_alt":"KI-%{object}-3D-Modell-Generator","hero_height":617,"hero_width":1080,"how_to":{"heading":"%{object} aus Bild- oder Textprompts generieren","paragraphs":["Es ist sehr einfach, ein dreidimensionales %{object}-Modell zu gestalten. Klicken Sie in der linken Seitenleiste oben links auf die Schaltfläche, um Ihr %{object}-Bild hochzuladen und ein 3D-Modell zu erstellen. Sie können auch einen Prompt im Feld „Besc
16518hreiben Sie, was Sie erstellen möchten\" unten eingeben. CGTrader AI verfeinert Ihren Prompt automatisch, sodass eine kurze Beschreibung ausreicht.","Die rechte Seitenleiste bietet Ihnen umfangreiche Kontrolle über Ihr 3D-%{object}-Modell. Wählen Sie Einstellungen wie Mesh-Typ, Material, Prompt-Führung und Dateiformat. Sie können Ihr %{object}-Modell auch texturieren. Klicken Sie nach Eingabe Ihrer Text- oder Bildinstruktionen auf „Generieren\". Das Tool verarbeitet Ihre Eingabe in wenigen Minuten, abhängig von Systemlast, Material, Mesh-Typ, Formen, Seed-Werten und Qualität."],"steps":["Geben Sie Ihre Textbeschreibung ein und/oder fügen Sie ein Referenzbild hinzu, um die Genauigkeit Ihres 3D-%{object}-Modells zu verbessern.","Wählen Sie eines unserer Voreinstellungen oder eine KI-Engine, geben Sie eine benutzerdefinierte Polygonanzahl an und wählen Sie den Materialtyp für Ihr 3D-%{object}-Modell.","Klicken Sie auf „Generieren\", um ein 3D-%{object}-Modell aus Ihrem Prompt und/oder Referenzbild zu erstellen.","Laden Sie Ihr erstelltes 3D-%{object}-Modell in verschiedenen Formaten herunter (GLB, FBX, OBJ, STL oder USDZ)."]},"intro":"Suchen Sie nach einer einfachen und schnellen Möglichkeit, in wenigen Minuten ein hochwertiges 3D-%{object}-Modell zu erstellen? Unser fortschrittlicher 3D-%{object}-Modell-Generator, CGTrader AI, kann schriftliche Befehle oder Referenzbilder in vollständig funktionierende Modelle umwandeln. Er basiert auf führenden 3D-Generierungs-Engines, darunter Meshy, Hunyuan und Tripo3D. Damit können sowohl Anfänger als auch Experten schnell 3D-Modelle für Visualisierung, Experimente und Prototypen erstellen. Er unterstützt Text-zu-3D und Bild-zu-3D und ist mit 3D-Druckern kompatibel. Sie können ein Bild im JPG- oder PNG-Format hochladen oder eine Beschreibung als Text eingeben. CGTrader AI übernimmt alle weiteren Schritte für Sie.","meta_description":"KI-%{object}-3D-Modell-Generator von CGTrader ☑️ Erstellen Sie detaillierte 3D-%{object}-Modelle aus Text oder Bildern. Jetzt kostenlos starten.","primary_cta":"Jetzt kostenlos generieren","title":"KI-%{object}-3D-Modell-Generator (kostenlos) | CGTrader"}}},"rules":{"errors":{"front_view_required":"Ein Bild der Vorderansicht ist erforderlich."}}},"home":{"index":{"3D-projects":{"info-section__cta":"Erfahren Sie mehr","info-section__description":"Wo Menschen mit spannenden 3D Projekten erfüllen talentierte 3D Designer","info-section__heading_html":"\u003cStrong\u003e 3D Projekte \u003c/strong\u003e Freier Hub"},"categories-conatainer":{"browse_all":"Alle Modelle durchsuchen","browse_all_tooltip":"Durchsuchen Sie alle 3D Modelle","discounts":"Rabatte"},"cgdream":{"cta":"Fangen Sie kostenlos an!","heading_html":"\u003cstrong\u003eEntdecke CGDream,\u003c/strong\u003e\u003c/br\u003e wo KI auf 3D-Modelle trifft.","text_html":"\u003cstrong\u003e CGDream \u003c/strong\u003e ist eine modernste Anwendung, die die Leistung generativer KI- und 3D-Modelle nutzt, um atemberaubende 2D-Bilder zu erzeugen. \u003cstrong\u003e Ihre kreative Reise beginnt jetzt. \u003c/strong\u003e"},"community":{"info-section__cta":"Trete der Community bei","info-section__heading_html":"Registriert CGTrader \u003cstrong\u003e Community \u003c/strong\u003e","info-section__icon-list_1":{"description":"Präsentieren Sie Ihre besten macht, erhalten Feedback und Anerkennung","heading":"Galerien"},"info-section__icon-list_2":{"description":"Lernen Sie neue Fähigkeiten und andere lehren,","heading":"Tutorials"},"info-section__icon-list_3":{"description":"Finden Tutorials und diskutieren andere 3D Fragen","heading":"Forum"}},"covid":{"timer-strip__item":"Kostenlose 3D Druck-Ressourcen für COVID-19 Schutz","timer-strip__item--button":"Durchsuchen 3D Modelle"},"enterprise":{"info-section__cta":"Erfahren Sie mehr","info-section__heading_html":"3D Dienste für die \u003cstrong\u003e Unternehmen \u003c/strong\u003e","info-section__icon-list_1":"Wir können für Sie die AR-Implementierung tun","info-section__icon-list_2":"Mehr Conversions Umsatz weniger Produktrück","info-section__icon-list_3":"Qualität gewährleistet mit strengem QA"},"header":"3D Modelle für VR / AR, 3D Druck und Computergrafik","marketplace":{"info-section__description":"Wählen Sie aus %{models_count} 3D Modelle für Anwendungen wie Spiele, AR / VR, Werbung, Animation, Kino und 3D Druck","info-section__heading_html":"3D-Modelle-\u003cstrong\u003eMarktplatz\u003c/strong\u003e","info-section__image-list_1":{"de
16518scription":"Kaufen Sie professionelle 3D Modelle für Ihr Projekt","heading":"3D Modelle"},"info-section__image-list_2":{"description":"Low-Poly-PBR 3D Modelle bereit für die Videospiele, VR \u0026 AR Echtzeitanwendungen","heading":"Low-Poly-PBR 3D Modelle"},"info-section__image-list_3":{"description":"Finden 3D Druck-Design für Ihren 3D Drucker","heading":"3D Druck modelle"},"view_all":"Alle"},"search_dropdown":{"ai_cards":{"image_to_3d":{"description":"Verwandeln Sie ein Bild in ein 3D-Modell","title":"Bild zu 3D"},"text_to_3d":{"description":"Beschreiben Sie Ihre Idee, um ein 3D-Modell zu erstellen","title":"Text zu 3D"}},"ai_section_title":"Mit KI erstellen","aria_label":"Suchvorschläge","columns":{"cg":{"search_label":"In 3D-Modellen suchen","title":"3D-Modelle"},"print":{"search_label":"In 3D-Druck suchen","title":"3D-Druckmodelle"}}},"sell":{"info-section__heading_html":"\u003cStrong\u003e Verkauf \u003c/strong\u003e Ihre 3D Modelle und Finden freie Projekte","info-section__icon-list_1":{"description":"Verkaufen Sie Ihre 3D Modelle und verdienen bis zu 80% Lizenzgebühren","link":"Verkaufen Sie 3D Modelle"},"info-section__icon-list_2":{"description":"Arbeiten mit Unternehmenskunden","link":"Holen Qualified"},"info-section__icon-list_3":{"description":"Lagern Sie Ihre Fähigkeiten und erhalten Sie bis zu 95% Lizenzgebühren","link":"Durchsuchen 3D Modellierung Jobs"},"info-section__icon-list_4":{"description":"Bauen Sie Ihr Portfolio und sichtbar","link":"Durchsuchen 3D Designer"}},"seo":{"description":"Entdecken Sie über 2 Millionen 3D-Modelle online für Druck-, Spiel-, VR/AR- und Designprojekte. Laden Sie herunter, passen Sie an oder beauftragen Sie 3D-Experten - alles auf CGTrader.","title":"CGTrader - 3D-Modelle und 3D-Druck-Marktplatz"}}},"homepage":{"budget_counter":{"range_0_10":"Unter 10 $","range_10_20":"10 $ - 20 $","range_20_15000":"Mehr als 20 $"},"quotes":{"quote_0":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Senken Sie Provisionen von den Verkäufern Mittel bessere Preise für die Käufer gemacht. Schauen Sie sich und vergleichen Sie die Preise bei CGTrader und die anderen Geschäfte."},"quote_1":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Der auffälligste Vorteil CGTrader ist Flat-Fee-Aktivierungssystem, das uns hohe Erträge liefert. Sie sind nicht eine Provision Prozentsatz unseres Umsatzes nehmen. Nach dem Verkauf erhalten wir das 100% des Preises. Nur wenn wir dieses Produkt verkaufen fortsetzen wollen, können wir es wieder zu aktivieren wieder um $ 5 bezahlen. Elegante Lösung."},"quote_10":{"author":{"name":"Threed Ken","username":"3dken"},"text":"Das Pauschalgebühr-Aktivierungssystem ist ausgeklügelte. Ich frage mich, warum niemand von diesem vorher gedacht hatte. Nach dem Verkauf zahlt eine geringe Gebühr für ein Produkt und das ist es zu aktivieren. Der Verkäufer ist in der vollen Kontrolle."},"quote_11":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"Es gibt zwei Dinge, die ich am meisten über CGTrader mögen. Erstens gibt es keine versteckten hier etwas bedeutet. Sie müssen nicht mit CGTrader vorsichtig sein. Zweitens ist dieser Speicher durch die Künstler und für die Künstler. Sie verstehen Sie besser. Etwas stört Sie hier ... sie wissen lassen, und sie sorgen dafür, dass Sie sich hier wohlfühlen."},"quote_12":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"Flat-Fee-Aktivierungssystem ist die beste! (Zumindest habe ich nicht über alles kommen besser als diese). Es ist sehr realistisch und fair gegenüber den Verkäufern. Sie kümmern sich nicht, wie viel Gewinn Sie machen, nur die feste Miete zahlen. Aber wenn Sie keinen Umsatz, Sie müssen auch nicht die Miete zahlen. Messe CG Handel ist hier!"},"quote_13":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"CGTrader zieht Kunden selbst aus Entwicklungsländern, weil die Preise sind günstiger als auf anderen Geschäften. Zur gleichen Zeit können Autoren mehr verdienen, weil es nicht so etwas wie hohe Provisionssätze ist. Es ist ein richtiger Weg, um die Arbeit von 3D-Künstler zu schätzen."},"quote_14":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"Intuitive Schnittstelle des Verlegers und innovative Tools wie Tag-Empfehlung und flexible Beschreibungsformen macht den Publishing-Prozess einfach und schnell."},"quote_15":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"CGTrader Ansatz von 3D-Verkauf ist anders und hat nichts gemein mit anderen Geschäften ich zu verkaufen verwendet. Zahlt eine geringe Gebühr von $ 5 nach dem Verkauf ist eine großartige Idee und lassen Sie mich zum besten Preis hier bieten. Kunde und Künstler sind mit dieser Arbeitsweise profitieren."},"quote_16":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"Das Upload-System ist einfach und klar. Es ist gut zu wissen, dass hinter dem Projekt sind die Menschen mit langjähriger Erfahrung in der Computer-Grafik-Angelegenheiten. Immer hatte ich ein Problem, festen CGTrader Team es schnell."},"quote_17":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"CGTrader erlaubt es mir, mein Profil mit meiner persönlichen Webseite zu verknüpfen. Kunden können mit mir Kontakt auf, wenn sie sich nicht sicher über ein Merkmal eines Postens sie kaufen möchten oder einfach nur eine Modifikation davon wollen. Es ist wie ein persönlichen 3D-Speicher in einem großen Mar
16518kt zu besitzen!"},"quote_2":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Wir haben eine große und reaktionsschnelle Unterstützung von CGTrader Team genossen. Das Personal freundlich bot die Veröffentlichung aller Modelle für uns zu tun, was eine große Hilfe für den professionellen Verkäufer ist."},"quote_3":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Auf dieser Plattform können wir viel bessere Preise für unsere Kunden bieten. Zum Beispiel sind alle unsere Produkte 20% niedriger als anderswo günstig und wir noch mehr Gewinn bekommen im Vergleich zu anderen Geschäften!"},"quote_4":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"Dies ist das beste System auf dem neuesten Stand. Alle anderen 3D-Online-Shops nehmen% unserer Gewinne. Dies war das Merkmal, dass unser Interesse an CGTrader von Anfang an gefangen. Kunden, den Kauf auf CGTrader, wissen, dass der größte Teil ihres Geldes gehen an den Autor, der am Ende wird mehr und bessere Modelle, wenn ihre Gewinne verbessern machen."},"quote_5":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"CGTrader ist der 3D-Online-Shop, die den am schnellsten wachsenden Umsatz und Gewinn für uns. Das Flat-Fee-Aktivierungssystem ist das Beste, was Sie finden können, wie es Content-Ersteller statt Mittelsmann belohnt."},"quote_6":{"author":{"name":"Ričardas Marozas","username":"Pumper"},"text":"Als ich $ 160 auf mein Paypal-Konto empfing, wusste nicht, was los ist, wie es noch nie passiert, aber dann dachte ich, dass es ein CGTrader Verkauf sein könnte und ich habe recht. Verkaufte die gleiche Packung auf Turbosquid im letzten Monat für $ 189, aber nach 40% Lizenzgebühr und 30% MwSt, alles, was ich bekam, war $ 52,92 - ein paar Cent auf $ 160 von CGTrader verglichen. Plus der Kunde bezahlt $ 29 weniger - nun, das ist ein richtiges Geschäft."},"quote_7":{"author":{"name":"Threed Ken","username":"3dken"},"text":"Am Anfang war ich skeptisch, was so ein gutes Geschäft für Verkäufer, die nicht die Norm für die großen 3D-Online-Shops ist. Wenn ich über die Flat-Fee-Aktivierungssystem gelernt, wurde ich von einem solchen kreativen Art und Weise beeindruckt digitale Assets zu verkaufen. Verkauf, aktiviert dann durch eine $ 5 Gebühr zahlen. Das alles macht Sinn für mich. Ich lud ein paar ausprobieren und wirklich, als ich meinen ersten Verkauf bekam, war es 100% Lizenzgebühren!"},"quote_8":{"author":{"name":"Threed Ken","username":"3dken"},"text":"Ich war so verwendet, um viele andere Websites, die Ladung zwischen 30% - 60% Provision, bis ich CGTrader entdeckt. Als Verkäufer, ich auf jeden Fall wie der hohe Anteil an Lizenzzurückkehrt."},"quote_9":{"author":{"name":"Threed Ken","username":"3dken"},"text":"Der Verkäufer Erfahrung ist fantastisch. Produkt-Vorlage ist ein Kinderspiel. Und ja, das System erlaubte mir jede Stufe der Art und Weise einen Entwurf meiner Produkt-Vorlage zu speichern."}}},"hyperwallet":{"failed_payment_agreement":"Etwas Unerwartetes ist passiert","success_notification":"Die Zahlungsvereinbarung wurde erfolgreich aktualisiert.","success_payment_agreement":"Die Zahlungsvereinbarung wurde erfolgreich übermittelt."},"item-search-filters":{"3d-models-url":"3d-modelle","3d-print-models-url":"3d-druck-modelle","dedicated-search-page-url":{"animated":"/animierte-","free":"/frei-","low_poly":"/low-poly-","pbr":"/pbr-","rigged":"/manipulierten-"}},"item_use_case":{"not_valid":"Ungültige Datei","pundit":{"not_authorized":"Sie sind nicht berechtigt, diese Aktion auszuführen."},"title":"Anwendungsfall für %{item_name}"},"items":{"already_purchased":"Sie haben diesen Artikel bereits gekauft.","filters":{"attributes":"Attribute","free":"Frei","max_value":"$ 500 +","min_value":"$ 2","reset":"Filter zurücksetzen"},"footer":{"next":"Nächster","per_page":"Pro Seite:","previous":"Bisherige"},"geometry_type_forbidden":"Geometrietyp verboten!","last_card":{"button_text":"Fordern Sie ein individuelles 3D-Modell","text":"Kann nicht das finden, was Sie suchen?"}
16518,"migrations":{"errors":{"invalid_owner":"Der neue Besitzer kann keine Artikel übernehmen.","unpublished_items":"Alle Artikel müssen veröffentlicht sein."}},"new_price_applied":"Neue Preise wurden erfolgreich auf alle Artikel angewendet.","new_price_applied_to_selected_items":"Neue Preise wurden erfolgreich auf die ausgewählten Artikel angewendet.","new_prices_to_filtered_models":"Neue Preise wurden erfolgreich auf alle gefilterten Artikel angewendet.","order_processed":"Bestellung bearbeitet.","order_processing":"Bestellung wird bearbeitet.","something_went_wrong":"Etwas ist schiefgelaufen","title_error":"Der Titel darf keine nicht-lateinischen Zeichen enthalten","uniq_user":"hat bereits einen Anwendungsfall für diesen Artikel eingereicht","upload_error":"Upload-Fehler.","validate_license_for_zero_price":"ist für kostenlosen Artikel ungültig","validate_minimum_price":"Preisangebote dürfen nicht unter dem Minimum von 50 % des ursprünglichen Preises liegen","verify_printable_formats_error":"Bitte laden Sie mindestens eine .stl-, .obj- oder .wrl-Datei hoch.","you_can_not_delete_models":"Sie können keine Modelle löschen"},"jobs":{"about":{"hire_designers_page":{"hero":{"description":"3D Projects macht es einfach, hochwertige freiberufliche 3D-Künstler zu engagieren und all Ihre Modellierungsherausforderungen zu lösen.","heading_html":"Lernen Sie die beste Freelancer-Plattform für Ihre \u003cstrong\u003e3D-Modellierungsaufträge\u003c/strong\u003e kennen"},"sections":{"call_to_action":{"description":"Veröffentlichen Sie Ihr Projekt kostenlos und sehen Sie, wie einfach es ist, Ihre nächste Modellierungsherausforderung mit 3D-Projekten zu lösen.","title":"Stellen Sie heute die besten 3D-Talente ein!"},"freelancer_selection":{"title":"Machen Sie Ihr nächstes 3D-Projekt zum Erfolg","widest_selection":{"description":"Geben Sie sich nicht mit weniger zufrieden. Mit 40.000 freiberuflichen 3D-Designern ist CGTrader die größte Plattform, um Sie mit qualifizierten Spezialisten zu verbinden: Modellierer, Animateure, Rigger, 3D-Druck-Experten und viele mehr.","title":"Größte Auswahl an 3D-Freelancern"}},"how_it_works":{"steps":{"describe":{"description":"Sagen Sie uns, was Sie brauchen. Verwenden Sie unser einfaches Formular, fügen Sie Bilder hinzu und veröffentlichen Sie Ihr Freelance-Angebot in wenigen Minuten.","title":"Beschreiben Sie Ihr Projekt"},"pay":{"description":"Zahlen Sie das Budget sicher im Voraus an CGTrader und lassen Sie Ihren Freelancer wissen, dass Sie startbereit sind. Chatten Sie, teilen Sie Anhänge und verfolgen Sie den Projektfortschritt bequem in Ihrem Arbeitsbereich. Geben Sie die Zahlung frei, wenn Sie mit dem Endergebnis zufrieden sind.","title":"Zahlen Sie für das Ergebnis"},"select":{"bullets":["Erhalten Sie Angebote von qualifizierten Designern direkt in Ihrem Posteingang","Chatten Sie in Echtzeit mit Designern","Durchsuchen Sie deren frühere Projekte","Sie können auch nach 3D-Fähigkeiten, Bewertungen und Software- oder Formatpräferenzen suchen, filtern und Freelancer einladen"],"title":"Wählen Sie den besten Freelancer aus"}},"title":"Erledigen Sie Ihren Job in 3 einfachen Schritten"},"payment_quality":{"description":"CGTrader stellt sicher, dass die freiberuflichen Dienstleistungen Ihre Anforderungen erfüllen und rechtzeitig geliefert werden. Unser QA-Team steht Ihnen jederzeit zur Verfügung.","title":"Qualität garantiert"},"secure_payments":{"description":"Unser Treuhandservice bedeutet, dass Ihr Geld geschützt ist und Sie nur zahlen, wenn Sie mit dem Endergebnis zufrieden sind.","title":"Sichere Zahlungen"},"workspace_convenience":{"description":"Vergessen Sie die Frustration, zwischen Tabs und verschiedenen Tools wechseln zu müssen. Mit dem dedizierten Arbeitsbereich können Sie alles an einem Ort verwalten: mehrere Projekte, Rechnungen, Unterhaltungen, Dateifreigabe und mehr.","title":"Bequemlichkeit auf einem einzigen Bildschirm"}},"shared":{"post_project_cta":"Projekt veröffentlichen"}},"navigation":{"find_jobs_cta":"3D-Jobs finden","hire_freelancers_cta":"3D-Freelancer einstellen","how_
16518it_works":"Wie es funktioniert","why_projects":"Warum 3D-Projekte"}},"agreed_to_terms_of_use":"Sie müssen den Geschäftsbedingungen von 3D Projects zustimmen, um fortzufahren.","budget_error_message":"ist zu hoch","check_designer_uniqueness_error":"Designer können nur einmal eingeladen werden","check_ownership_error":"Der Einladende ist nicht der Eigentümer des Projekts","check_status_error":"Designer können nur zu genehmigten Projekten eingeladen werden.","deadline_error_message":"Die Frist muss in der Zukunft liegen","discount_code_error":"Entschuldigung, der Rabattcode existiert nicht oder ist abgelaufen.","email_subject":"3D-Projektchance: %{subject}","error_check_application_uniqueness":"Sie können sich nur einmal bewerben","error_check_applier":"Sie können sich nicht auf Ihr eigenes 3D-Projekt bewerben","error_check_job_availability":"Benutzer ist nicht berechtigt, sich zu bewerben","error_check_job_status":"Projektbewerbungen sind ausgesetzt, bis unser neuer Moderationsprozess abgeschlossen ist, in der Regel innerhalb von 3 Werktagen. Vielen Dank für Ihr Verständnis!","modeling_request_failed":"Modellierungsanfrage fehlgeschlagen!","offer_has_been_sent":"Danke, Ihr Angebot wurde gesendet!","offer_is_closed":"Das Angebot ist geschlossen.","payment_successful":"Zahlung erfolgreich abgeschlossen.","payment_under_review":"Die Zahlung wird überprüft.","share_with_yourself_error":"Das Teilen von „Hire Me“-Vorlagen mit sich selbst ist nicht erlaubt","system_messages":{"buyer_cancels_job":"Das Projekt wurde storniert und in den Reiter %{archive_url} verschoben.","buyer_edits_hire_me_template":"Projektdetails wurden vom Kunden aktualisiert.","buyer_edits_job_details":"Projektdetails wurden aktualisiert.","buyer_marks_job_as_finished":"Projekt wurde als abgeschlossen markiert und in den Reiter %{archive_url} verschoben.","buyer_marks_job_as_finished_with_resell":"Kunde hat das Projekt als abgeschlossen markiert und erlaubt Ihnen, die Ergebnisse auf CGTrader zu verkaufen. Stellen Sie sicher, dass Sie das erstellte Modell \u003cb\u003e\u003ca href=\"/profile/upload/model\"\u003ehochladen\u003c/a\u003e\u003c/b\u003e!","buyer_negotiates":"Kunde hat Ihr Preisangebot verhandelt.","buyer_pays_for_the_job":"Projekt wurde vorausbezahlt und in den Reiter %{in_progress_url} verschoben.","designer_accepts_changes":"Designer hat die aktualisierten Details überprüft.","designer_accepts_offer":"Designer hat Ihr Angebot angenommen.","designer_edits_offer":"Designer hat sein Angebot bearbeitet.","designer_negotiates":"Designer hat Ihr Angebot verhandelt.","designer_withdraws_offer":"Designer hat sein Angebot zurückgezogen.","job_expires":"Projekt ist abgelaufen und wurde in den Reiter %{archive_url} verschoben."},"template_error":"Es wurden keine Projekte erstellt!","user_not_exists":"Benutzer mit diesem Benutzernamen existiert nicht.","want_that_job":"Ich will diesen Job!"},"kodama":{"invalid_request":"Ungültige Anfrage!"},"landing":{"consent_not_saved":"Die Zustimmung wurde nicht gespeichert","failure_notification":"Alle erforderlichen Felder des API-Zugriffsantrags müssen gültig sein.","success_notification":"Vielen Dank für Ihre Anfrage. Wir werden Sie in Kürze kontaktieren."},"layout":{"chunk_load_notice":"Beim Laden dieser Seite ist ein Fehler aufgetreten. Bitte lade die Seite neu.","cookies_policy":{"accept":"Akzeptieren","learn_more":"Mehr erfahren","text":"Unsere Website verwendet Cookies statistische Besucherdaten und Track Interaktion mit Direktmarketing-Kommunikation / Verbesserung unserer Website zu sammeln und Ihren Browser-Erfahrung verbessern. Bitte beachten Sie unsere Cookie-Hinweis für weitere Informationen über Cookies, Daten, die sie sammeln, die sie zugreifen können, und Ihre Rechte."},"translations_notice":{"cta":"Wechseln zu English","text":"Hallo! Wir übersetzen CGTrader. Übersetzungen sind im Gange und einige Abschnitte sind möglicherweise nicht auf Deutsch verfügbar."}},"model":{"already_purchased":"Sie haben dieses Produkt bereits gekauft. Sie können es von Ihrem \u003ca href='/profile/cart/purchased'\u003eWarenkorb\u003c/a\u003e herunterladen. Falls gewünscht, können Sie es erneut kaufen.","animated":"Animiert","artist_profile":"Künstlerprofil","author":"Autor","author_get_your_payment":"Der Autor erhält 100% Ihrer Zahlung. CGTrader ermöglicht Künstlern, maximale Einnahmen zu erzielen und motiviert sie, hochwertige Inhalte zu verkaufen.","author_products":"Weitere Produkte von \u003ca href=\"%{author_url}\"\u003e%{username}\u003c/a\u003e","authorised_seller":"Autorisierter Verkäufer bei CGTrader","authority":"Autorität","breadcrumb":{"3d_models":"3D-Modelle","home":"Startseite"},"bulk_download":{"download_all":"Alle Dateien herunterladen","files_included":{"one":"%{count} Datei enthalten","other":"%{count} Dateien enthalten"},"too_large":"Dieses Modell ist zu groß, um es als ein Archiv herunterzuladen (über %{limit}). Bitte laden Sie die Dateien einzeln herunter."},"buy":"Kaufen","cannot_download":"Nur Nutzer mit %{experience_needed} Rufpunkten können mehr als %{models} kostenlose Modelle herunterladen. Erfahren Sie mehr über Level und Reputation, indem Sie \u003ca href='%{reputation_url}' target='_blank'\u003ehier klicken\u003c/a\u003e.","collection":"3D-Paket","contact_author":"Autor kontaktieren","description":"Beschreibung","download":"Herunterladen","download_limit_exceeded":"Sie haben Ihr tägliches Download-Limit 
16518überschritten. Bitte versuchen Sie es morgen erneut. Dieses Limit gilt nicht für Nutzer, die in den letzten 12 Monaten einen Kauf getätigt haben.","download_modal":{"chat_with_us":"Chat mit uns","contact_support":"Bitte kontaktieren Sie uns über den Chat.","download_file":"%{name} herunterladen","empty":"Es wurden keine Dateien gefunden","generic_error":"Etwas ist schiefgelaufen, bitte versuchen Sie es erneut","link_error":"Es tut uns leid, aber mit Ihrem Download-Link scheint es ein kleines Problem zu geben.","pbr_ready":"PBR-fähig","preparing":"Ihr Download wird vorbereitet...","title":"Dateien herunterladen"},"format_downloads":"3D-Software-Format-Downloads","game_ready":"Spielbereit (Low-Poly)","geometry":"Geometrie","license":"Lizenz","make_offer":"Angebot machen","materials":"Materialien","messages":{"no_files":"Keine hochgeladenen Dateien","no_rating":"Für dieses Modell ist noch keine Bewertung verfügbar"},"model_details":"3D-Modell-Details","pbr_textures":"PBR-Texturen","plugin_used":"Verwendetes Plugin","polygons":"Polygone","price":"Preis","print_ready":"3D-Druckbereit","product_id":"Produkt-ID","product_support":"Produktsupport","product_type":"Produkttyp","published":"Veröffentlicht am","rated":{"error":"Sie haben dieses Modell bereits bewertet","success":"Danke für Ihre Bewertung dieses Modells"},"rating":"3D-Modell-Bewertung","ratings":{"desc":{"negative":"Nutzer, die dieses 3D-Modell gekauft haben, halten es für minderwertig","positive":"Nutzer, die dieses 3D-Modell gekauft haben, halten es für hochwertig"},"negative":"Negativ","positive":"Positiv"},"recently_viewed_models":"Kürzlich angesehene Modelle","renderer":"Renderer","report":"Melden","rigged":"Geriggt","scripts-plugins":"Skripte und Plugins","similar_models":"Ähnliche Modelle","statistics":"Statistiken","subscription":{"limit_reached":"Sie haben das Download-Limit für diesen Abrechnungszeitraum erreicht.","limit_reached_detailed":"Sie haben Ihr Download-Limit von %{limit} für diesen Monat erreicht. Ihr Limit wird am %{reset_date} zurückgesetzt. Um jetzt weitere Modelle herunterzuladen, können Sie diese einzeln kaufen.","limit_reached_detailed_no_date":"Sie haben Ihr Download-Limit von %{limit} für diesen Monat erreicht. Um jetzt weitere Modelle herunterzuladen, können Sie diese einzeln kaufen."},"tags":"Tags","textures":"Texturen","title":"%{item_title} 3D-Modell","unwrapped_uvs":"Entpackte UVs","uv_mapping":"UV-Mapping","vat_notice":"Alle Preise sind exklusive Mehrwertsteuer","version":"Version","vertices":"Scheitelpunkte","vr":"VR-Modelle"},"models":{"error":{"cannot_be_blank":" darf nicht leer sein."}},"navigation":{"3d-models":{"3d-printing":"3D Druckdateien","discounts":"Rabatte","find_designers":"Finden Sie Die Besten Designer","free":"Kostenlose 3D Modelle","pbr":"PBR","scanned_3d-models":"Gescannte 3D Modelle","scripts_apps":"Skripte \u0026 Apps","show-all":"Zeige alles","textures":"Texturen","title":"3D Modelle","vr_low-poly":"VR / Low-Poly"},"already_added_message":"hat bereits in den Warenkorb gelegt worden","already_purchased_message":"Sie haben diesen Artikel bereits gekauft.","aria":{"account":"Konto","cart":"Einkaufswagen","close_menu":"Menü schließen","logo":"CGTrader - 3D-Modelle / 3D-Designer","messages":"Nachrichten","notifications":"Benachrichtigungen","open_menu":"Menü öffnen","search":"Suchen","search_by_image":"Bildsuche"},"budgets":{"below_10":"Unter $10","collections":"Pakete","free":"Kostenlos","more_than_20":"Mehr als $20","more_than_50":"Mehr als $50","sale":"Im Verkauf","subscription":"Abonnement"},"cart_limit_reached":"Sie haben das Limit von 500 Artikeln in Ihrem Warenkorb erreicht. Um weiter einzukaufen, schließen Sie bitte Ihre aktuelle Bestellung ab und beginnen Sie eine neue für zusätzliche Artikel.","cart_widget":{"continue_shopping":"Weiter einkaufen","discount":"Rabatt mit Enterprise-Gutschein: ","discount_applied":"Angewendete Rabatte:","final":"Endgültiger Preis: ","have":"Du hast ","items":" Artikel im Warenkorb.","left_to_unlock_discounts":"Noch {{leftToUnlock}} bis zum Freischalten von Rabatten","potential_discount":"Möglicher Rabatt:","proceed":"Zur Kasse","recently_viewed_empty_text":"Beginnen Sie zu stöbern und Ihre kürzlich angesehenen Art
16518ikel erscheinen hier.","recently_viewed_empty_title":"Noch nichts hier","recently_viewed_tab":"Kürzlich angesehen","remainer":"Um CGTrader-Punkte zu aktivieren, fügen Sie hinzu: ","sale_discounts":"Rabatte angewendet: ","sale_time_left":"Verkauf endet in","sale_tooltip":"Rabatte aus unseren Sale-Kampagnen werden nur angewendet, wenn Ihr Warenkorb ${{threshold}} oder mehr erreicht.","sale_tooltip_text":"Dieser Artikel ist im Angebot, aber der Rabatt wird erst angewendet, wenn die Zwischensumme Ihres Warenkorbs ${{threshold}} oder mehr erreicht.","text":"Dein Einkaufswagen ist leer.","title":"Einkaufswagen","total":"Gesamt: ","used":"Credits verwendet: ","view":" Warenkorb ansehen","wishlist_empty_text":"Beginnen Sie zu stöbern und speichern Sie Ihre Favoriten für später — sie erscheinen hier.","wishlist_empty_title":"Ihre Wunschliste ist leer","wishlist_tab":"Wunschliste"},"community":{"awards":"Designer-Auszeichnungen 🏆","blog":"Blog","challenges":"Herausforderungen","community_ratings":"Kundenbewertung","designers":"3D-Designer","digital_art":"Digitaler Kunst-Wettbewerb","forum":"Forum","galleries":"Galerien","invite":"Einen Freund einladen","printlist":"3D drucke","sell_3d-models":"Verkaufen Sie 3D Modelle","title":"Gemeinschaft","tutorials":"Tutorials"},"conversations_widget":{"conversations":"Gespräche","messages":{"loading":"Laden Posteingang"},"types":{"cgtrader_support":"CGTrader Unterstützung","file_conversion_request":"Die Dateikonvertierung Anfrage","job_application":"3D-Projekt","model_feedback":"Product Feedback","model_support":"Model-Support","offer":"Angebotspreis","personal":"Persönliche Mitteilung","promotions":"Promotions"}},"create_ai":{"community_gallery":"Community-Galerie","free_trial":"Kostenlos testen","generate_3d_model":"3D-Modell generieren","generation":{"dismiss":"Benachrichtigung schließen","error_title":"Generierungsproblem","generating_title":"Ihr 3D-Modell wird generiert","open_model":"Modell öffnen","ready_title":"Ihr 3D-Modell ist fertig!"}},"create_with_ai":"Mit KI erstellen","credits_guest_widget":{"create":"Erstellen Sie jetzt ein Konto!","discount":" Rabatt","for":" für dein ","get":" und bis zu ","learn_more":"Erfahren Sie mehr über CGTrader Treue Credits ","link":"Hier","purchase":"erster Kauf!","sign-up":"Anmelden","text_below_1":"Credits können verwendet werden, beim Kauf \"","text_below_2":"\"Gekennzeichnete Modelle unterzeichnen und decken bis zu 30% Ermäßigung des Preises."},"credits_user_widget":{"condition_1":"CGTrader-Punkte können für den Kauf von Modellen verwendet werden, die mit dem \u003ci class=\"custom-font-icon custom-font-icon--small icon-wallet\"\u003e\u003c/i\u003e Symbol gekennzeichnet sind, und bieten bis zu 30% Rabatt auf ihren Preis.","condition_2":"Punkte können nur verwendet werden, wenn der Gesamtwert Ihres Warenkorbs \u003cstrong\u003e$%{minimum}\u003c/strong\u003e oder mehr beträgt.","condition_3":"Während aktiver Verkäufe oder Werbeaktionen können Punkte nicht verwendet werden.","condition_4":"Beachten Sie, dass Punkte ein Ablaufdatum haben - Ihre Punkte verfallen in \u003cstrong\u003e%{days}\u003c/strong\u003e Tagen.","earn_rate":"Kredit verdient Rate ","first_text_1":"Betrachten Sie es als ein Geschenk für Ihren ersten Einkauf - Sie können Kredite verwenden, wenn Einkauf \" ","first_text_2":" \" modelle gekennzeichnet und erhalten Sie bis zu 30% Rabatt von seinem Preis. Credits nur verwendet werden, wenn der Gesamtwert des Korbes ist gleich oder größer als","important":"WICHTIG:","learn_more":"Mehr erfahren","lifetime_spending":"Lebensdauer Ausgaben: ","list_item":"Durch die Erhöhung Ihrer Lebenszeit Ausgaben für CGTrader, Ihr Kredit verdient Rate steigt auch.","message":" Credits können nicht während des Verkaufs Kampagnen verwendet werden, aber man kann immer noch Ihre Lizenzstufe erhöhen und mehr Kredite für Ihren nächsten Einkauf sammeln.","more_credits":"Wie kann ich mehr Kredite bekommen?","next_level":"Nächste Stufe: ","notice":"HINWEIS: CGTrader-Punkte können während des laufenden Verkaufs nicht verwendet werden, stehen jedoch nach Ende des Verkaufs wieder zur Verfügung. In der Zwischenzeit können Sie sich tolle Angebote für 3D-Modelle \u003ca href=\"https://www.cgtrader.com/3d-models?sale_off=1\"\u003ehier\u003c/a\u003e ansehen.","par_1":"Du hast ","par_2":" CGTrader Credits.","par_2_variation":" in Punkte, die Sie einlösen können, wenn kein Ausverkauf verfügbar ist.","regular_text_1":"Credits können verwendet werden, beim Kauf \" ","regular_text_2":" \" Gekennzeichnete Modelle unterzeichnen und decken bis zu 30% Rabatt von seinem Preis.","title":"CGTrader Treuerabatt"},"enterprise":{"management":"3D Asset Management","title":"Produktion von 3D-Inhalten"},"hire_a_freelancer":"Freelancer beauftragen","item_add_message":"erfolgreich in de
16518n Warenkorb gelegt worden","item_restricted_message":"Dieser Artikel kann aufgrund von Sanktionen gegen das Land des Designers (RU/BY) nicht gekauft werden.","navbar":{"actions":{"new":"Neu","top_selling":"Bestseller","trending":"Im Trend"},"budgets":{"below_10":"Unter $10","free":"Kostenlos","more_than_20":"Mehr als $20","more_than_50":"Mehr als $50","sale":"Im Verkauf","up_to_10":"Bis zu $10"}},"projects":{"3d_customs":"Individuelles 3D","3d_printing":"3D-Druck","about":"Über 3D-Projekte","browse":"Durchsuchen Projekte","buyer_perspective":"Vermittlung eines 3D-Designers","for_designers":"Für Designer","my_projects":"Meine Projekte","seller_perspective":"Freiberuflicher 3D Projekte","start":"Starten Sie Ein Projekt","work":"Arbeit Mit Cgtrader"},"sell_3d-models":"3D-Modelle verkaufen","sellers_hub":"Verkäufer-Hub","subscription_includes_item_message":"Dieser Artikel ist bereits in Ihrem Abonnement enthalten.","tooltips":{"cart":"Einkaufswagen","conversations":"Meine Gespräche","discounts":"CGTrader Treuerabatt","notifications":"Benachrichtigungen","publisher":"Verleger"},"user":{"ban_notification":"Dieses Benutzerkonto ist gesperrt. Bitte kontaktieren Sie unseren Support für weitere Informationen.","cannot_refer_yourself":"Sie können sich nicht selbst empfehlen","email_confirmation":"Ihre E-Mail-Adresse wurde bestätigt.","email_reconfirmation":"wurde bereits bestätigt, bitte versuchen Sie sich anzumelden","ends_at":"Endet am: %{date}","handle_error_tfa_code_missmatch":"Der eingegebene Sicherheitscode ist falsch. Bitte versuchen Sie es erneut.","handle_error_tfa_code_missmatch_last":"Ihr Sicherheitscode ist abgelaufen. Wenn Sie fortfahren möchten, klicken Sie auf „E-Mail erneut senden“ und geben Sie den neuen Code ein.","handle_error_tfa_default":"Etwas ist schiefgelaufen, bitte versuchen Sie es später erneut.","handle_error_tfa_expired":"Ihr Sicherheitscode ist abgelaufen.","hundred_percent_royalties_popover":"Sie nehmen an einer temporären 100 %-Kampagne teil: Alle Verkaufserlöse aus neuen Modellen gehen bis zum Ende der Kampagne an Sie","invalid_email_message":"Bitte geben Sie eine gültige E-Mail-Adresse ein, um fortzufahren.","login":"Anmeldung","meta_description":"Sehen Sie sich das 3D-Designerprofil von %{username} auf CGTrader an, sein Portfolio und verfügbare 3D-Modelle. Entdecken Sie die besten 3D-Modelle von Top-Künstlern der Branche.","meta_title_suffix":"3D-Designer","not_valid_birthday":"Geburtstag ist ungültig.","offline":"Offline","online":"Online","page_title":"Designerprofil","rate_models_limit":"Sie können heute keine weiteren Modelle bewerten.","semantic_description":"3D-Modelle, Kunstwerke und Portfolio des Designers. Sehen Sie sich seine Kreationen auf CGTrader an","semantic_title":"Designerprofil","session_failure":"Falscher Benutzername/E-Mail oder falsches Passwort angegeben","session_password_expired":"Ihr Passwort ist aufgrund von Inaktivität abgelaufen. Bitte setzen Sie Ihr Passwort über den Link in der zugesandten E-Mail zurück.","session_two_fa_failure":"Codeüberprüfung fehlgeschlagen","session_unconfirmed":"Ihre E-Mail-Adresse wurde noch nicht bestätigt.","token_not_recognized":"Token wurde nicht erkannt","twitter_shareable_text":"Ein #3dartist, dem man folgen sollte","username_error":"darf keine Leerzeichen oder nicht-lateinische Zeichen enthalten"},"user_menu":{"buyer":{"freelancers":"finden Sie Freelancer","purchases":"Meine Einkäufe","sell":"Verkaufen Sie 3D Modelle","wishlist":"3D-Modell Wunschliste"},"dashboard":"Admin-Dashboard","logout":"Abmelden","public_profile":"Öffentlicher Profil","seller":{"analytics":"Analytics","find_new_projects":"Neue 3D-Projekte finden","freelance":"Freiberuflicher 3D Projekte","freelance_cgtrader":"Freiberuflicher mit CGTrader","models":"Meine Modelle","publish_project":"Projekt veröffentlichen","publishing":"Verlagswesen","sales":"Meine Verkäufe","subscription":"Abonnement","wishlist":"3D-Modell Wunschliste"},"settings":"die Einstellungen"}},"owly":{"failed_set_pending":"Fehler beim Setzen auf „ausstehend“.","failed_to_approve":"Fehler bei der Genehmigung.","failed_to_suspend":"Fehler beim Aussetzen.","failed_to_update_license":"Fehler beim Aktualisieren der Lizenz.","failed_to_update_status":"Fehler beim Aktualisieren des Genehmigungsstatus."},"print_models":{"browse_all_makes":"Alle 3D-Drucke durchsuchen","browse_all_models":"Alle Modelle durchsuchen","description":"Entdecken Sie trendige, druckfertige 3D-Modelle, druckbare Assets und STL-Dateien für Spiele, Kunst, Heimprojekte und mehr.","makes_description":"Entdecken Sie die neuesten 3D-Drucke, die von der Community hochgeladen wurden – reale Beispiele, die zeigen, wie 3D-Modelle in der physischen Welt aussehen.","makes_title":"Neue 3D-Drucke","new_description":"Entdecken Sie die neuesten 3D-Drucke in unserer Sammlung – frische Designs, die Sie für Ihr nächstes Projekt herunterladen und drucken können.","new_title":"Neue 3D-Druckmodelle","title":"Trendige 3D-Druckmodelle"},"profile":{"account_settings":{"account":{"email":"E-Mail","submit":"Speichern","title":"Profil","update":{"updated":"Profil aktualisiert"},"username":"Benutzername"},"email_settings":{"descriptions":{"ai_studio_notifications":"Benachrichtigungen über Ihre CGTrader AI Generierungen, einschließlich wenn ein noch nicht geöffnetes Modell fertig ist","communication":"Informationen über Ihre Kommunikation mit anderen CGTrader-Nutzern, einschließlich persönlicher Nachrichten, neuer Preis- und benutzerdefinierter Jobangebote, Feedback und Produktunterstützungsanfragen","news":"Informationen über CGTrader-Produkte, neue Feature-Ankündigungen und Community-News","onboarding_and_edu":"Informationen über Ihren Onboarding-Prozess und Tipps, um das Beste aus CGTrader herauszuholen","sales_and_offers":"Informationen über Sonderangebote, 3D-Modellverkäufe und CGTrader-Bonusguthaben","seller_sold_item":"Informationen über Ihre verkauften 3D-Modelle und erhaltenen Zahlungen"},"privacy_policy_html":"Bitte lesen Sie unsere \u003ca href='https://www.cgtrader.com/pages/privacy-policy' 'target='_blank'\u003eDatenschutzrichtlinie\u003c/a\u003e für weitere Informationen. ","t
16518itles":{"ai_studio_notifications":"CGTrader AI Benachrichtigungen","communication":"Kommunikation (Persönliche Nachrichten, 3D-Modell-Feedback, Community-Aktivitäten, Angebote)","news":"3D-News / CGTrader-Updates","onboarding_and_edu":"Onboarding \u0026 Bildung","sales_and_offers":"Verkäufe \u0026 Sonderangebote","seller_sold_item":"Verkaufs- \u0026 Zahlungsinformationen"},"tracking_attributes":{"ai_studio_notifications":"CGTrader AI Benachrichtigungen","communication":"Kommunikation","news":"3D-News / CGTrader-Updates","onboarding_and_edu":"Onboarding \u0026 Bildung","sales_and_offers":"Verkäufe \u0026 Sonderangebote","seller_sold_item":"Verkaufs- \u0026 Zahlungsinformationen"},"update":{"updated":"Ihre E-Mail wurde erfolgreich aktualisiert."}},"gdpr_success_notification":{"opt_in":"Danke! Sie haben sich erfolgreich wieder für unsere E-Mails angemeldet.","opt_out":"Sie haben sich erfolgreich von unseren Newslettern abgemeldet. Sie können diese Einstellung jederzeit in Ihren Profileinstellungen ändern.\n"},"location_tooltip_text":"\u003cdiv\u003eDieses Kontrollkästchen ermöglicht das Anzeigen des Standorts auf der Profilseite.\u003c/div\u003e \u003cdiv\u003eBeachten Sie, dass Ihr Standort aus Compliance-Gründen in jedem Fall Ihren Käufern (wenn Sie Verkäufer sind) und den Verkäufern, bei denen Sie gekauft haben, angezeigt wird.\u003c/div\u003e","moneybookers":{"account":"Skrill-Konto"},"password":{"current_password":"Aktuelles Passwort","new_password":"Neues Passwort","submit":"Passwort ändern","title":"Passwort","updated":"Ihr Passwort wurde erfolgreich aktualisiert. Sie können sich jetzt mit Ihrem neuen Passwort anmelden.","verify_password":"Passwort bestätigen"},"paypal":{"account":"PayPal-Konto","submit":"Speichern","title":"PayPal-Einstellungen","update":{"updated":"Ihre PayPal-Einstellungen wurden aktualisiert"}},"personal_info":{"areas_of_expertise":"Fachgebiete","country":"Land","description":"Ein paar Sätze über sich selbst","full_name":"Vollständiger Name","languages":"Sprachen","preffered_tools":"Bevorzugte Werkzeuge","submit":"Speichern","title":"Persönliche und berufliche Informationen","update":{"updated":"Ihre Informationen wurden erfolgreich aktualisiert"},"website":"Webseite"},"personalization":{"no_muted_title":"Keine stummgeschalteten Designer","title":"Stummgeschaltete Designer","tooltip":"3D-Modelle der aufgeführten Designer werden aus Ihren Suchergebnissen ausgeschlossen.","unmute_designer":{"confirm":"Sind Sie sicher?"}},"professional_info":{"allow_private_job_requests":"Käufer können Ihnen benutzerdefinierte 3D-Jobanfragen senden.","buyer_engagement_program_html":"Gewinnen Sie mehr Kunden, indem Sie ihnen ermöglichen, Ihre Modelle mit CGTrader CGTrader-Punkten zu kaufen. \u003ca href='https://www.cgtrader.com/pages/cgtrader-loyalty-discounts-explained' target='_blank'\u003eWeitere Informationen\u003c/a\u003e\n","pbr_program":"Der niedrigere Verdienstsatz wird nur angewendet, wenn die konvertierte Datei heruntergeladen wird","pbr_program_agreement":"Sie haben zugestimmt, am PBR-Programm für 24 Monate ab %{date} teilzunehmen. Weitere Details finden Sie in den Allgemeinen Geschäftsbedingungen. Bitte kontaktieren Sie unseren Support, wenn Sie Fragen haben.","product_analytics_participation_html":"Ich nehme an \u003ca href='https://www.cgtrader.com/profile/analytics'target='_blank'\u003eAnalytics\u003c/a\u003e teil und stimme deren Allgemeinen Geschäftsbedingungen zu\n","product_analytics_participation_tooltip_html":"Sie haben zugestimmt, an Analytics teilzunehmen und den Analytics-Nutzungsbedingungen (\u003ca href='https://www.cgtrader.com/pages/cgtrader-analytics-terms-of-service'target='_blank'\u003eLink\u003c/a\u003e) am %{accepted_at} zugestimmt. Wenn Sie Ihre Teilnahme beenden möchten, kontaktieren Sie bitte den CGTrader-Support\n"},"security":{"allow_mfa":"Aktivieren Sie die 2-Schritt-Verifizierung, um Ihr Konto zu schützen","use_authenticator_app":"Verwenden Sie eine Authentifikator-App für die 2-Schritt-Verifizierung","use_authenticator_app_tooltip":"Scannen Sie den QR-Code mit Ihrer Authentifikator-App (Google Authenticator / Authy), um Verifizierungscodes zu generieren.","use_email":"Verwenden Sie E-Mail für die 2-Schritt-Verifizierung"},"sidebar":{"general_settigns":"Allgemeine Einstellungen","member_since":"Mitglied seit","paypal":"PayPal","title":"Kontoinformationen"},"title":"Mein Konto"},"conversations":{"conversations":"Gespräche","messages":{"loading":"Laden Posteingang"},"types":{"cgtrader_support":"CGTrader Unterstützung","file_conversion_request":"Die Dateikonvertierung Anfrage","job_application":"3D-Projekt","model_feedback":"Product Feedback","model_support":"Model-Support","offer":"Angebotspreis","personal":"Persönliche Mitteilung","promotions":"Promotions"}},"meta":{"description":"Entdecke hochwertige 3D‑Modelle von %{username} auf CGTrader. Durchsuche einzigartige Assets für Spiele, VR/AR und Visualisierung.","title":"%{username} 3D Modell Designer"},"my_products":{"suspension_appeal":{"reasons":{"not_available":"N/A"}}},"settings_updated":"Ihre Einstellungen wurden erfolgreich aktualisiert."},"publishing":{"banned_error":"Ihr Konto wurde wegen der Veröffentlichung von Spam-Inhalten gesperrt. Wenn Sie glauben, dass dies ein Fehler ist, kontaktieren Sie bitte [email protected].","files_not_finalized":"Eine oder mehrere Dateien wurden nicht vollständig hochgeladen und können nicht veröffentlicht werden. Bitte entfernen Sie sie und laden Sie sie erneut hoch, bevor Sie veröffentlichen.","spam_suspect":"Ihr Konto ist nicht berechtigt, dieses Modell zu veröffentlichen. Wenn Sie glauben, dass dies ein Fehler ist, kontaktieren Sie bitte [email protected]."},"rating":{"anonymous_buyer":"Anonymer Käufer"},"routes":{"3d-models":"3d-modelle","3d-print-models":"3d-druck-modelle","animated-3d-models":"animierte-3d-modelle","controllers":{"three_d_studio":{"3d-models":"3d-models","images":"images"}},"discover":"entdecken","free-3d-models":"frei-3d-modelle","free-3d-print-models":"frei-3d-druck-modelle","low-poly-3d-models":"low-poly-3d-modelle","pbr-3d-models":"pbr-3d-modelle","pbr-textures":"pbr-textures","rigged-3d-models":"manipulierten-3d-modelle","search":"search"},"sale_off":{"no_overlapping_campaigns":"Die Kampagnendaten überschneiden sich mit einer anderen bestehenden Verkaufsaktion.","parse_discount_rules_text":"Zeile #{index} enthält nicht den erforderlichen Parameter 'discount:'"},"search":{"not_found":"Suchabonnement nicht gefunden","subscription_created":"Suchabonnement erstellt","successfully_unsubscribed":"Sie haben das Suchabonnement erfolgreich abbestellt"},"skills":{"3d-modeling":"3D Modeling","3d-print_modeling":"Print Modeling","3d-scanning":"Scanning","animating":"Animating","grooming":"Grooming","lighting":"Lighting","low-poly-modeling":"Low-poly Modeling","particles":"Particles","pbr-modeling":"PBR modeling","post-production":"Post Production","rendering":"Rendering","rigging":"Rigging","shaders":"Shaders","skinning":"Skinning","texturing":"Texturing","uv-mapping":"UV mapping"},"subscription_opt_ins":{"lock_period_error":"Some items cannot be opted out before the six-month lock period ends.","missing_items_error":"Items not found: %{items}."},"subscriptions":{"flash":{"active_subscription":"Sie haben bereits ein aktives Abonnement.","premium_upgrade_unavailable":"Das Upgrade auf Premium ist vorübergehend nicht verfügbar. Bitte versuchen Sie es später erneut.","update_payment_method":"Bei Ihrem Abonnement gibt es ein Zahlungsproblem. Bitte aktualisieren Sie Ihre Zahlungsmethode, anstatt ein neues Abonnement abzuschließen."}
16518,"payment_form":{"discountedPrice":"%{price}","features":{"first":"%{downloads} Assets / Monat — Modelle herunterladen oder mit KI generieren","second":"Sofortzugriff auf über %{models_count} essenzielle 3D-Modelle","third":"Lizenzfrei, keine KI"},"from_price_label":"ab","originalPrice":24.99,"per_model_label":"pro Modell","plan_name":"CGTrader Essentials Abonnement — Jahresplan, monatlich abgerechnet","premium":{"features":{"first":"%{downloads} Assets / Monat — Modelle herunterladen oder mit KI generieren","fourth":"Royalty-free, keine KI","second":"Sofortiger Zugriff auf über %{models_count} insgesamt verfügbare Modelle","third":"Profi-Modelle, die nicht in Essentials enthalten sind"},"plan_name":"CGTrader Premium Abonnement — Jahresplan, monatlich abgerechnet"},"price_unit":"/Monat","vatAmount":"%{vat_amount}"}},"tag":{"description":{"bottle":"Wissenschaftler sagen, dass eine Flasche Wein pro Tag für Sie ist nicht schlecht, aber wenn es um stärkere Getränke wie Whisky oder Bourbon kommt, sollten wir wirklich unsere Alkoholkonsum beobachten. Offensichtlich ist ein Glas Champagner an der Bar sieht viel cooler als eine Kunststoff-Flasche Wasser, aber denken Sie über die Möglichkeit, lebt ein längeres Leben und Bier wird wie ein vernünftiges Opfer zu sein scheint. Diese Kategorie bietet eine große Auswahl an Flaschen 3D Modelle, die von alkoholbedingten Objekten wie Weinregale und Korkenzieher auf alle Arten von Flaschen 3D Modelle (von Shampoo Olivenöl).","brain":"Das Gehirn ist eines der größten und komplexesten Organe im menschlichen Körper. Aufgrund seiner komplizierten Struktur und Funktion ist die Anatomie des Gehirns auch sehr komplex. Doch Wissenschaftler graben tiefer in die Architektur des menschlichen Gehirns, die Aufdeckung irrsinnig Geheimnisse. Eine breite Palette von realistischen und Gehirn 3D Modellen detailliert, die für Computergrafik-Anwendungen wie Werbung bereit sind, 3D-Visualisierung, 3D-Spiele, Web und jeden anderer Bereich der 3D-Konstruktion im Zusammenhang steht zum Download zur Verfügung.","building":"Es ist unklar, wo moderne Architekten ihre Inspiration, aber einige der weltberühmten Gebäude sind so Schneiden, dass sie wie Visionen aus der Zukunft aussehen. Und wir meinen nicht nur das äußere Teil ... In guten Händen ein Standard-Gebäudekonstruktion wird eine atemberaubende architektonische Schöpfung mit innovativen Innenarchitektur, unabhängig davon, ob es sich um ein Büro oder ein städtisches Gebäude. Unsere Auswahl an Gebäuden 3D Modelle zum Download bereit und kann in verschiedenen Bereichen angewandt werden - 3D-Animation, Grafik-Design, Werbung, 3D-Spiele oder Internet.","camera":"Fotografieren und Filmen ist derzeit eines der beliebtesten Berufsfelder bei jungen Menschen, aber nicht jeder mit einer professionellen Kamera ist ein Künstler oder ein Fotograf. Mit der Qualität der Handy-Kameras von Jahr zu Jahr zu verbessern, sehen wir Menschen, Bilder in jedem Schritt und diejenigen, die das neu entwickelten Bild haben Fähigkeiten Bearbeitung sind wirklich berühmt. Das Leben tut ein wenig anders aussehen, wenn man auf der anderen Seite der Linse ist, aber lassen Sie sich nicht mitreißen ... Eine breite Palette von detaillierter Kamera 3D Modelle ist zum Herunterladen von Grafikdesignern, Animationen und Spiele-Entwickler verwendet werden und Berufstätige in anderen 3D-verwandten Bereichen.","car":"Mit Blick auf diesen sehr detailliert und äußerst realistische 3D-Auto-Modellen, können Sie fast den Motor brüllen hören! Eine breite Palette von Fahrzeug 3D Modelle können Ihr Leben so viel einfacher machen, wenn Sie mit Computergrafik, Animation oder anderem 3D-verwandten Bereich zu kämpfen.","cell":"Wissenschaftler haben geschätzt, dass menschliche Körper enthält 5000000000-200.000.000 Billionen Zellen. Pflanzen und Tiere haben Zellen, auch. Wir wollen nicht zu langweilen Sie mit dieser Biologie Sachen, aber das breite Spektrum der Zelle 3D Modelle, dass wir reichen von Blut, Viren und DNA an Knochenmark und Zellwände. Ist das nicht ... nervenaufreibend?","chair":"Wir nehmen sitzen in Stühle gewährt und nicht immer feststellen, wie viel Charme für unser Heim oder Büro-Interieur hinzufügen können. Stuhl ist tatsächlich eines der zentralen Möbelstücke in jedem Haushalt. Hier können Sie qualitativ hochwertige und auffälliges Design Stuhl 3D Modelle, die in verschiedenen Bereichen angewendet werden können - von der Werbung und 3D-Visualisierung 3D-Spiele.","city":"Das Stadtleben ist ein exzentrisches Spiel: Straßen mit Renntaxen platzen, modernen Gebäuden, Fußgängern in perfekt zugeschnittene Bürokleidung, Schlaf Brücken und Parks ... Von Shanghai, nach New York (! Manhattan), nach London oder Paris - jede Stadt hat ihre eigene Magie. Sie können aus dieser reichen Auswahl an detaillierter Stadt 3D Modelle wählen, die in einer Reihe von Bereichen verwendet werden können - Werbung, C
16518omputergrafik, Animation, 3D-Visualisierung oder Spielen. Entdecken Sie das urbane Leben.","dragon":"Dragon ist wahrscheinlich das beliebteste Tier in der Fantasy-Welt leben. Es hat sich in so viele Mythen verschiedener Kulturen, Animationen, Filme und Bücher vorgestellt, dass es legendär geworden ist. Es wird vor allem durch Spiele-Entwickler geliebt, wie es die magische Zauber zu dunklen Höhlen und Verliese hinzufügt. Eine breite Palette von realistischen, in Ordnung gebracht Drachen 3D Modelle, die für Computergrafik-Anwendungen wie Werbung bereit sind, 3D-Visualisierung, 3D-Spiele, Web-und jede andere auf 3D-Design ist zum Download zur Verfügung verwandten Bereich.","earth":"Der Detaillierungsgrad in der kürzlich hinzugefügt Erde Ansicht in Google Maps hat eine Reihe von Menschen schockiert. Sie können den ganzen Planeten erforschen - Ozeane, Berge und Wüsten, entdecken Städte und versteckten Straßen, ohne Ihr Haus zu verlassen. Das ist einige unglaubliche Dinge Mutter Erde vor nicht erlebt hat. Eine Auswahl von realistischen Erde 3D Modellen, die für Computergrafik-Anwendungen wie Werbung bereit sind, 3D-Visualisierung, 3D-Spiele, Web und jeden anderer Bereich der 3D-Konstruktion im Zusammenhang steht zum Download zur Verfügung.","engine":"Manche Menschen sind in der Lage, den Motor zu benennen, indem Sie einfach auf den Klang hört es macht, und das ist wirklich erstaunlich. Unabhängig davon, ob es ein Düsentriebwerk oder ein Automotor, sind sie extrem Maschinen kompliziert, dass wir so oft benutzen, aber nicht zu viel Zeit denken verbringen darüber. Ein Motor ist ein Motor (bis es nicht mehr funktioniert). Diese Kategorie hat eine Reihe von verschiedenen Arten von Motor 3D Modelle, und es enthält auch Motorteile wie Propeller oder Zylinder, so dass wir sicher sind, finden Sie, was Sie brauchen.","female":"Es ist kein Witz, dass es erhebliche Unterschiede zwischen männlichen und weiblichen Gehirn, was bedeutet, dass es normal ist, dass ein durchschnittlicher Mann und Frau verhalten, fühlen und reagieren auf ganz unterschiedliche Weise. Aber es ist nicht alles über die Anatomie. Keine Liebesgeschichte, Film oder Spiel jemals ohne ein starkes Mädchen Charakter, weil die Macht der Dame Charme abziehbar ist nicht zu leugnen. Eine reiche Auswahl an realistischen weiblichen 3D Modelle bieten Ihnen alles, was Sie brauchen - von Nackt 3D Modelle weiblichen Atmungsorgane.","hand":"Der Grund, warum wir unsere Hände benutzen, für so viele Dinge, ist ihre außergewöhnliche Anatomie. Der Daumen allein durch neun verschiedene Muskeln gesteuert. Es ist schwer zu glauben, aber früh Hände waren exotischer als jede Hand heute - einige Arten hatten sieben Finger. Der menschliche Körper hat sich deutlich weiterentwickelt. Ein Arm oder eine Hand, wie wir sehen es heute sieht nichts, wie seine frühen Versionen. Eine breite Palette von realistischen, in Ordnung gebracht Hand 3D Modelle, die für Computergrafik-Anwendungen wie Werbung bereit sind, 3D-Visualisierung, 3D-Spiele, Web-und jede andere auf 3D-Design ist zum Download zur Verfügung verwandten Bereich.","head":"In der menschlichen Anatomie, der Kopf ist der obere Teil des menschlichen Körpers. einen 3D-Modellierung menschlichen Kopfes kann ein wenig kompliziert erweisen, da (auch wenn es so viele verschiedenen Formen und Größen sind) wir symmetrisch, realistisch aussehenden Köpfe schätzen - keine lustigen Augen, Ohren, Nase, Lippen oder Kiefer-Linie, bitte. Face ist ein sensibler Bereich, und es wäre einfach nur unhöflich sein, Spaß zu haben mit diesem. Unsere Auswahl umfasst realistisch männlich, weiblich, Kind und sogar Geschichte Figuren wie Harry Potter und Batman oder Prominente wie Tom Cruise und Scarlett Johansson Köpfe, die zum Download bereit sind.","helmet":"Denken Sie immer daran, dass die Gesundheit und Sicherheit an erster Stelle, so schützen Sie Ihren Kopf ist eine solide Idee. Mittelalterliche Ritter, Daft Punk und sogar die Ironman tragen Helme, so regelmäßig Superhelden wie Radfahrer oder Bauherren sollten sich keine Sorgen über uncool suchen. Diese Sammlung von Helm 3D Modelle, die zum Download verfügbar ist, wird sich als nützlich erweisen, wenn Sie in den Bereichen Grafikdesign, 3D-Animation, 3D-Spiele oder Visualisierung arbeiten.","house":"Es ist eine alte Wahrheit, dass ein Haus des Mannes sein Schloss, aber wenn es um den Aufbau dieses perfekte Haus kommt oder eine ideale Wohnung zu kaufen, wir neigen dazu, unsere Köpfe über sie zu verlieren. Zum Glück, dieses breite Spektrum von Hause 3D Modellen, die alles von architektonischen Details, innen und außen, bis hin zu Möbeln umfassen, werden Sie helfen, moderne 3D-Visualisierungen erstellen, die Schmerzen zu lindern.","human":"Wenn Sie die Anatomie des Menschen beginnen zu erforschen, merkt man, dass die Menschen so komplizierten und komplexen Maschinen sind, dass sie den Atem raubt. Jeder Körperteil, Muskel-, Gelenk-, Organ- oder Knochen Kombination in einem System verbunden ist, funktioniert nichts isoliert! Diese super-realistische 3D menschlichen Modelle ermöglicht es Ihnen, aus jedem Winkel anatomischen Strukturen zu untersuchen.","lamp":"Wenn Sie Ihr Haus umsehen und die Arten von Lampen zählen, dass Sie besitzen, werden Sie überrascht sein: Lesen, Tisch, Wand, Decke, Schreibtisch, Stativ Lampen ... Es 
16518ist genug, um Ihren Kopf drehen! Nicht die Stile zu erwähnen, dass sie kommen in - von extravagant modernen Lampen zu retro oder stilvollen futuristisch. In dieser Kategorie haben wir alle Arten von Lampe 3D Modellen, so durchsuchen und herunterladen in Grafik-Design zu verwenden, 3D-Visualisierung oder anderem 3D-verwandten Bereich.","light":"Haben Sie sich jemals gefragt, warum eine Glühbirne das Symbol einer brillanten Idee? Sicherlich kann eine helle Idee, die Architektur des Geistes aufzuklären, aber das ist es nicht. Eine Straßenlaterne, ein Kronleuchter oder jede andere Art von Lampe stehen würde nie eine Chance, dass vor Ort zu nehmen. Wenn das nächste Mal, wenn Sie an der Decke schauen und starren auf einer Art architektonischen Licht der geniale Idee dich nicht schlagen, könnte es sein, weil Sie an einem falschen Blitzsystem starren sind ... Unsere Auswahl an qualitativ hochwertigem Licht 3D Modelle werden sich praktisch, wenn Sie in den Bereichen Architektur, Computergrafik, 3D-Animation oder Design arbeiten.","machine":"Stellen Sie sich vor, wenn alle die Maschinen plötzlich aufgehört zu arbeiten: keinen Kaffee, kein bequemes Wäschewaschen, keine Geldautomaten an einer Ecke der Straße, keine Zeit im Fitnessstudio, nicht zu teuer Automaten ... Es ist deprimierend Ton, nicht wahr? Wir sind so an diese Haushaltsgeräte und elektronische Geräte verwendet, dass es buchstäblich die Welt Drehen aufhören würde! Zum Glück, unsere Auswahl an detaillierten Maschinen 3D Modelle reichen von Maschinengewehren für Stromgeneratoren, die nützlich für Designer beweisen werden die Arbeit mit Computer-Grafik, Werbung, 3D-Animation, 3D-Spielen oder Internet.","man":"Eine große Sammlung von Menschen 3D Modellen zum Download zur Verfügung. Sitzt einen Mann, einfacher Mann, fliegender Mann oder durchschnittlicher Mann, haben wir alles bekommen - außer dem perfekten Mann, dass jeder nach. Er ist ein fischig Charakter und wir hassen, dies zu bringen, aber es könnte ein Mythos sein ... Aber hey, sind Menschen geboren großartig zu werden, perfektionieren nicht.","robot":"R2D2 ist wohl der bekannteste Roboter in der Welt, aber es ist sicherlich nicht die einzige. Von der Industrie zu Spielzeug für den menschlichen Roboter, wir eine immer größere Auswahl an diesen mechanischen Maschinen sehen, die beide wunderbar und ein wenig beunruhigend ist. Es ist nicht allzu schwierig, die Menschen zu verstehen, die darauf bestehen, dass Computer, Technologie, Roboter sind eine zu erheblichen Auswirkungen auf den Menschen zu machen. Eine breite Palette von realistischen, in Ordnung gebracht Roboter 3D Modelle, die für Computergrafik-Anwendungen wie Werbung bereit sind, 3D-Visualisierung, 3D-Spiele, Web-und jede andere auf 3D-Design ist zum Download zur Verfügung verwandten Bereich.","ship":"Schiffe sind mächtige Schiffe und kommen in allen Größen, Farben und Formen. Sie können als Frachtschiffe, Kriegsschiffe oder Passagiersc
16518hiffe klassifiziert werden, aber sie arbeiten alle in der Tiefsee Gewässern. Wenn Sie die Beschwerden von Reisekrankheit erlebt haben, kann es nicht eine Ihrer Lieblingsbeförderungsmittel sein, aber viele Leute betrachten Bootsfahrten als sehr edel und angenehm. Unsere Auswahl an Schiff 3D Modelle zum Download bereit und wird die Standards der auch den anspruchsvollsten Kapitäns treffen.","skeleton":"Das menschliche Skelett oder Skelett-System (Anatomie) ist der innere Rahmen des Körpers, die von 270 Knochen bei der Geburt besteht. Es bietet die Körperstruktur, den Schutz und die Bewegung und ist eines der komplexesten Skelettsysteme. Eine reiche Auswahl an anatomisch korrekt, realistisch und Skelett 3D Modelle skaliert, die in 3D-Animation, Grafik-Design, Werbung, 3D-Spielen und Web-perfekt für den Einsatz sind, ist zum Download zur Verfügung.","skull":"Der älteste vollständigste, genetisch intakte menschliche Schädel wurde durch eine Vielzahl von ausgestorbenen Tieren mehr als 40 Meter unter dem Meeresspiegel umgeben entdeckt. Es ist eines der ältesten Skelette in der Neuen Welt gefunden, und es enthält auch alle großen Knochen des Körpers und einen intakten Schädel und Gebiss. Menschliche Schädel haben vor kurzem eine Sache in Mode und Design geworden, aber wenn man es tatsächlich einige Gedanken geben, sie Art machen uns alle aussehen wie Anatomie Freaks ... Diese Kategorie bietet eine breite Palette von Schädel 3D Modelle in Grafik-Design verwendet werden 3D, Animation, 3D-Spiele oder Werbung.","sofa":"Luxus oder einfach, modern oder antik, Leder oder minimalistisches Design? Wie gefällt Ihnen Ihr Sofa? Die Palette der Sofa 3D Modelle, dass wir erfüllen haben sogar den schlechtesten Perfektionisten. Wenn Sie aufhören, darüber nachzudenken, ist Sofa im Mittelpunkt jedes Wohnzimmer, so müssen Sie kreativ sein, wenn das Design der Wahl!","table":"Die neuesten Möbeltrends sind so vielfältig, dass die Wahl ein moderner, modischer Tisch zu stressig wird immer. Früher gab es eine Zeit, wenn Sie einen einfachen Tisch für Ihre bescheidene Feste hatte und das war genug, aber heute - Tabelle soll eine Erklärung sein, oder zumindest ein Blickfang Innen Akzent. Diese Auswahl bietet Ihnen eine große Auswahl an Tisch 3D Modelle bieten für jede Laune.","tower":"Jede Stadt hat einen besonderen Turm, dass jeder wirklich stolz. Denken Sie Eiffelturm oder Pisa Turm oder London Tower Bridge, geschweige denn alle modernen Wolkenkratzer, futuristische Gebäude und die Stadtlandschaft, dass die Touristen bewundern. Doch wenn es um die industriellen Teile der Stadt, Wassers oder Strommasten kommt, alle neigen dazu, ihre Augen abwenden ... Aber wir sollen jede architektonische Schöpfung, ganz gleich alten, industrielle oder modern schätzen lernen. Eine Reihe von detaillierten Turm 3D Modelle zum Download bereit und für Computergrafikanwendungen wie Werbung, 3D-Visualisierung, 3D-Spiele, Web und jedem anderen Bereich im Zusammenhang mit 3D-Design zur Verfügung.","tree":"Wenn Sie auf einer Mission sind einige Bäume oder exotischen Pflanzen zu pflanzen, sind Sie bei uns genau richtig. Diese Kategorie bietet eine Reihe von einfach zu bedienende, sehr realistische 3D-Baummodelle zu CG Künstlern und Architekten, die etwas Grün, um ihre Projekte hinzuzufügen streben. Wenn Sie gute Qualität zu schätzen wissen, sind wir diese Auswahl von 3D-Baum zu pflanzen Modellen zuversichtlich, dass Ihre Standards erfüllen!","truck":"einen LKW zu fahren ist ein harter Job, vor allem, wenn Sie Ihren Weg durch eine Stadt Stau finden müssen. Unabhängig davon, ob es ein Feuerwehrauto, Müllwagen, Nahrungsmittel-LKW oder ein generisches, dauert es eine große Geschicklichkeit diese riesigen Fahrzeuge zu manövrieren. Wir sind davon überzeugt, dass diese Auswahl realistischer, hochwertiger LKW 3D Modelle in 3D-Animation, Grafik-Design, Werbung oder anderem 3D-verwandten Bereich Gebrauch perfekt ist.","tv":"Früher gab es eine Zeit, in der Kamin das Herzstück eines jeden anständigen Hause war, jetzt TV-Gerät mit Spielern und Lautsprecher ist der König des Wohnzimmers (und sogar das Schlafzimmer). In den letzten Jahren hat das Fernsehen mit neuesten Technologien gefangen. Als Ergebnis haben wir intelligentes 3D-TV, Flach- und Plasmabildschirme, Millionen neue Funktionen und Touchscreen-Design. Kein Wunder, Fernsehen treibt Kino aus dem Markt. Eine große Auswahl an realistischen, detail TV 3D Modelle, die für Computergrafik-Anwendungen wie Werbung bereit sind, 3D-Visualisierung, 3D-Spiele, Web und jeden anderer Bereich der 3D-Konstruktion im Zusammenhang steht zum Download zur Verfügung."}},"tag_promotion":{"days_left_error":"muss zwischen 1 und 30 liegen","no_other_pending_promotions_for_same_item_tag":"hat bereits eine ausstehende Promotion"},"tooltips":{"buyer_loyalty_program_current_level_html":"Derzeit haben Sie ausgegeben %{total_price_paid} auf CGTrader und Ihre Kreditrate verdienen %{buyer_loyalty_rate}. \u003cbr/\u003e Es bedeutet, dass Sie erhalten %{buyer_loyalty_rate} Gutschriften aus jedem Kauf.\n","buyer_loyalty_program_html":"Dies ist der Prozentsatz, wie viel Sie von Ihren Käufen als neue Kredite bekommen. \u003cbr/\u003e Es wird bis zu 30% als Ihre Lebensdauer Ausgaben für CGTrader Erhöhung zu erhöhen. \u003cbr/\u003e Zur Zeit Ihre Kredit verdienen Rate %{buyer_loyalty_rate}.\n","buyer_loyalty_program_next_level_html":"Nächste Stufe %{next_lvl_expenses}  erhöht Ihre Kreditrate verdienen bis %{next_lvl_expenses_rate}. \u003cbr/\u003e Sie müssen Kauf für $%{left_until_next_lvl}  mehr zum nächsten Level zu erreichen.\n"},"trending_headers":{"models":"Sehen Sie sich weitere angesagte 3D-Modelle an","print":"Sehen Sie sich weitere angesagte 3D-Druckmodelle an"},"tutorial":{"destroy":"Tutorial wurde erfolgreich gelöscht.","submit_for_review_error":"Bitte füllen Sie alle erforderlichen Felder aus.","submitted":"Vielen Dank für das Einreichen Ihres Tutorials. Es wird überprüft und in Kürze veröffentlicht."},"valvat":{"country_adjectives":{"at":"österreichische","be":"belgische","bg"
16518:"bulgarische","cy":"zypriotische","cz":"tschechische","de":"deutsche","dk":"dänische","ee":"estnische","es":"spanische","eu":"europäische","fi":"finnische","fr":"französische","gb":"britische","gr":"griechische","hu":"ungarische","ie":"irische","it":"italienische","lt":"litauische","lu":"luxemburgische","lv":"lettische","mt":"maltesische","nl":"niederländische","pl":"polnische","pt":"portugiesische","ro":"rumänische","se":"schwedische","si":"slowenische","sk":"slowakische"}}},"en":{"_partials":{"search":{"submit":"Search"},"search_suggestions":{"group":{"cg":"3D Models","low_poly":"VR/ AR/ Low poly"},"title":"Suggestions"}},"activerecord":{"attributes":{"competition_image":{"image_name":"The uploaded images"},"content_report":{"categories":{"adult":"Adult or violent content","ai_generated":"AI-generated content","copyright":"Copyright infringement"},"category":"Reason"},"doorkeeper/application":{"name":"Name","redirect_uri":"Redirect URI"},"item_sketch":{"animated":"Animated","category":"Category","collection":"Pack","free":"Share for free","game_ready":"Game-ready","license":{"custom":"Custom","editorial":"Editorial","general":"General"},"materials":"Materials","plugins_used":"Plugins used","rigged":"Rigged","sell_source_files":"Allow downloading source files","textures":"Textures","title":"Product title","unwrapped_uvs":"Unwrapped UVs"},"job":{"agreed_to_terms_of_use":""},"job_application":{"agreed_to_terms_of_use":"You must accept Terms \u0026 Conditions"},"print":{"item_image":""}},"errors":{"messages":{"competitors_mention":"Please note that posting links to other marketplaces is not allowed %{location_title}.","embedded_script":"This file is not supported and cannot be uploaded. Please try a different file.","exceeds_daily_quota":"If you want to report more than two models, please contact us at [email protected]","file_size_limit":"should be less than %{file_size_limit}MB","file_too_big":"Attachment is too big, please try to not exceed 100MBs.","message_daily_limit":"You've reached your daily limit of active conversations. You can send more messages tomorrow.","new_username_confirmation":"New username did not match confirmation username.","personal_details":"It is not allowed to enter contact details in CGTrader conversations due to many fraud complaints and misunderstandings that occur while communicating or making purchases outside of the website. Both designer and customer are notified about new messages received on CGTrader by registered email. If you have any issues with communication please contact our \u003ca href='mailto:[email protected]'\u003esupport\u003c/a\u003e\n","record_invalid":"Validation failed: %{errors}","resolution_too_small":"should be high-resolution (minimum %{minimum} pixels)","restrict_dependent_destroy":{"has_many":"Cannot delete record because dependent %{record} exist","has_one":"Cannot delete record because a dependent %{record} exists"},"username_change_disable":"Your account is not eligible for a username change. Please contact our support team at \u003ca href='mailto:[email protected]'\[email protected]\u003c/a\u003e for assistance."},"models":{"address":{"attributes":{"base":{"invalid_vat":"The VAT number you've inserted does not exist in the country you selected","personal_or_company":"Please enter the personal or company details","vat_wrong_country":"Your inserted VAT number does not match the country you selected"},"zip_code":{"general":"Unfortunately, the ZIP code you provided in the checkout, seems to be invalid. Please double-check information you entered. If you believe the ZIP code is correct, please contact our customer support by emailing [email protected].\n","wrong_length":"Your ZIP code should only contain 5 digits","wrong_state":"Your state and ZIP code does not match. Did you mean to select state %{zip_code_state}?"}}},"comment":{"attributes":{"base":{"restricted_user":"Your account does not meet the requirements for  creating comments at this time.\u003cbr\u003e If you believe this is an error or need assistance, please contact our support\u003cbr\u003e team at [email protected]"}},"exceeds_comment_quota":"You have reached commenting limit. Please continue later or contact support."},"conversation":{"attributes":{"base":{"daily_limit_exceeded":{"message":"You have reached your daily limit for creating conversations."},"restricted_user":{"message":"Your account does not meet the requirements for sending messages at this time.\u003cbr\u003e If you believe this is an error or need assistance, please contact our support\u003cbr\u003e team at [email protected]"}}}},"conversation_message":{"attributes":{"base":{"restricted_user":"Your account does not meet the requirements for sending messages at this time.\u003cbr\u003e If you believe this is an error or need assistance, please contact our support\u003cbr\u003e team at [email protected]"}}},"doorkeeper/application":{"attributes":{"redirect_uri":{"forbidden_uri":"is forbidden by the server.","fragment_present":"cannot contain a fragment.","invali
16518d_uri":"must be a valid URI.","relative_uri":"must be an absolute URI.","secured_uri":"must be an HTTPS/SSL URI.","unspecified_scheme":"must specify a scheme."},"scopes":{"not_match_configured":"doesn't match configured on the server."}}},"forum/post":{"attributes":{"base":{"restricted_user":"Your account does not meet the requirements for creating forum posts at this time.\u003cbr\u003e If you believe this is an error or need assistance, please contact our support\u003cbr\u003e team at [email protected]"}}},"forum/topic":{"attributes":{"base":{"restricted_user":"Your account does not meet the requirements for creating forum topics at this time.\u003cbr\u003e If you believe this is an error or need assistance, please contact our support\u003cbr\u003e team at [email protected]"}}},"galleries":{"attributes":{"base":{"restricted_user":"Your account does not meet the requirements to publish Gallery projects at this time.\u003cbr\u003e If you believe this is an error or need assistance, please contact our support\u003cbr\u003e team at [email protected]"}}},"item":{"attributes":{"base":{"restricted_user":"Your account does not meet the requirements for creating items at this time.\u003cbr\u003e If you believe this is an error or need assistance, please contact our support\u003cbr\u003e team at [email protected]"}}},"item_tag":{"invalid_tag":"This tag can't be used. Please enter a different one"},"job":{"attributes":{"base":{"restricted_user":"Your account does not meet the requirements for creating projects at this time.\u003cbr\u003e If you believe this is an error or need assistance, please contact our support\u003cbr\u003e team at [email protected]"},"deadline":{"after":"must be in future"}}},"job_application":{"attributes":{"agreed_to_terms_of_use":{"blank":" to apply for this Job."},"deadline":{"after":"must be in future"}}},"payment_agreement":{"attributes":{"base":{"invalid_vat":"VAT code you entered seem to be invalid. If you don't have one, you can leave this field empty. If you have a VAT code, try entering it again or contact the Customer Success team for help.\n"}}},"print":{"attributes":{"item_image":{"blank":"Please provide image"}}},"scholarship_application":{"attributes":{"email":{"invalid":"To confirm you are from an educational institution we ask you to apply with and .edu email (ex. [email protected]). If you have a different email, please contact us via [email protected].\n","taken":"This email has already been taken."}},"base":{"blank":"Please make sure you filled in all fields."}},"user":{"attributes":{"username":{"blocked_for_one_year":"already changed this year. You will be able to change your username again on %{available_on}.","reserved":"is already taken. Please use a different username."}}},"wish_list_item":{"attributes":{"base":{"purchased":"You have already purchased this item"}}}}}},"activity":{"sale":{"create":{"payment_account":{"moneybookers":"Skrill Account","moneybookers_credit_card":"Skrill Account","paypal":"Paypal Account","paypal_credit_card":"Paypal Account"}}}},"admin":{"access":{"Admin::AccessController":{"edit_user":"edit_user","index":"index","update_user":"update_user"},"Admin::AdobeChallengeItemsController":{"edit":"edit","update":"update"},"Admin::AdobeChallengesController":{"index":"index","show":"show"},"Admin::BannedIpsController":{"create":"create","destroy":"destroy","index":"index"},"Admin::BlogPostsController":{"create":"create","destroy":"destroy","edit":"edit","images":"images","index":"index","new":"new","publish":"publish","unpublish":"unpublish","update":"update","upload":"upload","upload_image":"upload_image"},"Admin::BonusCreditsController":{"create":"create","show":"show"},"Admin::CategoriesController":{"create":"create","edit":"edit","index":"index","new":"new","update":"update"},"Admin::ChallengesController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","publish":"publish","unpublish":"unpublish","update":"update","users":"users"},"Admin::CompetitionSubmissionsController":{"index":"index","search":"search","show":"show","update":"update"},"Admin::ConversationForbiddenWordsController":{"create":"create","destroy":"destroy","index":"index","update":"update"},"Admin::ConversationMessagesController":{"create":"create"},"Admin::ConversationsController":{"filter":"filter","index":"index","show":"show"},"Admin::CouponsController":{"destroy":"destroy","index":"index"},"Admin::CustomCtrValuesController":{"create":"create","destroy":"destroy","index":"index"},"Admin::CustomReputationPointsController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","update":"update"},"Admin::DashboardBannersController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","update":"update","update_position":"update_position"},"Admin::DashboardController":{"index":"index"},"Admin::EmployeesController":{"destroy":"destroy","edit":"edit","index":"index","update":"update"},"Admin::FeaturesController":{"disable":"disable","enable":"enable","index":"index"},"Admin::FileConversionRequestFormatsController":{"create":"create","destroy":"destroy","index":"index"},"Admin::FileConversionRequestsController":{"filter":"filter","ignore":"ignore","index":"index","reopen":"reopen","reply":"reply","show":"show"},"Admin::FileExtensionsController":{"create":"create","destroy":"destroy","edit":"edit","update":"update"},"Admin::FileTypesController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","update":"update"},"Admin::FilterPresetsController":{"create":"create","destroy":"destroy","set_default":"set_default","show":"show"},"Admin::ForumBoardsController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","update":"update","update_position":"update_position"},"Admin::ForumTopicsController":{"destroy":"destroy","hide_from_community":"hide_from_community","index":"index","update_board":"update_board","update_sticky":"update_sticky"},"Admin::GalleriesController":{"destroy":"destroy","edit":"edit","index":"index","switch_status":"switch_status","toggle_parental_lock":"toggle_parental_lock","update":"update"},"Admin::GalleryCategoriesController":{"create":"create","destroy":"destroy","index":"index","update":"update"},"Admin::HundredPercentRoyaltiesController":{"create":"create","index":"index"},"Admin::ImagesController":{"index":"index"},"Admin::IpAddressesController":{"index":"index"},"Admin::ItemFilesController":{"attached":"attached","destroy":"destroy","download":"download"},"Admin::ItemOrdersController":{"authorize_payment":"authorize_payment","batch_destroy":"batch_destroy","comment":"comment","destroy":"destroy","index":"index","make_as_refunded":"make_as_refunded","paid_orders_csv":"paid_orders_csv","remove_item_rating":"remove_item_rating","search":"search","show":"show"},"Admin::ItemsController":{"batch_destroy":"batch_destroy","batch_restore":"batch_restore","block":"block","clear_cache":"clear_cache","delete_tags_by_stop_words":"delete_tags_by_stop_words","destroy":"destroy","edit":"edit","filter":"filter","index":"index","make_as_published":"make_as_published","manage":"manage","mark_as_non_adult":"mark_as_non_adult","rate":"rate","save_tags":"save_tags","set_as_featured":"set_as_featured","
16518stats":"stats","toggle_metaverse_assignment":"toggle_metaverse_assignment","trigger_image_upload":"trigger_image_upload","unblock":"unblock","update_index":"update_index","update_stop_words":"update_stop_words"},"Admin::JobOrdersController":{"destroy":"destroy","index":"index","refund":"refund","show":"show"},"Admin::JobTagsController":{"create":"create","destroy":"destroy","send_mail_to_related_designers":"send_mail_to_related_designers","update":"update"},"Admin::JobTemplatesController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","show":"show","update":"update"},"Admin::JobsController":{"approve_switch":"approve_switch","authorize_payment":"authorize_payment","destroy":"destroy","edit":"edit","index":"index","show":"show","update":"update"},"Admin::ManufacturersController":{"activate":"activate","create":"create","deactivate":"deactivate","destroy":"destroy","edit":"edit","index":"index","new":"new","show":"show","update":"update"},"Admin::Moderate::BatchesController":{"create":"create","index":"index","new":"new"},"Admin::Moderate::ChallengeItemsController":{"index":"index","update":"update"},"Admin::Moderate::ItemsController":{"approve_status":"approve_status","filter_bar":"filter_bar","hints":"hints","index":"index"},"Admin::Moderate::KeywordsController":{"create":"create","destroy":"destroy","index":"index","new":"new","refresh":"refresh"},"Admin::Moderate::ModelsController":{"filter":"filter","index":"index","update":"update"},"Admin::Moderate::TrendingItemsController":{"destroy":"destroy","index":"index","previews":"previews","reindex_page":"reindex_page","set_rating":"set_rating"},"Admin::MqsController":{"assign_reviewers":"assign_reviewers","destroy_item_file":"destroy_item_file","edit":"edit","index":"index","search":"search","update":"update","update_item":"update_item","upload_item_file":"upload_item_file"},"Admin::Notification::TemplatesController":{"index":"index"},"Admin::PageCategoriesController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","update":"update"},"Admin::PagesController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","update":"update"},"Admin::PartnershipDealsController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","search":"search","update":"update"},"Admin::PaymentAgreementsController":{"assign_payment_agreement_attributes":"assign_payment_agreement_attributes","edit":"edit","index":"index","payment_agreements_params":"payment_agreements_params","search":"search","update":"update"},"Admin::PayoutImportsController":{"corrupted_payouts":"corrupted_payouts","create":"create","destroy":"destroy","index":"index","proceed":"proceed","show":"show"},"Admin::PbrTextureNamesController":{"destroy":"destroy","edit":"edit","index":"index","update":"update"},"Admin::PrinterTypesController":{"index":"index"},"Admin::RedirectionsController":{"approve":"approve","create":"create","destroy":"destroy","index":"index"},"Admin::ReferralsController":{"index":"index"},"Admin::RefundImportsController":{"create":"create","example_csv":"example_csv","failures_csv":"failures_csv","index":"index","new":"new","proceed":"proceed","show":"show"},"Admin::RenderersController":{"create":"create","edit":"edit","index":"index","set_as_default":"set_as_default","update":"update"},"Admin::ReportedConversationsController":{"filter":"filter","index":"index","review":"review"},"Admin::ReputationLogsController":{"index":"index"},"Admin::ReputationScoresController":{"export":"export","index":"index","update":"update"},"Admin::SaleOffController":{"create":"create","reschedule_disable_sale_off_worker":"reschedule_disable_sale_off_worker","show":"show"},"Admin::SearchDebuggerController":{"show":"show"},"Admin::SellerDashboardNewsController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","update":"update"},"Admin::SettingsController":{"clear_cache":"clear_cache","toggle_payment_agreement_lock_settings":"toggle_payment_agreement_lock_settings"},"Admin::SimpleResourcesController":{"create":"create","destroy":"destroy","index":"index","new":"new","update":"update"},"Admin::SoftwareController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","update":"update"},"Admin::SuspiciousConversationsController":{"filter":"filter","index":"index","mark_as_ok":"mark_as_ok","show":"show"},"Admin::TakeoversController":{"impersonate":"impersonate","stop_impersonating":"stop_impersonating"},"Admin::TutorialCategoriesController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","update":"update"},"Admin::TutorialSoftwaresController":{"create":"create","destroy":"destroy","edit":"edit","index":"index","new":"new","update":"update"},"Admin::TutorialsController":{"destroy":"destroy","index":"index","update":"update"},"Admin::UrlShortcutsController":{"index":"index"},"Admin::UserTrustPoliciesController":{"filter":"filter","index":"index","update":"update"},"Admin::UserWarningsController":{"create":"create"},"Admin::UsersController":{"approve_artist":"approve_artist","ban":"ban","confirm":"confirm","destroy":"destroy","donate_store_credits":"donate_store_credits","edit":"edit","filter":"filter","index":"index","show":"show","
16518suspend_messaging":"suspend_messaging","toggle_oauth_owner_role":"toggle_oauth_owner_role","unapprove_artist":"unapprove_artist","update":"update","update_ftp_settings":"update_ftp_settings","update_password":"update_password","update_value":"update_value"},"Admin::WebflowPagesController":{"create":"create","destroy":"destroy","index":"index","update":"update"},"actions":{"edit":"Edit"},"controller_names":{"Admin::AccessController":"Access","Admin::AdobeChallengeItemsController":"Adobe challenge items","Admin::AdobeChallengesController":"Adobe challenges","Admin::AdobeSalesImportsController":"Adobe sales imports","Admin::ApproveController":"Models details approval in model page","Admin::BannedIpsController":"Banned IPS","Admin::BlacklistedDomainsController":"Blacklisted email domains","Admin::BlogPostsController":"Blog posts","Admin::BonusCreditsController":"Bonus credits","Admin::BulkActionsController":"Bulk Actions","Admin::BusinessAccountsController":"Business accounts","Admin::BusinessGroupsController":"Business groups","Admin::CategoriesController":"Categories","Admin::ChallengesController":"Challenges","Admin::CompetitionSubmissionsController":"Competition submissions","Admin::ConversationForbiddenWordsController":"Conversation forbidden words","Admin::ConversationMessagesController":"Conversation messages","Admin::ConversationsController":"Conversations","Admin::CouponOrdersController":"Coupon orders","Admin::CouponsController":"Coupons","Admin::CustomCtrValuesController":"Custom CTR Values","Admin::CustomReputationPointsController":"Custom reputation points","Admin::DashboardBannersController":"Dashboard banners","Admin::DashboardController":"Dashboard","Admin::EcommerceNewsController":"E-commerce News","Admin::EmployeesController":"Employees","Admin::EventsController":"Events","Admin::FeaturesController":"Features","Admin::FileConversionRequestFormatsController":"File conversion request formats","Admin::FileConversionRequestsController":"File conversion requests","Admin::FileExtensionsController":"File extensions","Admin::FileTypesController":"File types","Admin::FilterPresetsController":"Filter presets","Admin::ForumBoardsController":"Forum boards","Admin::ForumTopicsController":"Forum topics","Admin::GalleriesController":"Galleries","Admin::GalleryCategoriesController":"Gallery categories","Admin::HundredPercentRoyaltiesController":"Hundred percent royalties","Admin::ImagesController":"Images","Admin::IpAddressesController":"IP addresses","Admin::ItemFilesController":"Item files","Admin::ItemOrdersController":"Item orders","Admin::ItemsController":"Items","Admin::JobOrdersController":"Job orders","Admin::JobTagsController":"Job tags","Admin::JobTemplatesController":"Job templates","Admin::JobsController":"Jobs","Admin::ManufacturersController":"Manufacturers","Admin::Moderate::BatchesController":"Batches","Admin::Moderate::ChallengeItemsController":"Challenge moderation","Admin::Moderate::ItemsController":"Items","Admin::Moderate::KeywordsController":"Keywords","Admin::Moderate::ModelsController":"Models","Admin::Moderate::TrendingItemsController":"Trending items","Admin::MqsController":"MQS","Admin::Notification::TemplatesController":"Notification templates","Admin::PageCacheController":"Page cache control","Admin::PageCategoriesController":"Page categories","Admin::PagesController":"Pages","Admin::PartnershipDealsController":"Partnership deals","Admin::PaymentAgreementsController":"Payment agreements","Admin::PayoutImportsController":"Payout imports","Admin::PayoutsController":"Payouts","Admin::PbrTextureNamesController":"PBR Textures","Admin::PrinterTypesController":"Printer types","Admin::PromotionalConversationBatchesController":"Promotions","Admin::RedirectionsController":"Redirections","Admin::ReferralsController":"Referrals","Admin::RefundImportsController":"Bulk refunds","Admin::RefundsController":"Refunds","Admin::RenderersController":"Renderers","Admin::ReportedConversationsController":"Reported conversations","Admin::ReportedJobsController":"Reported Jobs","Admin::ReputationLogsController":"Reputation logs","Admin::ReputationScoresController":"Reputation scores","Admin::SaleOffController":"SaleOff","Admin::SearchDebuggerController":"Search debugger","Admin::SellerDashboardNewsController":"Seller dashboard news","Admin::SeoTagsController":"Seo Tags Information","Admin::SettingsController":"Settings","Admin::SimpleResourcesController":"SimpleResources (will be removed)","Admin::SoftwareController":"Software","Admin::SuspiciousConversationsController":"Suspicious conversations","Admin::TakeoversController":"User takeover","Admin::TutorialCategoriesController":"Tutorial sategories","Admin::TutorialSoftwaresController":"Tutorial softwares","Admin::TutorialsController":"Tutorials","Admin::UnrestrictUserController":"Unrestrict User","Admin::UrlShortcutsController":"Url shortener","Admin::UserTrustPoliciesController":"User Trust Policy","Admin::UserWarningsController":"User warnings","Admin::UsersController":"Users","Admin::WebflowPagesController":"Webflow pages"},"filters":{"username":"Username"},"title":"Access Management"},"business_accounts":{"item_unassigned":"Model unassigned.","items_assigned":"Assigned %{count} model(s)."},"challenges":{"adobe_status":{"approved":"Approved by Adobe","disapproved":"Disapproved by Adobe","none":"","resubmitted":"Resubmitted to Adobe","sent":"Sent to Adobe","to_send_to_adobe":"To send to Adobe"},"problems":{"asset_rotation":"Rotate asset so the most interesting side faces toward Z Positive axis in Maya or Y Positive Axis in 3ds Max","chaosgroup_standard":"Materials assigned to geometry must be Chaosgroup V-Ray Standard Material (VRayMtl) UV Layout (0-1 space, non-overlapping)","consistent_file_names":"Use camelCase or underscores and be consistent when naming the files. English only and be descriptive","consistent_normals":"Normals for the entire asset should have consistent facing direction","fit_theme":"Model does not fit the challenge theme","flatten_modifiers":"Flatten all modifiers (\"delete history\") before submission","formats":"Autodesk Maya or 3ds Max format only (.ma, .mb, .max, .3ds)","freeze_transformations":"Freeze Transformations (Reset xForm in Max)","geometry_separated":"Assets should have the geometry separated based on the materials","missing_faces_open_holes":"Ensure asset do not have any missing faces or open holes","models_placing":"Place models so they sit on top of the origin (0, 0, 0). No floating models","no_logos_trademarks":"Model should not display any logos or trademarks","real_world_size":"Use real world size (Centimeters)","sce
16518ne_materials":"Scene should consist of 3D model and connected V-Ray Standard materials only","texture_format":"Texture maps should be in PNG format (JPG and EXR accepted, but not preferred)","texture_image_size":"Texture image size should be square resolution, minimum of 2K (2048 x 2048). Use higher resolution if needed","topology_max_pole_edges":"Poles should have maximum of 5 edges","topology_triangles_quads":"Only quads are accepted. Triangles can be used sparingly and only where absolutely necessary"},"status":{"approved":"Public","disapproved":"Disapproved","re-published-review":"Re-published Review","re_published_review":"Re-published Review","to-review":"To review","to_review":"To review","withdrawn":"Withdrawn"}},"employees":{"teams":{"designers_team":"3D team","development":"Development","marketing":"Marketing","other":"Other","product":"Product","sales":"Sales","stakeholders":"Stakeholders","support":"Support"}},"file_conversion_requests":{"reply_message":{"can_use_existing_file":"We would like to let you know that you can import the existing %{format_name} file and will work as expected. You can buy this model with confidence or download the files if you have already made the purchase.","cannot_convert":"We are sad to let you know that such a conversion is not possible. We hope that you will be able to find other models in the marketplace that will suit your needs.","converted":"We would like to let you know that your request has been successfully completed and you can buy this model with confidence or download the required file if you have already made the purchase.","other":""}},"mqs":{"problems":{"clean_edge_flow":"Clean edge flow","correct_face_normals":"Correct face normals","description":"Product description","exchange_formats":"Exchange formats","frozen_controllers":"Frozen/zeroed rig transform controllers","good_naming":"Object, texture, layer and material naming","good_oriantation":"Correct model orientation (world-up axis)","good_position":"Model position at the origin (0,0,0)","good_transforms":"Reset/frozen model's transforms (position, rotation, scale)","instructions":"Intructions for custom rigs (readme, PDF document, etc.)","logical_arrangement":"Logical object arrangement (groups, layers, hierarchy)","native":"At least one native format (.max / .ma /.mb / .c4d / .lws / .blend)","no_coincident_vertices":"No coincident vertices","no_coplanar_faces":"No coplanar faces","no_creasing":"Holding/support edges (no Creasing)","no_default_names":"Rig hierarchy naming (no default names)","no_empty_objects":"No empty objects","no_errors":"Opens and renders without errors","no_isolated_vertices":"No isolated vertices","no_materials":"No objects without materials","no_mesh_holes":"No mesh holes","no_overlap":"No overlapping UVs","no_poles":"No poles with more than 5 edges","no_t_vertices":"No T-vertices","no_unnecessary_data":"No unnecessary data in the scene","no_uv_streching":"No visible UV stretching or obvious seams","previews":"Accurate model previews","quad_based_topology":"Quad based topology","real_world_scale":"Real world scale","smooth":"Smooth animation (no glitches or hitches)","striped_file_paths":"Str
16518ipped file paths (textures and other external assets)","structure":"File \u0026 Folder structure","uv_space":"0-1 UV space","wireframe_previews":"Wireframe previews"},"status":{"approved":"Approved","disapproved":"Disapproved"}},"notifications":{"job_failed_to_finish":"Job failed to finish","job_marked_as_finished":"Job has been marked as finished, payouts released","manually_distrusted_user":"User #%{id} is now distrusted! All future projects will require manual approval.","manually_trusted_user":"User #%{id} is now trusted! All future projects will be auto-approved.","refunding_disabled":"Refunding is disabled at the moment because payouts are being processed.","trust_policy":{"manually_distrusted_user_reason":"Manual distrusted by moderator: %{username}","manually_trusted_user_reason":"Manual trusted by moderator: %{username}"}}},"ancestry":{"cannot_delete_descendants":"Cannot delete record because it has descendants.","cannot_rebuild_depth_cache":"Cannot rebuild depth cache for model without depth caching.","conflicting_parent_id":"Conflicting parent id found in node %{node_id}: %{parent_id} for node %{node_id} while expecting %{expected}","exclude_self":"%{class_name} cannot be a descendant of itself.","invalid_ancestry_column":"Invalid format for ancestry column of node %{node_id}: %{ancestry_column}.","invalid_orphan_strategy":"Invalid orphan strategy, valid ones are :rootify, :adopt, :restrict and :destroy.","no_child_for_new_record":"No child ancestry for new record. Save record before performing tree operations.","option_must_be_hash":"Options for has_ancestry must be in a hash.","reference_nonexistent_node":"Reference to nonexistent node in node %{node_id}: %{ancestor_id}.","unknown_depth_option":"Unknown depth option: %{scope_name}.","unknown_format":"Unknown ancestry format: %{value}.","unknown_option":"Unknown option for has_ancestry: %{key} =\u003e %{value}."},"api":{"profile":{"actions":{"contact":"Contact","follow":"Follow","follow_tooltip":"Start following this designer to get updates about his content and easily filter favorite designers in Search results","hide":"Hide","hire_me":"Hire me","invite_client":"Start a Project","mute_tooltip":"Hide this designer’s models in the Search results","take_over":"Take over","unfollow":"Unfollow","unhide":"Unhide","view_all_models":"View all Designer's Models"},"private_project_review":"Private Project"},"responses":{"collection_archive_request":{"bad_parameters":"Bad parameters or parameters are missing.","success":"Collection Archive Request updated."},"file_ownership":"You do not own the specified file","invalid_file_id":"Invalid or nonexistent file ID provided","invalid_model_id":"Invalid or nonexistent model ID provided in URI","model_file_mismatch":"File does not belong to model"}},"arsenal":{"meta":{"description":"CGTrader ARsenal is a scalable enterprise 3D asset development platform representing a breakthrough in the way retailers plan, implement and finance 3D product visualization.","title":"CGTrader Arsenal - 3D Product Visualization Solution"}},"blogging_contest":{"meta":{"description":"CGTrader is calling for all bloggers interested in design to join the conversation about the future of design and win $4000 along the way.\n","title":"Bloggers' competition"},"notifications":{"success":"Thank you for participating! You will receive a confirmation email from us shortly."}},"browser":{"3d_models":"3D models","3d_print":"3D Print","active_filters":{"polygons":"%{range} polygons"},"adult_content":"Show Age+","animated":"Animated","breadcrumb":{"browse_by_type":"Browse by type","collection":"3D Model Packs","free":"free","free_models":"Free 3D Models","free_print_models":"Free 3D Printer Files","low_poly":"low-poly","low_poly_models":"VR / AR / low-poly 3D Models","low_poly_print_models":"Low-poly 3D Print Models","model_3d":"3D model","model_3dp":"3D print model","models":"3D Models","models_3d":"3D models","models_3dp":"3D print models","models_variant_0":"3D Models","models_variant_1":"3D Assets","models_variant_2":"VR Models","models_variant_3":"CG Models","models_variant_4":"3D Model Store","models_variant_5":"3D Objects","print_models":"3D Print Files","print_models_variant_0":"3D Print Models","print_models_variant_1":"3D Printer Models","print_models_variant_10":"3D Models For Print","print_models_variant_11":"3D Models To Print","print_models_variant_12":"3D Files For Printing","print_models_variant_13":"3D Models For Printer","print_models_variant_14":"Models For 3D Printing","print_models_variant_15":"3D Prints Models","print_models_variant_16":"3D Printable Files","print_models_variant_2":"3D Models For Printing","print_models_variant_3":"3D Printing Models","print_models_variant_4":"3D Print Files","print_models_variant_5":"3D Printing Files","print_models_variant_6":"3D Printer Files","print_models_variant_7":"3D Printable Models","print_models_variant_8":"Printable 3D Models","print_models_variant_9":"3D Printed Models"},"buyer_engagement_program":"Discounts","categories":"Categories","categories_page_title":"Buy 3D Models or download free 3D Models","category":{"meta":{"3d_models":"%{count} %{category} 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Download %{count} %{category} 3D print files ready for FDM, SLA, SLS, and other 3D printers, available formats STL, OBJ, 3DM, and more.%{page}","animated_3d_models":"%{count} %{category} animated 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Download %{count} %{category} free 3D files, available in MAX, OBJ, FBX, 3DS, C4D file formats, ready for VR / AR, animation, games and other 3D projects.%{page}","free_3d_print_models":"Download %{count} %{category} free 3D print files ready for FDM, SLA, SLS and other 3D printers, available formats STL, OBJ, 3DM and more.%{page}","lowpoly_3d_models":"Download %{count} VR / AR / low-poly %{category} 3D models ready for Virtual Reality, games and other real-time apps, available in FBX, OBJ, MAX, 3DS, C4D file formats.%{page}","rigged_3d_models":"%{count} %{category} rigged 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"cgt_verified":"3D Print Verified","collection":"Pack","descriptions":{"free_3d_models":"Here you can find and download free 3D models. Use filters at the left side of the page to find models for 3D print, game-ready 3D models or filter by file format such as Autodesk 3ds max, Maya, Softimage, Cinema4D or any other 3D modeling program.","game_ready_3d_models":"Here you can find game ready or low-polygon 3D models. Use filters at the left side of the page to filter by file format such as Autodesk 3ds max, Maya, Softimage, Cinema4D or any other 3D modeling program.","print_ready_3d_models":"Here you can find and download 3D print models. Use filters at the left side of the page to filter by file format such as Autodesk 3ds max, Maya, Softimage, Cinema4D or any other 3D modeling program."},"discounts":"Discounts","ext":{"meta":{"3d_models":"Download %{count} %{ext} files - %{software_name} 3D models ready for VR / AR, 3D design, animation, games and real-time apps.%{page}","3d_print_models":"Download %{count} %{ext} files - %{software_name} 3D print models ready for VR / AR, 3D design, animation, games and real-time apps.%{page}"}},"filter":"Filter","filter_button":"Apply filters","filters":{"adult_content_only":"Adult content only","cgt_standard_only":"CGT Standard","cgt_verified":"3D Print Verified","collections_only":"Packs only","content_filters":"Content filters","designer":"Designer","exclude_3d_print":"Exclude 3D Print","exclude_adult_content":"Exclude adult content","exclude_ai_generated":"Exclude AI generated content","exclude_editorial_license":"Exclude editorial license","exclude_free":"Exclude free","file_format":"File formats","following":"Following","free":"Free","has_marmoset":"Has 3D viewer","has_video":"Has video","include_ai_generated":"Include AI generated content","label":"Filters","poly_count":"Poly count","print_ready":"3D print","stl_files":"STL files","subscription":"Subscription","type":"Type"},"formats":"Formats","free":"Free","free_models":"Show free models only","free_to_premium":{"main":"%{category} 3D models","postfix":"in the premium listing.","prefix":"Do you need something special? You can find better quality"},"gallery":{"preview":"preview"},"game_ready":"Game ready","high_poly":"High-poly","items_page_title":"3D Models","license":{"exclude_branded":"Exclude Branded","exclude_editorial":"Exclude Editorial","prompt":"License","tooltip_more":"Read more","tooltip_text":" Don't show models that can only be used in non-commercial, educational purposes. ","tooltip_title":"Exclude Editorial:"},"low-poly":"Low-poly","low_poly":"Low-poly","meta":{"3d_models":"Browse all 3D models available on CGTrader to find the perfect one for your project. Sort, filter and compare 3D assets from professional designers.","3d_print_models":"View 3D print files from all categories on CGTrader. Refine, sort, and filter professional 3D printing objects to match your needs and printer capabilities.","animated_3d_models":"Animated models for use in games, movies and other projects. These models come pre-packed with some movements and specific actions. These help you save time and valuable resources during the development or prototyping process. Choose PBR textured models to make sure they’d look great in all lighting conditions.\n","free_3d_models":"Download %{count} free 3D models, available in MAX, OBJ, FBX, 3DS, C4D file formats, ready for VR / AR, animation, games and other 3D projects.%{page}","free_3d_print_models":"Download %{count} free 3D print files ready for FDM, SLA, SLS and other 3D printers, available formats STL, OBJ, 3DM and more.%{page}","lowpoly_3d_models":"Low poly models for download. These models have a smaller number of polygons and require less computing power. Low-poly models are best used in applications like VR, AR, games, especially mobile games. Choose from our collection of rigged and animated models for the best performance in your apps.\n","metaverse":"Enter the metaverse with our collection of high and low-poly 3d models rigged, animated and available any any file format.","newest_cg":"Download the latest 3D models, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","newest_print":"Download the latest and newest 3D print models, available in STL and 3MF file formats.","omniverse":"Build your virtual world with our Omniverse-ready 3D models. Choose from animated, rigged and PBR models with high and low polygon count.","page_prefix":"Page %{page_number} with ","pbr_3d_models":"Physically based rendering, or PBR, denotes models which are accurately and consistently rendered in any lighting conditions due to the texturing workflows used in the process. Developers can easily use these models across all major rendering engines.\n","rigged_3d_models":"Need movable models? Check out our collection of rigged 3D models, which are your go-to choice when you need a character or object to perform unique, specific movements. Rigged 3D models are available in Autodesk 3dsmax (.max), Maya (.ma), Softimage (.xsi), Cinema4D (.c4d) and many other file formats.\n","snap-ar":"With our 3D models you can join million of Snapchatters using Snap AR to create fun augmented reality experiences.","spark-ar":"Brighten up your Facebook and Instagram with a variety of Spark AR 3D models that are rigged, animated and ready to use.","stream_3d_print_models":"Browse through a variety of stream 3D print model categories to discover some of the most extraordinary 3D printable designs from professional 3D creators.","textures":"%{count} Textures 3D assets and materials available for download in various formats including .tga .sbsar .jpeg .png .tiff and other. %{page}","tiktok":"Make your video effects more impressive with TikTok 3D models. Our library can fulfill all of your creative needs and is constantly updated.","top_selling_cg":"Download top-selling 3D models of %{year}, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","top_selling_print":"Download top-selling 3D print models of %{year}, available in STL and 3MF file formats.","trending_cg":"Download the most popular and trending 3D models of %{month} %{year}, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","trending_print":"Download the most trending 3D printed items and products of %{year}, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","unity":"Find 3D models for your Unity project in any file format and any poly count. Everything you need to create a unique virtual environment.","unreal":"Create impressive visualizations with Unreal Engine compatible 3D models in any file format. Our library has low and high-poly models from every category."},"multiple_parts":"Multiple parts","optimized":"Low-poly / PBR","pbr":"PBR","pbr_texture":"PBR Texture","pbr_textures":{"description":"Download thousands of high-quality PBR textures for 3D models and game assets. Explore a massive library built for 3D artists, designers, and game developers.","h1":"Free \u0026 premium PBR textures and materials","intro":"Download high-quality PBR textures from our huge library of free individual materials and premium packs optimized for Unreal, Unity, Blender, Sketchup, and more.","tag_description":"Download high-quality %{tag} PBR textures for 3D models and game assets. Explore a massive library built for 3D artists, designers, and game developers.","tag_h1":"Free \u0026 premium %{tag} PBR textures and materials","tag_intro":"Download %{tag} PBR textures optimized for Unreal, Unity, Blender, Sketchup, and more. %{model_count} of free individual materials and premium packs.","tag_title":"%{tag} PBR textures and materials","title":"PBR textures and materials - free \u0026 premium"}
16518,"per_page":"Per Page","polygons_options":{"custom":"Custom range","gt_250k":"250k+","lt_5k":"Up to 5k","more_than_250000":"250K+","prompt":"Poly count","range_0_5000":"Up to 5K","range_100000_250000":"100 - 250K","range_10000_50000":"10 - 50K","range_100k_250k":"100k to 250k","range_10k_50k":"10k to 50k","range_50000_100000":"50 - 100K","range_5000_10000":"5 - 10K","range_50k_100k":"50k to 100k","range_5k_10k":"5k to 10k"},"pre_supported":"Pre-supported","price":"Price: $%{from}-%{to}","price_range":"Price","print_ready":"3D print ready","printable":"3D printed","reset_button":"Reset filters","rigged":"Rigged","rounded_count":{"exact":"%{n}","million":"%{n}M+","thousand":"%{n}+"},"sale_off":"On sale","search_placeholder":"Search keywords","show_all_formats":"Show all formats","simulation_ready":"Simulation-ready","sort_by":"Sort By","sort_by_options":{"best_match":"Best Match","highest_price":"Higher price","lowest_price":"Lower price","newest":"Newest","oldest":"Oldest","sales":"Top Selling","trending":"Trending"},"subcategory":{"meta":{"3d_models":"%{count} %{category} 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Download %{count} %{category} 3D print files ready for FDM, SLA, SLS, and other 3D printers, available formats STL, OBJ, 3DM, and more.%{page}","animated_3d_models":"%{count} %{category} animated 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Download %{count} %{category} free 3D models, available in MAX, OBJ, FBX, 3DS, C4D file formats, ready for VR / AR, animation, games and other 3D projects.%{page}","free_3d_print_models":"Download %{count} %{category} free 3D print files ready for FDM, SLA, SLS and other 3D printers, available formats STL, OBJ, 3DM and more.%{page}","lowpoly_3d_models":"Download VR / AR / Low poly %{tag} 3D models ready for Virtual Reality, games and other real-time apps, available in FBX, OBJ, MAX, 3DS, C4D file formats.%{page}","rigged_3d_models":"%{count} %{category} rigged 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"subtitle":{"aircraft":"In our hangar you will find 3D aircraft models from CG airplanes to drones that will elevate your simulations, videos and advertisements. Fully rigged airborne models are ready to jet off into your next game or animation project. Compatible with all 3D software our assets ensure your journey flies smoothly.","animals":"Ready to spark life into your virtual worlds? With countless 3D models of animals you are now ready to populate your digital landscapes. Find rigged animals to animate in your dynamic scenes and low-poly ones that will fit perfectly in your game. Buy or download assets for free without any format constraints to worry about.","animated":"Explore fully animated %{tag} 3D models to speed up your workflow and bring your projects to life instantly.\n","architectural":"A broad collection of 3D architecture models, each rich with elements and design details of real-world construction. They are your building blocks for creating lifelike renders, immersive game environments and engaging animations. Browse through detailed high-poly ones or simple yet beautiful low-poly PBR designs. A catalog of versatile architectural assets ensure your project's vision takes shape, no mater your software of choice.","car":"Your next ride awaits in our virtual garage. Browse through endless pages of 3D models of cars and expand your car collection for every project and occasion. Rigged low poly automobiles to be animated into your next racing movie and classic retros for a vintage vibe in your lifelike renders are ready to be discovered. Park them in your software of choice without format constraints.","character":"In our vast world of 3D models of characters, all mythical creatures, sci-fi beings and even humans are welcome.Objects of any universe are set to be the center piece of your future motion graphics project or video game. Animated assets from our collection come in all the popular 3D formats and are set to integrate into your workflow seamlessly.","custom"
16518:"Here you can buy or download free %{tag} 3D models. %{tag_heading_subtitle}","electronics":"%{count} electronics 3D models available for download. This  is a category for architects, interior decorators, level designers, scene makers and other creatives. 3D models of %{smartphones_link}, %{laptops_link}, %{cameras_link}, %{tvs_link} and more will help to accurately capture modern life’s obsession with devices and technology. There are 3D models available in file formats including FBX, OBJ, MAX, 3DS, C4D. Download free or buy premium models and use for animation, games and VR/AR projects.","exterior":"Imagine having a wide array of 3D exterior models at your fingertips. Our collection serves every architect's and designer's needs, providing everything from general assets to urban buildings and full cities. Ease your design process of highly detailed exterior rendering and visualization or up the details of your games with ready to use, diverse and versatile models available in the most popular 3D formats.","food":"Step into our virtual pantry with 3D models of food that can add mouthwatering realism to any scene. All the appetizing 3D assets from fruits and vegetables to pizzas and burgers await to be part of your animations. Looking for some beer or wine to decorate your barbecue scenes and renders? We've got you covered, served fresh and in all the popular file formats.","formats":"There currently are %{found_items_count} 3D models ready for download in the %{required_software_title} file format on \nthe marketplace. Feel free to browse the collection and do not forget to use the other search filters to find \nPBR, rigged, animated or printable 3D models for your projects. If you’re a game developer use the ‘Poly count’ filter \nto view low-poly 3D models which need less computational power to render. The majority of 3D assets on CGTrader are \navailable in several file formats, and if a model you want is unavailable in the %{required_software_title} file format, \nyou can always ask for a file conversion by selecting ‘Request free file format conversion’ on the product page. You can \nalso visit our 3D Jobs page and post a request for model conversion, custom models and any other modeling services you \nmight need.\n","free_general":"Free 3D models available for download from %{link_1} to %{link_2} 3D assets. Available in number of file formats including MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB. Find professional %{link_3} for any 3D design projects like virtual reality (VR), augmented reality (AR), games, 3D %{link_4} visualization or animation.","free_no_tag":"%{model_count} free 3D %{tag} models ready for 3D printing. Download %{tag} free 3D print models, file formats available including STL, OBJ, 3DM, 3DS, MAX. 3D models are ready to be used with FDM, SLA, SLS and other types of 3D printers.","free_printable":"%{model_count} free 3D models ready for 3D printing. Download free or look for premium 3D print models. File formats available including STL, OBJ, WRL. 3D models are ready to be used with FDM, SLA, SLS and other types of 3D printers.","free_tag":"Free 3D %{tag} models available for download. Available in many file formats including MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB. Find professional %{tag_link} for any 3D design projects like virtual reality (VR), augmented reality (AR), games, 3D visualization or animation.","furniture":"Ready to explore our showroom of 3D furniture where comfort meets versatility of models? At CGTrader you will find them in all shapes and forms in the file format of your choice. Expand your collection with couches, chairs and tables for your visualization projects or add a touch of realism with PBR low poly models to your game's interior or exterior scenes.","general":"This view-all page contains 3D models from every category on CGTrader. You can refine the product selection to show Animated, Rigged, PBR or Low Poly models in the most popular file formats using available filters. Sort 3d assets by price, popularity or newness to find exactly what you are looking for.","high_poly":"Buy or download Animated Professional 3D models ready to be used in CG projects, film and video production, animation, visualizations, and others. Assets are available for download in many industry-accepted formats including MAX,  OBJ, FBX, 3DS, UE4 and other.  If you’re in search of high poly quality 3D assets, we have a leading digital art library for all your needs.\n","household":"Make your projects feel like home with our collection of 3D household models. Familiar appliances, 
16518kitchen utensils, cleaning tools and elements of decor are here to bring warmth and lifelike realism into your scenes with digital objects. Perfect for interior design renders, video games, lifestyle scenes or any digital environment, these useful assets can transform your next project into a true masterpiece, filled with the comfortable charm of home.","industrial":"Whether it's static structures or animated assets you are after, our library of industrial 3D models will add precision and detail to any scene. Ideal for constructing lively, interactive environments, illustrating intricate processes or just filling a project with raw metal goodness, these industriall-themed models are ready to rev up your creations.","interior":"With an extended selection of 3D models for interiors you have the power to shape your indoor spaces and make your interior designs pop with high quality 3D assets. Create lifelike interior visualization renders with high-poly assets for archviz or chose the low-poly highly optimized ones for your game's closed spaces. Ease into the design process worry free - whether you are a game dev, an architect or an animator, our platform's got you covered.","low_poly":"%{count} low poly 3D %{tag} models are available for download. These models contain a significantly smaller number of polygons and therefore require less computing power to render. Models which have fewer polygons are best used in real time applications that require fast processing, like virtual reality (VR), augmented reality (AR), mixed reality (MR), cross reality (XR) and games, especially mobile games. Choose from our collection of rigged and animated models to easily use them in your real-time applications. Get 3D assets for environments, pick props, objects or buy complete 3D model collections, bundles and packs with everything your game might need. Save development time and costs, make prototype experiences or use 3D models as placeholders in your project. To find models that render predictably in various engines, use the PBR filter next to the search bar.\n","military":"Arm yourself with versatile 3D military models and get to the frontline of epic medieval battles and futuristic sci-fi wars. Ideal for games, simulations or cinematic projects that are in need of some explosive action. Gear up to transform your virtual battlefield with modern weapons or historical armour. Pre-optimized 3D assets are ready to be imported in game engines and are rigged for dynamic scenes. Available in any file format for maximum compatibility.","not_custom":"%{found_items_count} 3D %{tag} models available for download. 3D %{tag} models are ready for animation, games and VR / AR projects. Use filters to find rigged, animated, low-poly or free 3D models. Available in any file format including FBX, OBJ, MAX, 3DS, C4D","optimized":"Models which have fewer polygons are best used in real time applications that require fast processing, like virtual reality (VR), augmented reality (AR), mixed reality (MR), cross reality (XR) and games, especially mobile games. Choose from our collection of rigged and animated models to easily use them in your real-time applications. Get 3D assets for environments, pick props, objects or buy complete 3D model collections, bundles and packs with everything your game might need. Save development time and costs, make prototype experiences or use 3D models as placeholders in your project. Physically based rendering, or PBR, denotes models which are rendered accurately and consistently in any lighting conditions due to the texturing workflows used in the creation process.\n","pbr":"Physically based rendering, or PBR, denotes models which are rendered accurately and consistently in any lighting conditions due to the texturing workflows used in the creation process. Reflectiveness, surface roughness and colouring, together with the principle of energy conservation and the advantages of the bidirectional reflectance distribution function (BRDF), provide the best standard for any 3D objects that can be rendered in any lighting conditions. A PBR 3D model will never reflect more light than hits its surface. PBR textures are available in all standard file formats. CGTrader also offers 3D model conversion to PBR for some models. Use PBR models across all major rendering engines, like Unity, Unreal engine, Marmoset Toolbag 3 and others, which support physically based rendering.\n","plant":"Invite nature into your projects with these 3D plants. Foliage, trees and beautiful flowers are easy to download. Perfect for enhancing outdoor scenes, architectural visual
16518s, or crafting enchanting landscapes, our catalog of 3D vegetation will breathe a breath of fresh air into your work. And with all major file formats available, you can effortlessly bring nature's charm into your next scene.","printable":"Browse all 3D printing models from various categories on CGTrader. Filter, sort and refine our extensive library of printable 3D objects by date, popularity and price to easily find the desired product for your professional needs as well as fun home projects for beginners.","printable_merged_tag":"Here you can find %{tag} 3D print files (STL, OBJ, FBX, and many others) ready for printing. Purchase and download free 3D models, stream and print with your 3D printer.","printable_tag":"Here you can find %{tag} 3D print files (STL, OBJ, FBX, and many others) ready for printing. Purchase and download 3D models, stream and print with your 3D printer.","rigged":"Browse rigged %{tag} 3D models ready for animation, ideal for game development and production pipelines.\n","scanned":"%{count} scanned 3D models are available for purchase or free download. 3D Scanned Models are ready for animation, games and VR / AR projects. You can find 3D models of objects found in nature such as rocks and natural surfaces, humans and other real life objects here...and it cannot get any more realistic than this. Use filters to find rigged, animated, low-poly or free 3D models. Available in any file format including FBX, OBJ, MAX, 3DS, C4D.","science":"%{count} science 3D models available for download in this category range from medicine, biology, physics, chemistry fields and other real world objects related to science and technology: laboratory equipment, microscopic bodies and subatomic world. Look no further, %{cell_link}, %{skull_link} and %{skeleton_link} 3D models can be found here! To buy or download a free 3D assets, browse the categories above. You can use rigged models for animation or low-poly ones for games and VR / AR projects. Both free and premium 3D models can be found in a number of file formats including FBX, OBJ, MAX, 3DS, C4D.","scripts_plugins":"%{count} 3D Scripts / Plugins are available for download. 3D Scripts / Plugins can be added to various software and used to make your work pipeline more efficiently. Filter by formats to find tools fitting your prefered software.\n","space":"Journey to the stars with myriads of space themed 3D models. Spaceships, celestial bodies and shuttles will take your future projects far and beyond, no matter your software of choice. Our space arsenal features a broad spectrum of 3D assets, including rigged and low-poly ones, ideal for efficient rendering in game development or real-time applications like VR and AR experiences. Bring your ideas light years ahead now and explore the cosmos.","sports":"%{count} sports 3D models available nest together equipment that is used for recreational activities, entertainment, exercise, leisure, pleasurable pastime, fun as well as games, children toys and full range of musical instruments. To buy or download a free 3D model in any file format needed, including FBX, OBJ, MAX, 3DS, C4D, browse the categories above. You can use models for animation, games, VR/AR projects - plenty of them are rigged, low-poly or animated already.","test":{"airplane":"Aviation enthusiasts, listen up, it has never been so easy to get the perfect 3D printed airplane model for your board game or model collection. Search through %{model_count} 3D printable airplane models and find a model you just need to have on that shelf. Discover real airplane models or made-up ones. Search for newest designs or something that counts back from the time of the Wright brothers. It‘s also a perfect gift for any child who dreams to be a pilot when he grows up. Purchase and download a 3D print model, print it by yourself or submit the model to one of our 3D printing service providers to have it printed and shipped to you in no time!\n","anatomy":"%{model_count} 3D Anatomy models available for download on our marketplace. Explore the human anatomy with high - poly bone, muscle, organ models. Perfect for any educational or scientific project. Use printable 3D models for improved education experience. Create stunning animations and video games with our\n vast library of rigged models. Use our advanced filtering system to easily find low poly, animated, PBR,\n rigged models or anything that suits your needs best. Choose from a great deal of file formats including the\n most popular ones like FBX, OBJ, MAX, 3DS, C4D.\n","castle":"Your own astonishing 3D printed castle is a click away! Search various 3D castle models ready for 3D printing. Create a unique fantasy fortress for your D\u0026D session or any other medieval rpg tabletop game. These medieval fortresses are an amazing gift for any board game enthusiast or a great addition to your own collection. Find the perfect 3D model for painting hobbyists or a one of a kind toy for children. Purchase and download your chosen 3D castle design, print it with your own printer, or submit the model to one of our 3D printing service providers to have it printed and shipped to you.\n","dice":"Luck won’t run out with 3D printed dice! If you want authentic gaming dice for your board game night, look no more. Here you can choose between %{model_count} free 3D dice models ready for 3D printing. When the game is over, use these beautiful models as an accessory. Download free 3D printable dice models, file formats available including STL, OBJ, 3DM, 3DS, MAX. Our 3D models are ready to be used with FD
16518M, SLA, SLS and other types of 3D printers. Make your game a little more interesting and get your 3D printed dice!\n","drone":"3D print and build your own drone or find parts for replacement. 3D printed drones are easy to assemble and repair after a crash landing. Discover a model with the perfect camera mounting position or request for minimal alterations. Perfect for fun leisure activities, aerial photography, videography or mapping. Find and purchase a uniquely designed 3D drone model that fits all your needs. Print it by yourself or submit the model to one of our 3D printing service providers to have it printed and shipped to you in no time! Get your own 3D printed drone and roam the wide blue skies!\n","pokemon":"Pikachu, I choose you! Choose from loads of pocket monsters ready for 3D printing. Great to expand your 3D printed pokemon collection. Search and stumble upon the best, most detailed pokemon designs for your next tabletop game with friends. Purchase and download your 3D pokemon, print it with your own 3D printer, or submit the model to one of our 3D printing service providers to have it printed and shipped to you. After all, this is one of the coolest things to 3D print!\n","ring":"3D printed jewelry is the new black in the industry, as are 3D printed rings! Search an enormous amount of %{model_count} free 3D printable ring models. Browse through unique designs and find something made just for you. Can’t find anything for yourself at the store? Or maybe you want an accessory exactly like the one your favorite movie character has? Choose from a great deal of file formats including STL, OBJ, 3DM, 3DS, MAX. 3D models are ready to be used with FDM, SLA, SLS and other types of 3D printers, so browse through our selection and try on your new favorite ring.\n","shoes":"Big brands are onboard with 3D printed shoes and now you can share the experience as well. Browse and find free printable shoe designs – from sneakers to fashionable heels there are a lot of ready-to-print models just waiting for you to try them on. Make the outcome perfect by selecting materials, adjusting softness and size. Choose from 146 free 3D shoe models and download them in available file formats including STL, OBJ, 3DM, 3DS, MAX. Designs are ready to be used with FDM, SLA, SLS and other types of 3D printers. Get your 3D printed shoes today!\n","textures":"Elevate your scenes with high-quality 3D textures from our extensive catalog. Discover a vast selection of detailed 3D materials for creating various surfaces that range from wood, metal and glass to things like grass and dirt. Boost your projects with realistic PBR textures and ensure easy workflow integration with top-notch quality assets that are available in all popular file formats. "},"ukraine_items":"3D models created by Ukrainian designers and designers who are donating their earnings to Ukraine","various":"%{count} various 3D models that don’t easily find their place in other categories are available for download. %{rocks_link}, %{clouds_link}, %{logos_link}, %{cigarette_link} and other non-categorized 3D models that you might need to complete your game, animation or VR/AR project are here. Use filters to find rigged, animated, low-poly or free 3D models, or sort out by  available formats such as FBX, OBJ, MAX, 3DS, C4D.","vehicle":"Many projects need 3D vehicles for action scenes and our catalog full of models for every kind of transportation. Rigged for games and animations or high poly ones for breathtaking renders, all are designed to make your scenes more dynamic. All types of sport and leisure as well as commercial and military vehicles are waiting to be driven to exciting destinations without file format constraints.","watercraft":"Ready to explore the deep sea or to set sail for a relaxing cruise across the world? We harbour 3D watercraft models for any kind of adventure. From roaring speedboats to majestic digital naval vessels, our collection anchors your projects in an ocean of possibilities, bringing the authentic sailor's life to your games and animations."},"tag":{"meta":{"3d_merged_premium":"Download %{count} free and premium %{tag} 3D models, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","3d_merged_premium_print":"Download %{count} %{tag} free \u0026 premium 3D print files ready for FDM, SLA, SLS and, other 3D printers, available formats STL, OBJ, 3DM, and more.","3d_models":"%{count} %{tag} 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Download %{count} %{tag} 3D print files ready for FDM, SLA, SLS, and other 3D printers, available formats STL, OBJ, 3DM, and more.%{page}","animated_3d_models":"%{count} %{tag} animated 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}","description":{"3d_merged_premium":{"vr":"Download %{model_count} free and premium VR 3D models, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","vr-character":"Download %{model_count} free and premium VR Character 3D models, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","vrchat":"Download %{model_count} free and premium VRChat 3D models, available in MAX, OBJ, FBX, 3DS, and C4D file formats. Avatars, skins and more."},"3d_models":{"vr-hands":"Download %{model_count} free and premium VR Hand 3D models, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","vr-rooms":"Download %{model_count} free and premium VR Room 3D models, available in MAX, OBJ, FBX, 3DS, and C4D file formats, ready for VR / AR, animation, games, and other 3D projects.","vrchat-alien":"Download %{model_count} free and premium VRChat Alien avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-angel":"Download %{model_count} free and premium VRChat Angel avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-anime":"Download %{model_count} free and premium VRChat Anime avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-assets":"Download %{model_count} free and premium VRChat assets available in MAX, OBJ, FBX, 3DS, and C4D file formats.","vrchat-avatar":"Download %{model_count} free and premium VRChat Avatars 3D models, available in MAX, OBJ, FBX, 3DS, and C4D file formats. Avatars, skins and more.","vrchat-base":"Download %{model_count} free and premium VRChat Base avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-bunny":"Download %{model_count} free and premium VRChat Bunny avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-cat":"Download %{model_count} free and premium VRChat Cat avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-chibi":"Download %{model_count} free and premium VRChat Chibi avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-female":"Download %{model_count} free and premium VRChat Female avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-femboy":"Download %{model_count} free and premium VRChat Femboy avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-furry":"Download %{model_count} free and premium VRChat Furry avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-girl":"Download %{model_count} free and premium VRChat Girl avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-male":"Download %{model_count} free and premium VRChat Male avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more.","vrchat-shark":"Download %{model_count} free and premium VRChat Shark avatars available in MAX, OBJ, FBX, 3DS, and C4D file formats. Characters, skins and more."}},"free_3d_models":"Download %{count} %{tag} free 3D models, available in MAX, OBJ, FBX, 3DS, C4D file formats, ready for VR / AR, animation, games and other 3D projects.%{page}","free_3d_print_models":"Download %{count} %{tag} free 3D print files ready for FDM, SLA, SLS and other 3D printers, available formats STL, OBJ, 3DM and more.%{page}","lowpoly_3d_models":"Download %{count} VR / AR / low-poly %{tag} 3D models ready for Virtual Reality, games and other real-time apps, available in FBX, OBJ, MAX, 3DS, C4D file formats.%{page}","rigged_3d_models":"%{count} %{tag} rigged 3D models available for download in any file format, including FBX, OBJ, MAX, 3DS, C4D.%{page}","title":{"3d_models":{"vr":"VR Models - Buy 3D models for VR\u0026AR | CGTrader","vr-character":"VR Character models | CGTrader","vr-hands":"VR Hand models | CGTrader","vr-rooms":"VR Room models: designs \u0026 ideas | CGTrader","vrchat":"VRChat 3D Models, Skins \u0026 Assets for sale | CGTrader","vrchat-alien":"VRChat Alien avatars for sale | CGTrader","vrchat-angel":"VRChat Angel avatars for sale | CGTrader","vrchat-anime":"VRChat Anime avatars for sale | CGTrader","vrchat-assets":"VRChat assets: hair, clothing, and much more! | CGTrader","vrchat-avatar":"VRChat Avatars, Skins, Characters for sale | CGTrader","vrchat-base":"VRChat Base avatars for sale | CGTrader","vrchat-bunny":"VRChat Bunny avatars for sale | CGTrader","vrchat-cat":"VRChat Cat avatars for sale | CGTrader","vrchat-chibi":"VRChat Chibi avatars for sale | CGTrader","vrchat-female":"VRChat Female avatars for sale | CGTrader","vrchat-femboy":"VRChat Femboy avatars for sale | CGTrader","vrchat-furry":"VRChat Furry avatars for sale | CGTrader","vrchat-girl":"VRChat Girl avatars for sale | CGTrader","vrchat-male":"VRChat Male avatars for sale | CGTrader","vrchat-shark":"VRChat Shark avatars for sale | CGTrader"}}}},"title":{"download":"| Download","files":"3D Files","formats":"Free \u0026 Premium %{required_software_title} %{product_type_title} (%{required_extension_title})","free":"Free 3D models","free_items_section":{"cg_general":"Free %{selected_filter}3D models of the day","cg_tag":"Free %{selected_filter}%{tag} 3D models of the day","print_general":"Free %{selected_filter}3D print models of the day","print_tag":"Free %{selected_filter}%{tag} 3D print models of the day"},"free_printing":"Free files for 3D printing","free_tag":"Free %{selected_tag} 3D models","general":"%{selected_filter}3D Models","high_poly":"%{selected_filter}3D Models","low_poly":"%{selected_filter}3D Models","newest_cg":"New \u0026 Latest 3D Models","newest_print":"New \u0026 Latest 3D Print Models","page_prefix":"Page %{page_number} of ","pbr":"PBR 3D Models","printing":"3D Print %{selected_filter}Models","sales_cg":"Best-Selling 3D Models","sales_print":"Best-Selling 3D Print Models","tag_heading":{"cg":"Free %{tag} STL print files","filter":"%{selected_filter}%{tag} 3D Models","general":"%{tag}
16518 3D models","merged_premium_print_tag":"Free \u0026 Premium %{selected_tag} print files","merged_premium_tag":"Free \u0026 Premium %{selected_tag} 3D Models","printable":"%{tag} 3D print files and models","tag":{"3d_merged_premium":{"vr":"VR Models","vr-character":"VR Character Models","vrchat":"VRChat Models"},"3d_models":{"vr":"VR Models","vr-character":"VR Character Models","vr-hands":"VR Hand Models","vr-rooms":"VR Room Models","vrchat":"VRChat Models","vrchat-alien":"VRChat Alien avatars","vrchat-angel":"VRChat Angel avatars","vrchat-anime":"VRChat Anime avatars","vrchat-assets":"VRChat assets","vrchat-avatar":"Buy VRChat Avatars, Skins, and Characters","vrchat-base":"VRChat Base avatars","vrchat-bunny":"VRChat Bunny avatars","vrchat-cat":"VRChat Cat avatars","vrchat-chibi":"VRChat Chibi avatars","vrchat-female":"VRChat Female avatars","vrchat-femboy":"VRChat Femboy avatars","vrchat-furry":"VRChat Furry avatars","vrchat-girl":"VRChat Girl avatars","vrchat-male":"VRChat Male avatars","vrchat-shark":"VRChat Shark avatars"}}},"trending_cg":"Trending 3D Models","trending_print":"Trending 3D Print Models","ukraine_items":"Support Ukraine 🇺🇦"},"with_makes":"Has Makes"},"cart":{"request_invoice_failed":"Oops. Something went wrong.","request_invoice_successful":"Request Invoice successfully sent.","shareable_cart_created":"Cart successfully shared.","shareable_cart_failed":"Oops. Something went wrong.","subscription_covered_items_removed":"Removed from your cart: %{titles}. Already included in your subscription.","subscription_covered_order_refused":"Your subscription now includes %{titles}, so we did not charge you. Download from your subscription instead."},"categories":{"cg":{"header_title_html":"Buy Professional \u003cstrong\u003e3D models\u003c/strong\u003e","text":"Find the exact right 3D content for your needs, including AR/VR, gaming, advertising, entertainment and others"},"head-title":{"cg":"3D Models \u0026 Assets for Games, Films \u0026 VFX","cg-models":{"sort-by":{"newest":"New 3D Models","top-selling":"Best-Selling 3D Models","trending":"Trending 3D Models"}},"print":"3D Print Models \u0026 STL Files – Download \u0026 Buy","print-models":{"sort-by":{"newest":"New 3D Print Models","top-selling":"Best-Selling 3D Print Models","trending":"Top Trending 3D Printed Items"}},"product-type":{"plural":{"capital":{"cg":"3D Models","cg-merge-premium":"3D Models – Free \u0026 Premium Downloads","print":"3D Print Files And Models","print-free":"3D printer STL files","print-free-with-tag":"3D %{tag} STL print files and models","print-merge-premium":"%{tag} 3D print files - free \u0026 premium STL models","print-with-tag":"3D %{tag} STL Print Files And Models"},"lower":{"cg":"3D models","print":"3D STL print files and models"}},"singular":{"capital":{"cg":"3D Model","print":"3D Printing Model"},"lower":{"cg":"3D model","print":"3D printing model"}}}},"index":{"browse_all":"Browse all models","info-section__description_1_html":"Buy or free-download professional 3D models ready for CG projects, film and video production, animation, visualizations, games, VR/AR, and others. Assets are available for download in many \u003ca href='https://www.cgtrader.com/blog/3d-file-formats' target='_blank' rel='noopener noreferrer'\u003eindustry-accepted file formats\u003c/a\u003e, including MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB, and others.","info-section__description_1_printable_html":"Professional 3D files, prints, and models ready to be printed at home with desktop FDM or industrial SLS 3D printers, available in many industry formats including OBJ, \u003ca href=\"https://www.cgtrader.com/3d-print-models/stl-files\"\u003eSTL\u003c/a\u003e, WRL. If you are 3D printing hobbyist, maker, educator or business prototyping by 3D-printing, our home decorations, trendy gadget accessories, eye-catching fashion pieces, unique jewelry, mechanical parts, and variety of miniatures will surely give you an inspiration boost.","info-section__heading_1_html":"Explore 3D Model \u003cstrong\u003eCategories\u003c/strong\u003e","info-section__heading_1_printable_html":"Explore 3D Printing Model \u003cstrong\u003eCategories\u003c/strong\u003e","info-section__heading_2_html":"3D Models \u003cstrong\u003eMarketplace\u003c/strong\u003e","info-section__heading_2_printable_html":"What's \u003cstrong\u003emore\u003c/strong\u003e?"},"marketplace":{"3d-printing":{"link_text":"Browse 3D Print Models","text":"Find 3D print models for your 3D printer.","title":"3D Printing Models"},"blender_text":"Discover thousands of Blender 3D Models available in blend format.","browse":"Browse","cinema-4d_text":"Uncover thousands of high quality Cinema 4D Models available in c4d file format.","discounted":{"link_text":"Browse Discounted 3D Models","text":"Buy professional 3D models at a discount.","title":"Discounted 3D Models"},"low-poly":{"link_text":"Browse Low-poly 3D Models","text":"Low-poly 3D models ready for video games, VR \u0026 AR real-time applications.","title":"Low-poly 3D Models"}
16518,"pbr":{"link_text":"Browse PBR 3D Models","text":"Physically Based Rendering 3D Models.","title":"PBR 3D Model"},"scripts_apps":{"link_text":"Browse all","text":"3D scripts and plugins to make your 3D modeling pipeline more efficient.","title":"Scripts \u0026 Apps"},"sketchup_text":"Browse the vast selection of Sketchup 3D Models for your architectural, interior and video game designs.","textures":{"link_text":"Browse textures","text":"Ready-to-use textures to save you some time preparing your 3D models.","title":"Textures"},"zbrush_text":"Find ZTL file format 3D assets for your digital sculpting, 3D modeling, texturing and painting projects."},"meta-description":{"cg":"Browse 2M+ professional 3D models and assets for games, VFX, architecture and animation. Characters, vehicles, environments, and more — download in FBX, OBJ, MAX and other formats.","print":"Browse 800,000+ 3D print STL files for FDM and resin printing. Figurines, miniatures, cosplay props, jewelry, and more. Instant download. Multiple formats."},"more":{"designers":{"link_text":"Browse 3D Designers List","text":"Browse thousands of 3D designer portfolios and find exactly what you need for your application.","title":"Find Best 3D Designers"},"projects":{"link_text":"Learn more about 3D Projects","text":"Hire quality freelance 3D artists and solve all your custom modeling challenges.","title":"CGTrader 3D Projects"},"visualization":{"link_text":"Learn more","text":"Turn your products into photorealistic 3D models for e-Commerce and other business applications.","title":"3D Visualization for e-Commerce"}},"navbar":{"aircraft":"Aircraft","animal":"Animals","animals":"Animals","architectural":"Architectural","art":"Art","car":"Car","character":"Character","exterior":"Exterior","fashion":"Fashion","food":"Food","furniture":"Furniture","gadgets":"Gadgets","games-toys":"Games \u0026 Toys","hobby-diy":"Hobby \u0026 DIY","house":"House","household":"Household","industrial":"Industrial","interior":"Interior","jewelry":"Jewelry","military":"Military","miniatures":"Miniatures","plant":"Plant","science":"Science","space":"Space","vehicle":"Vehicle","watercraft":"Watercraft"},"print_models":{"subtitle":"Find the exact right 3D Printing content for your needs","title_html":"Buy Professional \u003cstrong\u003e3D models for printing\u003c/strong\u003e"},"profile":{"collection":"3D model packs","free":"Free 3D models","high_poly":"High-Poly 3D models","optimized":"Low-Poly/PBR 3D models","printable":"3D Print models"},"seo":{"cg":{"aircraft":"This category covers 3D aircraft. CG {\"slug\": \"airplane\", \"title\": \"airplanes\"} will fit into simulations, visualizations, advertisements and videos. {\"slug\": \"drone\", \"title\": \"Drone\"} bodies and parts will delight fans of tiny flying vehicles. And the {\"slug\": \"aircraft\", \"title\": \"rigged models\", \"active_filter\": \"rigged\"} are ready to be imported into game engines.\n","animals":"The collection of 3D fauna - {\"slug\": \"cat\", \"title\": \"cats\"}, {\"slug\": \"horse\", \"title\": \"horses\"}, {\"slug\": \"bear\", \"title\": \"bears\"}, {\"slug\": \"rhino\", \"title\": \"rhinos\"}, {\"slug\": \"eagle\", \"title\": \"eagles\"}, {\"slug\": \"lion\", \"title\": \"lions\"} and more - can be found here. The CG 3D animals will satisfy the needs of advertisements or static scenes. We also have {\"slug\": \"animal\", \"title\": \"rigged animal\", \"active_filter\": \"rigged\"} models for games.\n","architectural":"{\"slug\": \"stairs\", \"title\": \"Stair\"}, {\"slug\": \"fireplace\"}, 3D models as well as decorations like {\"slug\": \"frame\"}, {\"slug\": \"pillow\"}, or {\"slug\": \"lamp\"} can be found in the extensive architectural detail model library. Interior and exterior can be used to build both digital kitchens, bedrooms, castle rooms or dungeons whether you are an architect, engineer or indie game developer.\n","car":"Car category contains automobile 3D models, \u003ca href=\"%{link_to_low_poly_items_url}\"\u003elow-poly\u003c/a\u003e versions of which will be attractive to game designers, others - to film makers or advertisers. From classic cars to racing and track cars to the concept ones, there’s a great variety of 3D auto models.\n","character":"In this category you will find 3D models of {\"slug\": \"human\"}, people, sci-fi, clothing and other. From {\"slug\": \"heart\"} and {\"slug\": \"hand\"} to detailed realistic {\"slug\": \"male\"} or {\"slug\": \"female\"} models. A number of the models are rigged and could be easily added to your game or animation.\n","electronics":"Electronics is a category for architects, interior decorators, level designers, scene makers and other creatives. 3D models of {\"slug\": \"smartphone\", \"title\": \"smartphones\"}, {\"slug\": \"laptop\", \"title\": \"laptops\"}, {\"slug\": \"camera\", \"title\": \"cameras\"}, {\"slug\": \"tv\", \"title\": \"TVs\"} and more will help to accurately capture modern life’s obsession with devices and technology.\n","engineering-parts":"Windows, pipelines, sliding door mechanisms and other bits that are necessary for CAD projects and even DIY plans fall under this category. This is where you can find 3D models from global manufacturers like ANVER, Dynatec and FCi.\n","exterior":"Browse architectural exteriors from facades, urban buildings to landscape objects, residential houses, industrial 
16518constructions, and full scale 3D {\"slug\": \"city\"} models. Some of the assets are low poly and could help you complete your game or VR/AR application.\n","food":"Food helps to create realistic scenes of domestic life. 3D fruits, vegetables, beverages and meals are all here. Advertisements for restaurants, AR applications for healthier eating. Whether you were looking for 3D {\"slug\": \"pizza\"} or a wedding {\"slug\": \"cake\"} model, they’re here.\n","furniture":"3D tables, chairs, sofas, beds in a variety of textures (wood, metal, etc.) and styles ( {\"slug\": \"retro\"}, {\"slug\": \"modern\"}, etc.) are on-hand. This category is meant for anyone involved with architecture and interior design, as well as advertisers.\n","furniture-set":"If you need alike looking antique chair and sofa looking, or full set of kitchenware, this is a place to be as this category collect sets of 3D furniture models from kitchen and living room to office and dining hall furniture sets. This helps the interior or level design specialist build rooms with matching elements.\n","household":"Household item 3D models are used to give details to rooms. You can put a 3D dinner set on the table, or have pepper and salt containers to complete kitchen interior, maybe put some {\"slug\": \"light\", \"title\": \"lights\"} and {\"slug\": \"flowers\"} near the bed to create luxurious feel.\n","industrial":"Construction materials, {\"slug\": \"engineering\"} tools, machinery and {\"slug\": \"container\", \"title\": \"containers\"} that provide both environmental details and interactive, rigged elements. This is perfect for simulation game developers or people building {\"slug\": \"construction\"} yard level.\n","interior":"This category contains realistic  3D elements and details of architecture, construction and engineering interior design. You can use these 3D models for animation or games - some of them are rigged and low poly.\n","military":"In this category you can buy 3D models of weapons, arms, {\"title\": \"swords\", \"slug\": \"sword\"} and artillery both modern and historical used by military forces and self defence. You can use models for games - some of them are polygons optimized and ready for game engines.\n","plant":"This category contains 3D models of flora organisms such as flowers, {\"slug\": \"tree\", \"title\": \"trees\"}, grass, creepers, greenery, herbs, perennials, seedlings, shoots, shrubs, slips, sprouts, vines, weed. To buy or download a free 3D model, browse the categories below.\n","scanned":"Modelers create these models by using 3D scanning technologies like RealSense. Tree stumps, statues, even males and females are popular here. The product is ready to be used in scenes and animations, offering unparalleled levels of realism.\n","science":"This category contains 3D models related to medicine, biology, physics, chemistry and technology, such as laboratory equipment, microscopic bodies and subatomic world. Look no further, {\"slug\": \"cell\"}, {\"slug\": \"skull\"} and {\"slug\": \"skeleton\"} 3D models can be found here.\n","scripts-plugins":"Scripts and plug-ins provide you with software shortcuts. Modeling, rendering, texturing - all of these can be made easier using plug-ins that come for all sorts of software like Maya and V-Ray.\n","space":"Space is a category full of low poly, game-ready models of sci-fi {\"slug\": \"spaceship\", \"title\": \"spaceships\"}, {\"slug\": \"asteroid\", \"title\": \"asteroids\"}, planets (including {\"slug\": \"earth\", \"title\": \"Earth\"} and realistic space {\"slug\": \"shuttle\", \"title\": \"shuttles\"} for the more scientifically inclined. 3D space models are useful for VR space simulator games.\n","sport-hobby":"This category encompases anything that is related to {\"slug\": \"field\"} events, sports and hobbies in general. From {\"slug\": \"football\"} and ski-slopes to {\"slug\": \"guitar\"} and {\"slug\": \"piano\"}, these are useful 3D models for advertisements, scenes and animation. Low-poly versions are there for sports video games.\n","textures":"Browse through a library of beautifully-made 3D materials and find the best suited, ready-to-use textures to prepare \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e game-ready \u003c/a\u003e or photorealistic models, upgrade your asset library, and improve your 3D works.\n","various":"These are the 3D models that didn’t easily fit into other categories, so we created one that would accept them all. {\"title\": \"Rocks\", \"slug\": \"rocks\"}, {\"slug\": \"clouds\"}, {\"title\": \"logos\", \"slug\": \"logo\"}, {\"title\": \"umbrellas\", \"slug\": \"umbrella\"} and other non-categorized 3D models are here.\n","vehicle":"Trucks, {\"title\": \"ambulances\", \"slug\": \"ambulance\"}, buses, trains, tractors, {\"title\": \"bikes\", \"slug\": \"bike\"} and motorcycles - this category is for the people who want their city streets to be filled with realistic and sci-fi vehicles, and for those making simulation games or formating posters.\n","watercraft":"Watercraft covers {\"title\": \"boats\", \"slug\": \"boat\"}, {\"title\": \"yachts\", \"slug\": \"yacht\"}, {\"title\": \"ships\", \"slug\": \"ship\"} and other water vehicles. The rigged 3D models will do well in video games, animation while CG models are there for your scenes or other applications where visual fidelity is more important than polycount.\n"},"forwarded":{"cg":{"aircraft":"This category covers 3D aircraft. CG {\"slug\": \"airplane\", \"title\": \"airplanes\"} will fit into simulations, visualizations, advertisements and videos. {\"slug\": \"drone\", \"title\": \"Drone\"} bodies and parts will delight fans of tiny flying vehicles. And the {\"slug\": \"aircraft\", \"title\": \"rigged models\", \"active_filter\": \"rigged\"} are ready to be imported into game engines.\n","animals":"The collection of 3D fauna - {\"slug\": \"cat\", \"title\": \"cats\"}, {\"slug\": \"horse\", \"title\": \"horses\"}, {\"slug\": \"bear\", \"title\": \"bears\"}, {\"slug\": \"rhino\", \"title\": \"rhinos\"}, {\"slug\": \"eagle\", \"title\": \"eagles\"}, {\"slug\": \"lion\", \"title\": \"lions\"} and more - can be found here. The CG 3D animals will satisfy the needs of advertisements or static sce
16518nes. We also have {\"slug\": \"animal\", \"title\": \"rigged animal\", \"active_filter\": \"rigged\"} models for games.\n","architectural":"{\"slug\": \"stairs\", \"title\": \"Stair\"}, {\"slug\": \"fireplace\"}, 3D models as well as decorations like {\"slug\": \"frame\"}, {\"slug\": \"pillow\"}, or {\"slug\": \"lamp\"} can be found in the extensive architectural detail model library. Interior and exterior can be used to build both digital kitchens, bedrooms, castle rooms or dungeons whether you are an architect, engineer or indie game developer.\n","car":"Car category contains automobile 3D models, \u003ca href=\"%{link_to_low_poly_items_url}\"\u003elow-poly\u003c/a\u003e versions of which will be attractive to game designers, others - to film makers or advertisers. From classic cars to racing and track cars to the concept ones, there’s a great variety of 3D auto models.\n","character":"In this category you will find 3D models of {\"slug\": \"human\"}, people, sci-fi, clothing and other. From {\"slug\": \"heart\"} and {\"slug\": \"hand\"} to detailed realistic {\"slug\": \"male\"} or {\"slug\": \"female\"} models. A number of the models are rigged and could be easily added to your game or animation.\n","electronics":"Electronics is a category for architects, interior decorators, level designers, scene makers and other creatives. 3D models of {\"slug\": \"smartphone\", \"title\": \"smartphones\"}, {\"slug\": \"laptop\", \"title\": \"laptops\"}, {\"slug\": \"camera\", \"title\": \"cameras\"}, {\"slug\": \"tv\", \"title\": \"TVs\"} and more will help to accurately capture modern life’s obsession with devices and technology.\n","engineering-parts":"Windows, pipelines, sliding door mechanisms and other bits that are necessary for CAD projects and even DIY plans fall under this category. This is where you can find 3D models from global manufacturers like ANVER, Dynatec and FCi.\n","exterior":"Browse architectural exteriors from facades, urban buildings to landscape objects, residential houses, industrial constructions, and full scale 3D {\"slug\": \"city\"} models. Some of the assets are low poly and could help you complete your game or VR/AR application.\n","food":"Food helps to create realistic scenes of domestic life. 3D fruits, vegetables, beverages and meals are all here. Advertisements for restaurants, AR applications for healthier eating. Whether you were looking for 3D {\"slug\": \"pizza\"} or a wedding {\"slug\": \"cake\"} model, they’re here.\n","furniture":"3D tables, chairs, sofas, beds in a variety of textures (wood, metal, etc.) and styles ( {\"slug\": \"retro\"}, {\"slug\": \"modern\"}, etc.) are on-hand. This category is meant for anyone involved with architecture and interior design, as well as advertisers.\n","furniture-set":"If you need alike looking antique chair and sofa looking, or full set of kitchenware, this is a place to be as this category collect sets of 3D furniture models from kitchen and living room to office and dining hall furniture sets. This helps the interior or level design specialist build rooms with matching elements.\n","household":"Household item 3D models are used to give details to rooms. You can put a 3D dinner set on the table, or have pepper and salt containers to complete kitchen interior, maybe put some {\"slug\": \"light\", \"title\": \"lights\"} and {\"slug\": \"flowers\"} near the bed to create luxurious feel.\n","industrial":"Construction materials, {\"slug\": \"engineering\"} tools, machinery and {\"slug\": \"container\", \"title\": \"containers\"} that provide both environmental details and interactive, rigged elements. This is perfect for simulation game developers or people building {\"slug\": \"construction\"} yard level.\n","interior":"This category contains realistic  3D elements and details of architecture, construction and engineering interior design. You can use these 3D models for animation or games - some of them are rigged and low poly.\n","military":"In this category you can buy 3D models of weapons, arms, {\"title\": \"swords\", \"slug\": \"sword\"} and artillery both modern and historical used by military forces and self defence. You can use models for games - some of them are polygons optimized and ready for game engines.\n","plant":"This category contains 3D models of flora organisms such as flowers, {\"slug\": \"tree\", \"title\": \"trees\"}, grass, creepers, greenery, herbs, perennials, seedlings, shoots, shrubs, slips, sprouts, vines, weed. To buy or download a free 3D model, browse the categories below.\n","scanned":"Modelers create these models by using 3D scanning technologies like RealSense. Tree stumps, statues, even males and females are popular here. The product is ready to be used in scenes and animations, offering unparalleled levels of realism.\n","scie
16518nce":"This category contains 3D models related to medicine, biology, physics, chemistry and technology, such as laboratory equipment, microscopic bodies and subatomic world. Look no further, {\"slug\": \"cell\"}, {\"slug\": \"skull\"} and {\"slug\": \"skeleton\"} 3D models can be found here.\n","scripts-plugins":"Scripts and plug-ins provide you with software shortcuts. Modeling, rendering, texturing - all of these can be made easier using plug-ins that come for all sorts of software like Maya and V-Ray.\n","space":"Space is a category full of low poly, game-ready models of sci-fi {\"slug\": \"spaceship\", \"title\": \"spaceships\"}, {\"slug\": \"asteroid\", \"title\": \"asteroids\"}, planets (including {\"slug\": \"earth\", \"title\": \"Earth\"} and realistic space {\"slug\": \"shuttle\", \"title\": \"shuttles\"} for the more scientifically inclined. 3D space models are useful for VR space simulator games.\n","sport-hobby":"This category encompases anything that is related to {\"slug\": \"field\"} events, sports and hobbies in general. From {\"slug\": \"football\"} and ski-slopes to {\"slug\": \"guitar\"} and {\"slug\": \"piano\"}, these are useful 3D models for advertisements, scenes and animation. Low-poly versions are there for sports video games.\n","textures":"Browse through a library of beautifully-made 3D materials and find the best suited, ready-to-use textures to prepare \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e game-ready \u003c/a\u003e or photorealistic models, upgrade your asset library, and improve your 3D works.\n","various":"These are the 3D models that didn’t easily fit into other categories, so we created one that would accept them all. {\"title\": \"Rocks\", \"slug\": \"rocks\"}, {\"slug\": \"clouds\"}, {\"title\": \"logos\", \"slug\": \"logo\"}, {\"title\": \"umbrellas\", \"slug\": \"umbrella\"} and other non-categorized 3D models are here.\n","vehicle":"Trucks, {\"title\": \"ambulances\", \"slug\": \"ambulance\"}, buses, trains, tractors, {\"title\": \"bikes\", \"slug\": \"bike\"} and motorcycles - this category is for the people who want their city streets to be filled with realistic and sci-fi vehicles, and for those making simulation games or formating posters.\n","watercraft":"Watercraft covers {\"title\": \"boats\", \"slug\": \"boat\"}, {\"title\": \"yachts\", \"slug\": \"yacht\"}, {\"title\": \"ships\", \"slug\": \"ship\"} and other water vehicles. The rigged 3D models will do well in video games, animation while CG models are there for your scenes or other applications where visual fidelity is more important than polycount.\n"},"printable":{"art":"This is where the artistic 3D prints go. Looking for fancy sculpture design recreating classical works and statues of historic personalities, or inspiring figurines, maybe even 3D-printable logos? That is the category.\n","fashion":"Beautiful and stylish things that you can wear are found in this category. See if you can find any use for fancy space age {\"slug\": \"footwear\"}, novelty glasses and cosplay {\"slug\": \"accessories\"} found here.\n","gadgets":"All {\"title\": \"iPhone\", \"slug\": \"iphone\"}, {\"title\": \"iPad\", \"slug\": \"ipad\"} (and other smart devices) cases can be found in gadget category. From simple 3D add-ons to complex parts and “hacks” for your items, this is the category for the creatives, and lovers of technology.\n","games-toys":"Games and toys is the category covering printable 3D alien or monster miniatures, tiny {\"title\": \"tanks\", \"slug\": \"tank\"}, funky {\"slug\": \"chess\"} pieces, movie character helmets, hobby paraphernalia and accessories. This is the to-go category for all things geeky.\n","hobby-diy":"If you are our next great inventor and want to build your DIY project or get some spare parts for your {\"slug\": \"drone\"}, this is the place to be. Additionally, it features a lot things for people who want to make their own figurines or plastic gun replicas.\n","house":"All sorts of knick-knacks can be found here. {\"title\": \"Lamps\", \"slug\": \"lamp\"}, artistic {\"title\": \"bowls\", \"slug\": \"bowl\"}, and all kinds of {\"title\": \"holders\", \"slug\": \"holder\"} whether you cannot find a right spot for your phone, keep order of toothpicks, pile of pens or jewelry on your desk.\n","jewelry":"Jewelry is where you go to get bracelets and pendants or rings with diamonds. While 3D printers aren’t good enough to make diamonds - yet - you can still print out a metallic ring as a base for your IT geek proposal ring.\n","miniatures":"3D printable {\"title\": \"characters\", \"slug\": \"character\"}, famous people, {\"title\": \"landmarks\", \"slug\": \"landmark\"}, animals, {\"title\": \"Male\", \"slug\": \"male\"}, {\"slug\": \"female\"}, {\"title\": \"monsters\", \"slug\": \"monster\"}, {\"title\": \"aliens\", \"slug\": \"alien\"} - it’s all here. Whether you’re printing utility vehicles to put on your table or creating miniatures for your tabletop games, it’s your category.\n","scie
16518nce":"What can be classed as science? Well, models of skeleton {\"slug\": \"bones\"}, physicians {\"slug\": \"tools\"} and astrophysics-related 3D printable models that can be used in many study processes.\n"}},"printable":{"art":"This is where the artistic 3D prints go. Looking for fancy sculpture design recreating classical works and statues of historic personalities, or inspiring figurines, maybe even 3D-printable logos? That is the category.\n","fashion":"Beautiful and stylish things that you can wear are found in this category. See if you can find any use for fancy space age {\"slug\": \"footwear\"}, novelty glasses and cosplay {\"slug\": \"accessories\"} found here.\n","gadgets":"All {\"title\": \"iPhone\", \"slug\": \"iphone\"}, {\"title\": \"iPad\", \"slug\": \"ipad\"} (and other smart devices) cases can be found in gadget category. From simple 3D add-ons to complex parts and “hacks” for your items, this is the category for the creatives, and lovers of technology.\n","games-toys":"Games and toys is the category covering printable 3D alien or monster miniatures, tiny {\"title\": \"tanks\", \"slug\": \"tank\"}, funky {\"slug\": \"chess\"} pieces, movie character helmets, hobby paraphernalia and accessories. This is the to-go category for all things geeky.\n","hobby-diy":"If you are our next great inventor and want to build your DIY project or get some spare parts for your {\"slug\": \"drone\"}, this is the place to be. Additionally, it features a lot things for people who want to make their own figurines or plastic gun replicas.\n","house":"All sorts of knick-knacks can be found here. {\"title\": \"Lamps\", \"slug\": \"lamp\"}, artistic {\"title\": \"bowls\", \"slug\": \"bowl\"}, and all kinds of {\"title\": \"holders\", \"slug\": \"holder\"} whether you cannot find a right spot for your phone, keep order of toothpicks, pile of pens or jewelry on your desk.\n","jewelry":"Jewelry is where you go to get bracelets and pendants or rings with diamonds. While 3D printers aren’t good enough to make diamonds - yet - you can still print out a metallic ring as a base for your IT geek proposal ring.\n","miniatures":"3D printable {\"title\": \"characters\", \"slug\": \"character\"}, famous people, {\"title\": \"landmarks\", \"slug\": \"landmark\"}, animals, {\"title\": \"Male\", \"slug\": \"male\"}, {\"slug\": \"female\"}, {\"title\": \"monsters\", \"slug\": \"monster\"}, {\"title\": \"aliens\", \"slug\": \"alien\"} - it’s all here. Whether you’re printing utility vehicles to put on your table or creating miniatures for your tabletop games, it’s your category.\n","science":"What can be classed as science? Well, models of skeleton {\"slug\": \"bones\"}, physicians {\"slug\": \"tools\"} and astrophysics-related 3D printable models that can be used in many study processes.\n"},"titles":{"aircraft":"Aircraft 3D Models","animal":"Animals 3D Models","animals":"Animals 3D Models","architectural":"Architectural 3D Models","car":"Car 3D Models","character":"Character 3D Models","exterior":"Exterior 3D Models","food":"Food 3D Models","furniture":"Furniture 3D Models","household":"Household 3D Models","industrial":"Industrial 3D Models","interior":"Interior 3D Models","military":"Military 3D Models","plant":"Plant 3D Models","plants":"Plants 3D Models","space":"Space 3D Models","textures":"CG Textures 3D Models","vehicle":"Vehicle 3D Models","watercraft":"Watercraft 3D Models"}},"title":{"cg":" 3D Models","print":" 3D Print Files"}},"cgt_checks":{"verification_details":{"UV":{"text":"UVs"},"allowedCharacters":{"text":"Allowed characters","tooltip":"\u003cspan\u003e\n  Allowed ASCII characters: a-zA-Z0-9-_\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12485803270929#h_01GWP88M0S448FKN1SMABDP21R' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"archive_structure":{"text":"Archive structure"},"arithmeticJpegs":{"text":"JPEG texture format","tooltip":"Arithmetic coded JPEG texture files are not allowed"},"bounding_box_x":{"text":"Bounding box X","tooltip":"Bounding box X (to be updated)"},"bounding_box_y":{"text":"Bounding box Y","tooltip":"Bounding box Y (to be updated)"},"bounding_box_z":{"text":"Bounding box Z","tooltip":"Bounding box Z (to be updated)"},"centeredChildPivots":{"text":"Child object pivot location","tooltip":"Child object pivot points should be centered"},"centeredGeometryBoundingBox":{"text":"Model centered at the origin","tooltip":"The bounding box of the model should be centered at the origin"},"checkColorValuesUsedInOpacityMap":{"text":"Separate materials","tooltip":"Separate materials should be used for transparent and opaque objects"},"connectedTextures":{"text":"Textures connected to materials","tooltip":"All PBR textures should be connected to corresponding material slots"},"consistentBoundingBox":{"text":"Consistent bounding box","tooltip":"Model should have consistent bounding box across models in the same group"},"consistentMatIdOrder":{"text":"Consistent material ID order","tooltip":"Material order should be consistent across models from the same group"},"consistentNaming":{"text":"Consistent naming","tooltip":"Geometry and material names should have consistent suffixes or prefixes"},"consistentTransforms":{"text":"Consistent transforms","tooltip":"Model should have consistent transforms across models in the same group"},"consistentUvShells":{"text":"Consistent UV islands","tooltip":"Child model UVs should sufficiently match parent model UVs"},"dccObjectTypes":{"text":"No unsupported objects","tooltip":"\u003cspan\u003eUnsupported objects:\u003c/span\u003e\n\u003c/br\u003e- Lights\n\u003c/br\u003e- Cameras\n\u003c/br\u003e\u003ca href='https://help.cgtrader.com/hc/en-us/articles/12484392675473#h_01GWP8ESMVEC66RKEHAZ0PQNYS' target='_blank'\u003eLearn more\u003c/a\u003e\n"},"dccResetTransforms":{"text":"Reset transforms","tooltip":"All objects should have reset transforms"},"dccUnitScale":{"text":"File units","tooltip":"Model exported in required system units"},"details":{"text":"Details"},"embeddedTexturesFbx":{"text":"No embed textures","tooltip":"\u003cspan\u003e\n  Embedded textures are stored inside the model file, increasing its size and sometimes causing compatibility issues.\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12486535401361#h_01GWP83GFSK677SPVYTBX5NBQC' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"facetedGeometry":{"text":"No faceted geometry","tooltip":"\u003cspan\u003e\n  Faceted geometry uses flat surfaces without smoothing, which can look unrealistic on curves.\
16518n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12485385944849#h_01GWP8ABZ92CRXF1804RH0SN3S' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"fbxFormat":{"text":"Binary FBX","tooltip":"\u003cspan\u003e\n  Binary FBX file is more compact and faster to load and process.\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/14118757652881#h_01GWP8R5FAZFB7DW272VWPC70Y' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"fbxLayers":{"text":"No layers","tooltip":"Layers should not be used in FBX file"},"fbxUnits":{"text":"File units","tooltip":"Model exported in required system units"},"files":{"text":"Files"},"flippedNormals":{"text":"No flipped normals","tooltip":"\u003cspan\u003e\n  Surface normals of a 3D model consistently point outward, ensuring proper lighting,\n  shading, and rendering. Flipped normals can cause visual artifacts and printing issues.\n\u003c/span\u003e\n"},"flippedUv":{"text":"No flipped UVs","tooltip":"UV islands should not be flipped"},"geometry":{"text":"Geometry"},"glbSizeLimit":{"text":"GLB file size","tooltip":"GLB file size should not exceed allowed limit"},"gltfSizeLimit":{"text":"glTF file size","tooltip":"glTF file size should not exceed allowed limit"},"inconsistentTexResInMaterial":{"text":"Consistent texture resolution","tooltip":"Textures should have consistent resolution across all textures in the same texture set"},"inconsistentTexelDensity":{"text":"Consistent texel density","tooltip":"Texel density should be consistent across all UV islands"},"indexedColors":{"text":"No indexed color textures","tooltip":"Textures with  indexed color should not be used"},"materialNames":{"text":"Material naming","tooltip":"Material names should follow required naming convention"},"mesh":{"text":"Mesh"},"minimal_allowed_thickness":{"text":"Minimal allowed thickness","tooltip":"Model should not have too thin walls (to be updated)"},"missingRequiredTextures":{"text":"PBR textures","tooltip":"\u003cspan\u003e\n  PBR textures simulate how light interacts with materials, making the model look realistic under different lighting.\n  \u003c/br\u003eRequired PBR textures::\n  \u003c/br\u003e- Base Color\n  \u003c/br\u003e- Roughness\n  \u003c/br\u003e- Metalness\n  \u003c/br\u003e- Normal\n  \u003c/br\u003e\u003ca href='https://help.cgtrader.com/hc/en-us/articles/12486535401361#h_01GWP80KZMFE5299HB69Q4ZWQ2' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"multiMaterial":{"text":"No multi-materials","tooltip":"No multi-materials should be used in the model"},"multipleFileFormats":{"text":"Multiple texture file formats","tooltip":"Each texture should be saved in required multiple file formats"},"naming":{"text":"Naming"},"noBadContiguousEdges":{"text":"No bad contiguous edges","tooltip":"Model should not have faces with normals pointing in the wrong direction"},"noHardEdges":{"text":"No hard edges","tooltip":"Model should not contain any hard edges"},"noNgons":{"text":"No N-gons","tooltip":"\u003cspan\u003e\n  N-gons are polygons with five or more sides which might cause issues\n  in certain processes like rendering or animation. \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12485385944849#h_01GWP89PYY82N9HR4X42R98SKR' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"noSelfIntersections":{"text":"No self-intersections","tooltip":"Model should not contain self-intersections (to be updated)"},"noTransparentMeshes":{"text":"No transparent materials","tooltip":"Model should not have transparent materials"},"noUvTiling":{"text":"0-1 UV area","tooltip":"UV islands should be placed in 0-1 UV area"},"nonManifoldFaces":{"text":"Non-manifold faces","tooltip":"Model should not contain non-manifold faces (to be updated)"},"nonManifoldVertices":{"text":"Manifold geometry","tooltip":"\u003cspan\u003e\n  Manifold geometry means all surfaces are properly connected avoiding issues like edges shared by more than two faces.\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12485385944849#h_01GWP89YFDSNN5QRRW6W1A8KGB' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"numberOfMaterialsPerObject":{"text":"Assigned materials","tooltip":"\u003cspan\u003e\n  Materials are applied to the 3D model to allow visualize a model’s surface properties and appearance.\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12486535401361#h_01GWP85Y2XHM130GNRYSWF91QE' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"numberOfUVs":{"text":"UV unwrapped model","tooltip":"\u003cspan\u003e\n  A UV unwrapped model means its 3D surface is flattened into 2D space, allowing textures to be applied accurately.\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12485759132177#h_01GWP86PHGJY9H3PARWNABV34J' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"objectByMaterialTypeCount":{"text":"Amount of objects by material type","tooltip":"Model should have required material count for each material type"},"objectNames":{"text":"Object naming","tooltip":"Object names should follow required naming convention"},"objectTypes":{"text":"No unsupported objects","tooltip":"\u003cspan\u003eUnsupported objects:\u003c/span\u003e\n\u003c/br\u003e- Lights\n\u003c/br\u003e- Cameras\n\u003c/br\u003e\u003ca href='https://help.cgtrader.com/hc/en-us/articles/12484392675473#h_01GWP8ESMVEC66RKEHAZ0PQNYS' target='_blank'\u003eLearn more\u003c/a\u003e\n"},"openEdges":{"text":"No open edges","tooltip":"Model should be watertight"},"overlappedUvPerMaterial":{"text":"No overlapped UVs per material","tooltip":"UV islands should not overlap across the same material"},"overlappedUvPerModel":{"text":"No overlapped UVs per model","tooltip":"All UV islands should not overlap"},"overlappedUvPerObject":{"text":"No overlapped UVs per object","tooltip":"UV islands should not overlap across the same object"},"overlappedUvPerUVIsland":{"text":"No UV overlaps","tooltip":"\u003cspan\u003e\n  UVs overlap when multiple points on the 3D model’s surface are mapped to the same point on the UV island causing texture stretching.\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12485759132177#h_01GWP86YVAZRJNZ4PBARMTMWR7' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"overlappingFaces":{"text":"No overlapping faces","tooltip":"Model should not contain overlapping faces"},"potentialTextures":{"text":"Recognized textures","tooltip":"\u003cspan\u003e\n  PBR (Physically Based Rendering) materials are a type of shading and rendering\n  technique used in 3D computer graphics that simulates the way light interacts with\n  real-world materials. PBR materials use a set of p
16518hysical properties and measurements\n  to accurately represent the appearance of a material under different lighting conditions.\n  \u003c/br\u003eMandatory textures for PBR material:\n  \u003c/br\u003e- Base Color\n  \u003c/br\u003e- Roughness\n  \u003c/br\u003e- Metalness\n  \u003c/br\u003e- Normal\n  \u003c/br\u003e\u003ca href='https://help.cgtrader.com/hc/en-us/articles/12486535401361#h_01GWP80KZMFE5299HB69Q4ZWQ2' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"recommendedTexturesNaming":{"text":"Texture naming","tooltip":"Texture names should follow required naming convention"},"resetTransforms":{"text":"Reset transforms","tooltip":"All objects should have reset transforms"},"resolutionOrmMismatch":{"text":"Consistent AO, Roughness, Metalness texture resolution","tooltip":"Textures should have consistent resolution across AO, Roughness, Metalness in the same texture set"},"resolutionsColorOpacityMismatch":{"text":"Consistent Albedo, Opacity texture resolution","tooltip":"Textures should have consistent resolution across Albedo, Opacity in the same texture set"},"scene":{"text":"Scene"},"singleUVChannel":{"text":"One UV channel","tooltip":"Only one UV channel should be used"},"texelDensity":{"text":"Texel density","tooltip":"Texel density should be within required range"},"textureAlphaChannel":{"text":"No textures with alpha channel","tooltip":"Textures should have no alpha channel"},"textureAspectRatio":{"text":"Square textures","tooltip":"\u003cspan\u003e\n  Texture aspect ratio is the width-to-height ratio of a texture. Expected texture aspect ratio: 1:1\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12486535401361#h_01GWP82FF8TT77MNKGXGW6G8KS' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span\u003e\n"},"textureMaxResolution":{"text":"Maximum texture resolution","tooltip":"Textures should meet required maximum texture resolution"},"textureMinResolution":{"text":"Minimum texture resolution","tooltip":"Textures should meet required minimum texture resolution"},"textureResolutionNotPowerOf2":{"text":"Power of 2 texture sizes","tooltip":"\u003cspan\u003e\n  Textures with dimensions in power of two (e.g. 512x512px, 1024x1024px) are used to optimize performance and memory usage.\n  \u003ca href='https://help.cgtrader.com/hc/en-us/articles/12486535401361#h_01GWP82FF8TT77MNKGXGW6G8KS' target='_blank'\u003eLearn more\u003c/a\u003e\n\u003c/span?\n"},"texturelessMaterial":{"text":"Materials without textures","tooltip":"Model materials should not contain any textures"},"textures_and_materials":{"text":"Textures and Materials"},"too_thin_walls_count":{"text":"Too thin walls count","tooltip":"Model should not have too thin walls (to be updated)"},"topLevelZeroPosition":{"text":"Top-level object pivot location","tooltip":"Pivot point location should be at the origin (0, 0, 0) for top-level objects"},"triangleCountRelativeToParent":{"text":"Triangle count relative to parent","tooltip":"Child model should contain required triangle count relative to parent"},"triangleMaxCount":{"text":"Triangle max count","tooltip":"Model total triangle count should not exceeded required limit"},"trianglesRatio":{"text":"Triangle ratio","tooltip":"Model triangle ratio should not exceeded required limit"},"uniqueMeshNames":{"text":"Unique mesh names","tooltip":"All scene object names should be unique"},"unparseableMaterialName":{"text":"No unsupported material names","tooltip":"Material names should follow required naming convention"},"unsupportedBmpCompression":{"text":"No unsupported BMP compression","tooltip":"Unsupported BMP compression should not be used in BMP files"},"unsupportedPng":{"text":"PNG texture format","tooltip":"8-bit depth PNG textures are required"},"unusedTextures":{"text":"Supported texture file formats","tooltip":"Textures should be saved in supported file formats: 'png', 'jpg', 'exr', 'tga', 'bmp', 'tif'"},"usdzSizeLimit":{"text":"USDZ file size","tooltip":"USDZ file size should not exceed allowed limit"},"volume":{"text":"Volume","tooltip":"Volume (to be updated)"},"zeroAngleFaceCorners":{"text":"No zero-angled corners","tooltip":"Model should not contain faces with zero-angle corners"},"zeroFaceArea":{"text":"No zero-area faces","tooltip":"Model should not contain zero-area faces"},"zeroLengthEdge":{"text":"No zero-length edges","tooltip":"Model should not contain zero-length edges"}}},"checkout":{"adyen":{"declined_non_generic_error":"Payment unsuccessful due to a credit card issue. Try purchasing with a PayPal account instead."},"braintree":{"error_messages":{"Authentication Required":"Additional authentication is required to complete this transaction.","Call Issuer. Pick Up Card.":"Please contact your bank and do not use this card.","Cannot Authorize at this time (Life cycle)":"The transaction cannot be authorized currently due to lifecycle restrictions.","Cannot Authorize at this time (Policy)":"The transaction couldn't be authorized due to policy restrictions.","Card Issuer Declined CVV":"The CVV number provided is incorrect.","Card Not Activated":"The card being used is not activated.","Closed Card":"The card being used is closed.","Declined":"Your transaction was declined.","Declined - Call Issuer":"Your bank declined the transaction. Please contact them for further details.","Do Not Honor":"Your bank is not allowing this transaction.","Expired Card":"The card being used has expired.","Funding Instrument In The PayPal Account Was Declined By The Processor Or Bank, Or It Can't Be Used For This Payment":"The payment method linked to your PayPal account was declined.","Gateway Rejected: cvv":"The CVV number is incorrect or missing.","Gateway Rejected: duplicate":"This transaction appears to be a duplicate.","Gateway Rejected: fraud":"This transaction was blocked by our fraud detection measures.","Gateway Rejected: three_d_secure":"This transaction requires additional security verification through 3D Secure.","Insufficient Funds":"Your account doesn't have enough funds to complete this transaction.","Invalid Merchant ID":"The Merchant ID used for the transaction is invali
16518d.","Invalid Transaction":"The transaction is invalid.","Issuer or Cardholder has put a restriction on the card":"There is a restriction on your card.","Limit Exceeded":"This transaction exceeds your card's limit.","No Account":"The card number does not match any account.","Payer Cannot Pay For This Transaction With PayPal":"Your PayPal account cannot be used for this transaction.","Processor Declined":"Your bank declined the transaction.","Processor Declined - Fraud Suspected":"Your transaction was declined due to suspected fraud.","Transaction Not Allowed":"This type of transaction is not allowed.","Violation":"This transaction violates a rule or restriction.","Voice Authorization Required":"A special authorization is needed to complete this transaction."}},"captcha_failed":"Please complete the captcha verification to continue with checkout.","dialogs":{"new_user":{"confirm_text":"Create account","text":"Do you want to create an account and continue?","title":"User does not exist"},"wrong_password":{"confirm_text":"Send reminder","text":"Do you want us to send you a password reminder?","title":"Wrong password!"}},"error_messages":{"bot_restriction":"We are sorry, but we cannot process your order"}},"cleverpush":{"run_succeeded":{"text":"Open CGTrader to view it.","title":"Your 3D model is ready"}},"comments":{"comment_posted":"Comment has been successfully posted","leave_comment":"Leave a comment","no_comments":"There are no comments yet.","please_enter_comment":"Please enter your comment","posted_by":"Posted by \u003ca href='%{author_profile_url}' target='_blank'\u003e%{author}\u003c/a\u003e \u003cspan class='comment-time'\u003e%{time}\u003c/a\u003e","reply":"Reply","submit":"Post Comment","title":"Comments","you_must_login":"You must \u003ca href='javascript:;' class='user-form-trigger login'\u003eLog In\u003c/a\u003e to join the conversation."}
16518,"competition":{"digital-art-competition":{"admin":{"reasons_of_disapproval":{"artwork_per_category_limit":"Participant submitted more than 3 different artworks per category\n","contains_nudity":"Artwork contains nudity and/or violence\n","copyright":"Artwork contains watermarks, company logos, trademarks or otherwise display copyrighted content which does not belong to the participant\n","duplicate_entry":"Participant submitted images of the same character, object or environment to both the concept art and illustration categories\n","low_resolution_image":"Participant submitted low-resolution image/s – less than 1920 pixels\n","no_link_to_cgtrader":"Participant has placed a participant tag/note, but have not linked it to CGTrader\n","no_tag_note_and_link_to_cgtrader":"Participant has not placed a participant tag/note \u0026 link to CGTrader\n","not_cg":"Artwork is traditional art image (not CG)\n","not_owned_by_participant":"Artwork is not owned by the participant\n"}},"categories":{"category_filters":[{"illustration_character":null,"slug":"illustration_character","label":"Character Illustration"},{"illustration_environment":null,"slug":"illustration_environment","label":"Environment Illustration"},{"illustration_object":null,"slug":"illustration_object","label":"Object Design Illustration"},{"concept_character":null,"slug":"concept_character","label":"Character Concept Art"},{"concept_environment":null,"slug":"concept_environment","label":"Environment Concept Art"},{"concept_object":null,"slug":"concept_object","label":"Object Design Concept Art"}],"concept":[{"character":null,"short_title":"Character","title":"Character Concept Art","slug":"concept_character","description":"Some character and creature concepts are works of art on their own. This is the category where you can submit your game character concepts, cartoon character sketches, including character designs of both real and imaginary creatures. You can also submit fan arts of your favorite characters.","sponsor":{"name":"Paintable","slug":"paintable","url":"https://paintable.cc"},"prizes":{"prize_pool":"~$10,000","total_values":{"first_place":4340,"second_place":2770,"third_place":2770},"first_place":[{"prize":"GT-221 PRO Pen Tablet Monitor by","sponsor":"Huion","value":1150},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"2 years subscription to","sponsor":"Stash Magazine","value":280},{"prize":"“Sapphire” creative plug-in 1 year subscription by","sponsor":"Boris FX","value":900},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 \u0026 Hexels 3 licenses by","sponsor":"Marmoset","value":240},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"second_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"third_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}]}},{"environment":null,"short_title":"Environment","title":"Environment Concept Art","slug":"concept_environment","description":"Conceptual environments, sketched interiors and exteriors or natural landscapes find their place in this category. If you have fantasy land concepts or places that are waiting to come alive, submit them to this category.","sponsor":{"name":"Pencilkings","slug":"pencilkings","url":"https://www.pencilkings.com"},"prizes":{"prize_pool":"~$10,000","total_values":{"first_place":4200,"second_place":2770,"third_place":2770},"first_place":[{"prize":"GT-221 PRO Pen Tablet Monitor by","sponsor":"Huion","value":1150},{"prize":"2 years subscription to","sponsor":"Stash Magazine","value":280}
16518,{"prize":"“Mocha VR” creative plug-in 1 year subscription by","sponsor":"Boris FX","value":500},{"prize":"Master Collection by","sponsor":"Tonytextures","value":630},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 \u0026 Hexels 3 licenses by","sponsor":"Marmoset","value":240},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"second_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"third_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}]}},{"object_design":null,"short_title":"Object","title":"Object Design Concept Art","slug":"concept_object","description":"Your greatest sketches and concept art of separate objects (e.g. furniture concepts, gun sketches, props, etc.), including prototypes and conceptual CG product design can be submitted to this category. Please note that artworks containing visible brands or watermarks cannot participate in the Competition.","sponsor":{"name":"Marmoset","slug":"marmoset","url":"https://www.marmoset.co"},"prizes":{"prize_pool":"~$10,000","total_values":{"first_place":4140,"second_place":2770,"third_place":2770},"first_place":[{"prize":"GT-221 PRO Pen Tablet Monitor by","sponsor":"Huion","value":1150},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"2 years subscription to","sponsor":"Stash Magazine","value":280},{"prize":"“Continuum” creative plug-in 1 year subscription by","sponsor":"Boris FX","value":700},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 \u0026 Hexels 3 licenses by","sponsor":"Marmoset","value":240},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"second_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"third_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}]}}],"illustration":[{"character":null,"short_title":"Characters","title":"Character Illustration","slug":"illustration_character","description":"Whether you are creating CG humans, historical personalities or mythical beings, this is the category to showcase the best characters in your portfolio. Final versions of both real and fantasy creatures, including fan art, can be submitted for this nomination.","sponsor":{"name":"Huion","
16518slug":"huion","url":"https://www.huiontablet.com"},"prizes":{"prize_pool":"~$10,000","total_values":{"first_place":4610,"second_place":2770,"third_place":2770},"first_place":[{"prize":"GT-221 PRO Pen Tablet Monitor by","sponsor":"Huion","value":1150},{"prize":"“Sapphire” creative plug-in 1 year subscription by","sponsor":"Boris FX","value":900},{"prize":"Master Collection by","sponsor":"Tonytextures","value":630},{"prize":"2 years subscription to","sponsor":"Stash Magazine","value":280},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 \u0026 Hexels 3 licenses by","sponsor":"Marmoset","value":240},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"second_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"third_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}]}},{"environment":null,"short_title":"Environment","title":"Environment Illustration","slug":"illustration_environment","description":"Digitally designed spaces which fully convey your style and emotion are a true testament to the hard work and dedication of the artist. Finalized environments, from interiors and exteriors to natural landscapes find their place in this category.","sponsor":{"name":"Tony Textures","slug":"tonytextures","url":"http://www.tonytextures.com"},"prizes":{"prize_pool":"~$10,000","total_values":{"first_place":4210,"second_place":2770,"third_place":2770},"first_place":[{"prize":"GT-221 PRO Pen Tablet Monitor by","sponsor":"Huion","value":1150},{"prize":"2 years subscription to","sponsor":"Stash Magazine","value":280},{"prize":"“Mocha VR” creative plug-in 1 year subscription by","sponsor":"Boris FX","value":500},{"prize":"Master Collection by","sponsor":"Tonytextures","value":630},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 \u0026 Hexels 3 licenses by","sponsor":"Marmoset","value":240},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"second_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"third_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}]}},{"object_design":null,"short_title":"Object","title":"Object Design Illustration","slug":"illustration_object","description":"All detailed or stylized separate objects, from modern product design to a moldy rock or a cool spaceship design, find their place in this category. Please note that artworks containing visible brands or watermarks cannot participate in the Competition.","sponsor":{"name":"Celsys","slug":"celsys","url":"https://www.celsys.co.jp/en"},"prizes":{"prize_pool":"~$10,000","total_values":{"first_place":4140,"second_place":2770,"third_place":2770},"first_place":[{"prize":"GT-221 PRO Pen Tablet Monitor by","sponsor":"Huion","value":1150},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"2 years subscription to","sponsor":"Stash Magazine","value":280},{"prize":"“Continuum” creative plug-in 1 year subscription by","sponsor":"Boris FX","value":700},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 \u0026 Hexels 3 licenses by","sponsor":"Marmoset","value":240},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"second_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}],"third_place":[{"prize":"GT-191 PRO Pen Tablet Monitor by","sponsor":"Huion","value":650},{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Creative software licenses set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Toolbag 3 license by","sponsor":"Marmoset","value":190},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings yearly plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"Digital Painting Academy yearly plan by","sponsor":"Paintable","value":300},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60}]}}],"landing_page":[{"title":"Illustration","slug":"illustration"},{"title":"Concept art","slug":"concept"}],"number":"six","page_description":"Participate in the CGTrader Digital Art Competition by submitting your works to Character Illustration, Character Concept Design, Environment Illustration, Environment Concept Design, Object Design, and Object Concept Design categories. If you have several great works that suit different categories, feel free to submit them to the corresponding categories. Select a category to see the prizes for that category.","public_award":[{"public_award":null,"title":"Public Award","slug":"public_award","description":"Two artworks that generate the most likes in the Competition gallery will be awarder the Public Award. This is everyone's favorite category, because you can choose the best of the best! All valid submissions to the Competition compete for this Prize.","prizes":{"total_values":{"first_place":2000,"second_place":2000,"third_place":0},"first_place":[{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Creative software license set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings annual plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"H950P Graphic Tablet by","sponsor":"Huion","value":80},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60},{"prize":"Digital Painting Academy annual plan by","sponsor":"Paintable","value":300}],"second_place":[{"prize":"Starter Collection by","sponsor":"Tonytextures","value":360},{"prize":"1 year subscription to","sponsor":"Stash Magazine","value":160},{"prize":"Creative software license set by","sponsor":"Nevercenter","value":200},{"prize":"Time tracking software license by","sponsor":"Fanurio Time Tracking","value":120},{"prize":"Selected graphic software license by","sponsor":"Escape Motions","value":90},{"prize":"Clip Studio Paint EX software license by","sponsor":"Celsys","value":220},{"prize":"Online Art Trainings annual plan by","sponsor":"Pencil Kings","value":300},{"prize":"Visual apps making software license by","sponsor":"Lightwell","value":120},{"prize":"H950P Graphic Tablet by","sponsor":"Huion","value":80},{"prize":"Black Ink painting application by","sponsor":"Bleank","value":60},{"prize":"Digital Painting Academy annual plan by","sponsor":"Paintable","value":300}]}}],"tabs":[{"title":"Illustration","slug":"illustration"},{"title":"Concept art","slug":"concept"},{"title":"Public award","slug":"public_award"}]},"description_html":"CGTrader is calling CG artists (\u003cstrong\u003eboth 2D and 3D\u003c/strong\u003e) from around the world to show their best works of art. Creativity and innovation come together in our Competition to bring out the vast potential of CG. Creatives are invited to join before September 30 and win their share of the prize pool. Our community of 1.2M+ users is waiting for your artworks! All it takes is these 3 simple steps:","gallery":{"page_description":"This is the CGTrader Digital Art Competition Gallery. Every submission to the Competition is automatically published and showcased in this gallery. Please admire, share and vote for your favorite works of art: two submissions that receive the most likes will win the Public Award!"},"header":{"title":{"description_html":"Enter your artwork to win prizes worth a total of \u003cstrong\u003e$60,000\u003c/strong\u003e","first_line_html":"CGTrader \u003cstrong\u003eDigital Art Competition\u003c/strong\u003e","second_line_html":"where best CG artworks come together"}},"judges":[{"name":"Stephen Price","title":"Founding Editor / Creative Director in","company":"Stash Media","slug":"stephen_price","url":"http://www.stashmedia.tv","description_html":"Stephen Price is the founder and creative director of Stash Media Inc. He is also the executive editor of Stash Magazine, and Stash Permanent Collection, the world’s largest online video library of design, animation and VFX.\u003cbr /\u003e\u003cbr /\u003eStephen has given dozens of talks on design and digital production at festivals and corporate events spanning from Los Angeles to Istanbul and has curated scores of animation/VFX film screenings in over 20 countries.\u003cbr /\u003e\u003cbr /\u003ePrior to foun
16518ding Stash Media, Stephen has been the executive producer of animation and visual effects for feature films, TV series and advertising – winning multiple industry awards along the way. From 1995-2000, Stephen created and executive produced five seasons of “Splat!” a TV series for teens about animation and VFX broadcast in over 100 markets."},{"name":"Samur Isma","title":"Founder of","company":"FREEYORK","slug":"samur_isma","url":"https://freeyork.org","description_html":"Samur Isma is halfway between tech and design with a strong entrepreneurial mindset. After starting his career as a freelance graphic designer, he has studied computer science at the Eastern Mediterranean University in Mersin and, then, at the Technical University of Wroclaw – where he graduated in computer science and marketing. In 2009 he founded FREEYORK, and nowadays he divides his mind and his time between his daytime job as a project manager and the management of his editorial project FREEYORK."},{"name":"Kirill Tokarev","title":"Editor in Chief at","company":"80 Level","slug":"kirill_tokarev","url":"https://80.lv","description_html":"Kirill Tokarev is the Editor in Chief of 80.lv. Over the years he worked in one of the biggest game magazines in Ukraine – Gameplay – under the Sergey Galyonkin’s (SteamSpy) management, worked as a features editor at Igromania magazine, and a bunch of other publications in former USSR region. Coming from game marketing and journalism he turned his passion of 3d into building a powerful platform for 3d artists. Within 3 years his small team managed to grow 80.lv to the size we know today. \"It’s a great honor to be part of this creative community and tell the stories of amazing people working in this field\" - Kirill Tokarev."}],"meta":{"description":{"categories":"Participate in the CGTrader Digital Art Competition by submitting your works to one of the six Competition categories and win great prizes. Submit your Concept Designs and Illustration of Characters, Environments and Product designs.","gallery":"This is the CGTrader Digital Art Competition Gallery. Every submission to the Competition is automatically published and showcased in this gallery. Please admire, share and vote for your favorite works of art: two submissions that receive the most likes will win the Public Award!","general":"%{artwork_name} is participating in the %{category_name} category of the CGTrader Digital Art Competition! Vote for it on https://cgtrader.com/digital-art-compet
16518ition/%{entry_link} Thanks!","judges":"Meet the industry experts who will be evaluating all submissions for Digital Art Competition and selecting the best of the best.","main_page":"CGTrader is calling digital artists from around the world to show their best works of art. Creativity and innovation come together in this competition to bring out the vast potential of CG. By taking a part, designers will get exposure and opportunity to win a share of the great prize pool.","partners":"There are several companies and brands helping us make the CGTrader Digital Art Competition happen. Find out more about the partners of the CGTrader Digital Art Competition, their products and prizes awarded to the winners.","rules":"Take a minute to familiarize yourself with the terms and conditions of the CGTrader Digital Art Competition. See key dates, find out how to take part in the Competition, how to add a competition tag on your portfolio website and how the entries will be judged.","twitter":"%{artwork_name} is participating in the CGTrader Digital Art Competition I %{category_name} | Vote for %{artwork_name}
16518 here: https://cgtrader.com/digital-art-competition/%{entry_link} #CGart #CGTrader #competition"},"title":{"categories":"CGTrader Digital Art Competition – Categories \u0026 Prizes","gallery":"CGTrader Digital Art Competition - Gallery","judges":"CGTrader Digital Art Competition - Judges","main_page":"CGTrader Digital Art Competition","partners":"CGTrader Digital Art Competition - Partners","rules":"CGTrader Digital Art Competition - Rules","social":"%{artowrk_name} - CGTrader Digital Art Competition"}},"partners":{"bronze_tier":[{"name":"Escape Motions","slug":"escape_motions","url":"http://www.escapemotions.com","description":"Escape Motions is a small studio of creatives and programmers who love to experiment with art and code to find a new visual expression in digital media. We put our heart and soul into what we do, our every application is handcrafted and we play with each bit of code to bring original innovative tools to creative people at all skill levels. This is why our Flame Painter, Amberlight and Rebelle were born."},{"name":"Fanurio Time Tracking","slug":"fanurio_time_tracking","url":"http://www.fanuriotimetracking.com","description":"Fanurio Time Tracking is a privately-held software company specialized in business applications. It develops Fanurio, a time tracking and billing application for freelancers and teams."},{"name":"Bleank","slug":"bleank","url":"http://www.bleank.com","description":"Bleank is an independent studio that creates high-end creative software for digital artists. Known for their digital painting solutions, Bleank’s mission is to provide artists with tools that allow them to be unique. Black Ink is their flagship product, and was designed to challenge the norms of the generative art world."},{"name":"Lightwell","slug":"lightwell","url":"https://lightwell.pro","description":"Lightwell is a powerful creation tool for artists, designers, and developers. Build, prototype, and publish your own native iOS app without writing code."}],"diamond_tier":[{"name":"Huion","slug":"huion","url":"https://www.huiontablet.com","description":"Huion is a professional supplier who provides the solutions of digital handwriting and digital painting. Since its foundation in 2007, Huion’s products include pen display, pen tablets, light pads, helping customers who are engaged in digital creativity field such as digital painting, 3D industrial design, film tricks, game concept art, cartoons, graphic design etc from creativity building, design and creation , visualization to the execution, which enables every creative person to make its dreams come true."}],"gold_tier":[{"name":"Tonytextures","slug":"tonytextures","url":"http://www.tonytextures.com","description":"All you need for architecture visualization! Grab our free collection of high quality cutouts and textures, browse through archiviz tutorials or get lost in our free 4000+ free texture library."},{"name":"Pencilkings","slug":"pencilkings","url":"https://www.pencilkings.com","description":"Pencilkings provides professional level art education to anyone, anywhere in the world without the traditional high costs. Price and location will not be a barrier to becoming a better artist."},{"name":"Paintable","slug":"paintable","url":"https://paintable.cc","description":"Do you dream of being a world-class digital painter? At Paintable, we design interactive exercises to help you paint the ideas in your imagination. Since 2016, we've been fortunate enough to help hundreds of thousands of motivated students in their journey towards becoming a professional illustrator, concept artist or character designer."}],"main_page_partners":[{"name":"Huion","slug":"huion","url":"https://www.huiontablet.com"},{"name":"Tonytextures","slug":"tonytextures","url":"http://www.tonytextures.com"},{"name":"Pencilkings","slug":"pencilkings","url":"https://www.pencilkings.com"},{"name":"Paintable","slug":"paintable","url":"https://paintable.cc"}],"media":[{"name":"Andy Sowards","slug":"andy_sowards","url":"http://www.andysowards.com/blog/"},{"name":"Freeyork","slug":"freeyork","url":"https://freeyork.org/"},{"name":"80 level","slug":"80level","url":"https://80.lv"},{"name":"Concept Art World","slug":"concept_art_world","url":"http://conceptartworld.com"}],"page_description":"There are several companies and brands helping us make the CGTrader Digital Art Competition happen. Our partners believe in the power of digital art and want to supp
16518ort artists globally. Find out more about the partners of the CGTrader Digital Art Competition, their products and prizes awarded to the winners.","silver_tier":[{"name":"Marmoset","slug":"marmoset","url":"https://www.marmoset.co","description":"Marmoset is the creative standard in digital art software; makers of the real-time 3D rendering and lookdev suite, Marmoset Toolbag 3, the WebGL-powered Marmoset Viewer, and the 2D grid-based painting and design suite, Marmoset Hexels 3."},{"name":"CELSYS","slug":"celsys","url":"https://www.celsys.co.jp/en","description":"CELSYS is the developer and distributor of \u003ca href=\"http://www.clipstudio.net/en\" target=\"_blank\"\u003eCLIP STUDIO PAINT\u003c/a\u003e (formerly Manga Studio), the illustration, comics and animation software that is beloved by more than 3,000,000 artists worldwide. It offers a wide variety of creative features, such as screen tones and 3D drawing models, and provides artists with brushes, pens and more that feel close to traditional media."},{"name":"Boris FX","slug":"boris_fx","url":"https://goo.gl/Uxm36w","description":"Boris FX is the leading developer of award-winning creative plug-ins for editing, visual effects, and motion graphics."},{"name":"Stash Magazine","slug":"stash_magazine","url":"http://www.stashmedia.tv","description":"Stash Magazine keeps the design, animation, and VFX world inspired with its daily news site and the subscription-based STASH PERMANENT COLLECTION – the planet's largest online creative resource of motion projects – streaming over 5,000 TV/cinema ads, music videos, title sequences, brand films, game trailers, and short films, plus behind the scenes features and exclusive interviews."},{"name":"Nevercenter","slug":"nevercenter","url":"http://nevercenter.com","description":"Nevercenter is a small group of software artists making the kind of creative software we most enjoy using. Silo 2 is our professional standalone 3D modeling software for Mac, PC, and Linux; the perfect tool for achieving 3D modeling zen."}]},"rules":{"badges":[{"title":"badge","link":"https://www.cgtrader.com/competitions/digital_art/badge.png","code":"\u003ca href=\"https://www.cgtrader.com\" target=\"_blank\"\u003e\u003cimg src=\"https://www.cgtrader.com/competitions/digital_art/badge.png\" alt=\"CGTrader Digital Art Competition\" /\u003e\u003c/a\u003e"},{"title":"badgeWithText","link":"https://www.cgtrader.com/competitions/digital_art/text_badge.png","code":"\u003ca href=\"https://www.cgtrader.com\" target=\"_blank\"\u003e\u003cimg src=\"https://www.cgtrader.com/competitions/digital_art/text_badge.png\" alt=\"CGTrader Digital Art Competition\" /\u003e\u003c/a\u003e"}],"email":"[email protected]","entry_validation":{"dates":"March 1, 2018 to September 30, 2018 (11:59 PM PT)","first_point":"The Participant shall only submit a single project per entry and assign it to the single most relevant category. The Participant should not submit images of the same character, object or environment to both the concept art and illustration categories.","max_submissions":3},"how_to_enter":{"badges_link":"https://www.dropbox.com/sh/qsjw0zbshu7w30f/AABv_9Rx5vgULtccChNW1Myma?dl=1","first_step_html":"Submit your best 2D or 3D artwork(s) to the corresponding category(-ies). There are a total of six different categories: Character Illustration, Character Concept Design, Environment Illustration, Environment Concept Design, Object Design, and Object Concept Design. You can submit up to 3 different works per each category (from six available).\u003cbr /\u003e\u003cbr /\u003eA single artwork can be submitted only once by assigning it to a relevant category.","third_step":"Submit the application form online before the deadline on September 30th, 2018 (11:59 PM Pacific Time). All submissions will also be published to the special competition gallery on CGTrader and will not only gain visibility in our community, but will also get a chance to win the Public Award."},"key_dates":{"evaluation":"October 1-20, 2018","submission":"March 1 - September 30, 2018","winners":"October 31, 2018"},"page_description":"Take a minute to familiarize yourself with the terms and conditions of the CGTrader Digital Art Competition. See key dates, find out how to take part in the Competition, how the entries will be judged and what responsibilities and rights are held by related parties.","prizes_page":"categories","sponsors":"“Huion”, “Tony Textures”, ”Pencil Kings”, “Paintable”, “Marmoset”, “Boris FX”, “Celsys”, “Stash Magazine”, “Nevercenter”,”Escape Motions”, “Lightwell”, “Fanurio Time Tracking”, “Bleank”","winner_selection":{"categories":"Character Illustration, Environment Illustration, Object Design, Character Concept Art, Environment Concept Art, Object Concept Design","public_award":"The Public Award will be given for the artwork which generates the most likes on CGTrader.","public_award2":"Two artworks that generate the most likes will be awarded the Public Award."}},"submission_form":{"categories":[{"title":"Illustration","buttons":[{"text":"Character Illustration","slug":"illustration_character"},{"text":"Environment Illustration","slug":"illustration_environment"},{"text":"Object Illustration","slug":"illustration_object"}]},{"title":"Concept art","buttons":[{"text":"Character Concept","slug":"concept_character"},{"text":"Environment Concept","slug":"concept_environment"},{"text":"Object Concept","slug":"concept_object"}]}]},"title":"Digital Art Competition"},"notifications":{"failure":"Sorry, something went wrong, please check if you have filled all fields correctly and try again","success":"Thank y
16518ou for participating! You will receive a confirmation email from us shortly"}},"conversations":{"action_button_confirmation":"Do you really want to reopen this request?","reply_posted":"Reply has been posted.","with_yourself":"can't be same with sender."},"coupons":{"bannerSubtitle":"Seeking official CGTrader coupon codes? Look no further – you're in the right spot for the best discounts","bannerTitle":"CGTrader Coupons and Promo Codes %{year}","couponExpires":"Coupon Expires","couponsList":{"discount":"Sign up and receive a 10% discount for your first purchase","discounts":"Sign up and get notified about time-limited discounts for your purchases","freeModels":"Download high-quality 3D models for free"},"deal":"DEAL","faq":"Faq","getDeal":"Get deal","title":"Coupons","useCode":"Use code:"},"date":{"abbr_day_names":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"abbr_month_names":[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"day_names":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"formats":{"default":"%Y-%m-%d","long":"%B %d, %Y","short":"%b %d"},"month_names":[null,"January","February","March","April","May","June","July","August","September","October","November","December"],"order":["year","month","day"]},"datetime":{"distance_in_words":{"about_x_hours":{"one":"about 1 hour","other":"about %{count} hours"},"about_x_months":{"one":"about 1 month","other":"about %{count} months"},"about_x_years":{"one":"about 1 year","other":"about %{count} years"},"almost_x_years":{"one":"almost 1 year","other":"almost %{count} years"},"half_a_minute":"half a minute","less_than_x_minutes":{"one":"less than a minute","other":"less than %{count} minutes"},"less_than_x_seconds":{"one":"less than 1 second","other":"less than %{count} seconds"},"over_x_years":{"one":"over 1 year","other":"over %{count} years"},"x_days":{"one":"1 day","other":"%{count} days"},"x_minutes":{"one":"1 minute","other":"%{count} minutes"},"x_months":{"one":"1 month","other":"%{count} months"},"x_seconds":{"one":"1 second","other":"%{count} seconds"}},"prompts":{"day":"Day","hour":"Hour","minute":"Minute","month":"Month","second":"Seconds","year":"Year"}},"design_partnership":{"benefits":{"benefits_arr":[{"title":"Start working immediately","info":"Skip the client search - work on hundreds of available modeling tasks that meet your interest and payment expectations.","icon":"rabbit-fast"},{"title":"Flexible working hours","info":"Suitable for night-owls and early birds alike - you will be the one who chooses how much to work.","icon":"clock"},{"title":"Work from the comfort of your home","info":"All freelance tasks can be completed from your workstation at home or any other place you prefer.","icon":"couch"},{"title":"Earn real money","info":"Earn real money. Transferred via Hyperwallet or Payoneer.","icon":"money-check-alt"},{"title":"Dedicated 3D support","info":"Our in-house team is ready to share 3D know-how, answer technical questions and help you complete the task.","icon":"user-friends"},{"title":"Work with industry-leading clients","info":"Create custom models for global businesses (including Fortune 500 companies).","icon":"hands-helping"}],"section_title":"What's in it for you?"},"cta_button":"Join Wildcat today","explanation_steps":{"section_title":"Join us today","step_1":{"info":"Create an account with CGTrader, if you don’t have one yet. Once logged in, provide samples of your work or take a quick trial task. If your skills match, you’ll get full access to our paid modeling tasks.","title":"Get on board"},"step_2":{"info":"Got approved? Instantly see all available 3D modeling tasks on our Wildcat system, work on the ones you like or come back later for more.","title":"Get started"},"step_3":{"info":"Select your favorite payout method and receive all your freelance earnings on a monthly or weekly basis.","title":"Get paid"}},"header":{"benefits":[{"text":"Earn up to $200 per delivered model"},{"text":"Pick the tasks you want, no bidding"},{"text":"Enjoy multiplier bonuses and top rates"},{"text":"A constant stream of work and income"}],"page_title":"A no-bidding freelance platform for 3D designers","subtitle":"Join an exclusive 3D design community and get full access to hundreds of curated modeling tasks. Work from anywhere, anytime!","switcher_html":"\u003ca href='/freelance-with-us' class='is-active'\u003eEN\u003c/a\u003e\n\u003cspan class='u-mr10 u-ml10'\u003e|\u003c/span\u003e\n\u003ca href='/ru/Zarabatyvayte-s-CGTrader'\u003eRU\u003c/a\u003e\n"},"help_section":{"contact_btn":"Contact us","help_center_btn":"Visit Help Center","section_title":"Got questions?"},"meta":{"description":"Join an exclusive 3D design community and get full access to hundreds of curated modeling tasks. Work from anywhere, anytime!","title":"Freelance work with CGTrader"},"positions":{"open_positions":[{"type":"pbr","title":"PBR (low-poly) modeling","info_html":"\u003cp\u003eOpen for 3D designers experienced in creating PBR models.\u003c/p\u003e\n\u003cp\u003e\n \u003cspan class=\"text-semibold\"\u003eNo software requirements\u003c/span\u003e \u003cbr /\u003e\n Delivery format: \u003cspan class=\"text-semibold\"\u003e.fbx\u003c/span\u003e\n\u003c/p\u003e\n","badge":"In demand!"},{"type":"archviz","title":"Arch-viz (high-poly) modeling","info_html":"\u003cp\u003eOpen for 3D designers skilled in 3D modeling and texturing.\u003c/p\u003e\n\u003cp\u003e\n Software requirements: \u003cspan class=\"text-semibold\"\u003e3ds Max 2020\u003c/span\u003e (earlier versions are supported but not recommended)\u003cbr /\u003e\n Renderer: \u003cspan class=\"text-semibold\"\u003eV-ray Next, update 3.2\u003c/span\u003e (other versions are supported but not recommended)\n\u003c/p\u003e\n","badge":""}],"section_title":"Currently available tasks"}},"devise":{"confirmations":{"confirmed":"Your account was successfully confirmed. You are now signed in.","send_instructions":"Confirmation email was successfully sent.","send_paranoid_instructions":"If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes.","sent_success_message":"\t✓ Your email reset was successfully sent. Please check your inbox..."},"failure":{"already_authenticated":"You are already signed in. Log out and try again.","inactive":"Your account was not activated yet.","invali
16518d":"Invalid email or password.","invalid_token":"Invalid authentication token.","last_attempt":"You have one more attempt before your account is locked.","locked":"Your account is locked.","no_email":"Your Facebook privacy settings prevent from providing your email for logging in. Please change them or use alternative log in methods","not_found_in_database":"Invalid email or password.","timeout":"Your session expired, please sign in again to continue.","too_many_requests":"You’ve already requested a password reset email. Please wait a few minutes before trying again.","unauthenticated":"You need to sign in or sign up before continuing.","unconfirmed":"This email address is not confirmed yet. Please check your inbox for confirmation email."},"mailer":{"confirmation_instructions":{"subject":"Confirmation instructions"},"email_changed":{"subject":"Email Changed"},"password_change":{"subject":"Password Changed"},"reset_password_instructions":{"subject":"Reset password instructions"},"unlock_instructions":{"subject":"Unlock Instructions"}},"omniauth_callbacks":{"failure":"Could not authenticate you from %{kind} because \"%{reason}\".","success":"Successfully authenticated from %{kind} account."},"passwords":{"no_token":"You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided.","send_instructions":"You will receive an email with instructions about how to reset your password in a few minutes.","send_paranoid_instructions":"If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.","updated":"Your password was changed successfully. You are now signed in.","updated_not_active":"Your password was changed successfully."},"registrations":{"destroyed":"Bye! Your account was successfully cancelled. We hope to see you again soon.","signed_up":"Welcome! You have signed up successfully.","signed_up_but_inactive":"You have signed up successfully. However, we could not sign you in because your account is not yet activated.","signed_up_but_locked":"You have signed up successfully. However, we could not sign you in because your account is locked.","signed_up_but_unconfirmed":"Thanks for registering. Please check your inbox and confirm your email address to continue.","signed_up_in_checkout":"Thanks for registering. Feel free to continue to checkout.","update_needs_confirmation":"You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address.","updated":"You updated your account successfully.","updated_but_not_signed_in":"Your account has been updated successfully, but since your password was changed, you need to sign in again."},"sessions":{"already_signed_out":"Signed out successfully.","signed_in":"Signed in successfully.","signed_out":"Signed out successfully."},"unlocks":{"send_instructions":"You will receive an email with instructions about how to unlock your account in a few minutes.","send_paranoid_instructions":"If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.","unlocked":"Your account has been unlocked successfully. Please sign in to continue."}},"disabled_cookies_modal":{"button":"Cancel","text_1":"You will not be able explore millions of great 3D models, add them to the cart or Buy.","text_2":"If you want to experience CGTrader fully, we encourage enabling cookies on this site.","title":"It seems you have your browser's cookies disabled"},"doorkeeper":{"applications":{"buttons":{"authorize":"Authorize","cancel":"Cancel","destroy":"Destroy","edit":"Edit","submit":"Submit"},"confirmations":{"destroy":"Are you sure?"},"edit":{"title":"Edit application"},"form":{"error":"Whoops! Check your form for possible errors"},"help":{"blank_redirect_uri":"Leave it blank if you configured your provider to use Client Credentials, Resource Owner Password Credentials or any other grant type that doesn't require redirect URI.","confidential":"Application will be used where the client secret 
16518can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential.","native_redirect_uri":"Use %{native_redirect_uri} for local tests","redirect_uri":"Use one line per URI","scopes":"Separate scopes with spaces. Leave blank to use the default scopes."},"index":{"actions":"Actions","callback_url":"Callback URL","confidential":"Confidential?","confidentiality":{"no":"No","yes":"Yes"},"name":"Name","new":"New Application","title":"Your applications"},"new":{"title":"New Application"},"show":{"actions":"Actions","application_id":"Application Id","callback_urls":"Callback urls","confidential":"Confidential","not_defined":"Not defined","scopes":"Scopes","secret":"Secret","secret_hashed":"Secret hashed","title":"Application: %{name}"}},"authorizations":{"buttons":{"authorize":"Authorize","deny":"Deny"},"error":{"title":"An error has occurred"},"form_post":{"title":"Submit this form"},"new":{"able_to":"This application will be able to","prompt":"Authorize %{client_name} to use your account?","title":"Authorization required"},"show":{"title":"Authorization code"}},"authorized_applications":{"buttons":{"revoke":"Revoke"},"confirmations":{"revoke":"Are you sure?"},"index":{"application":"Application","created_at":"Created At","date_format":"%Y-%m-%d %H:%M:%S","title":"Your authorized applications"}}
16518,"errors":{"messages":{"access_denied":"The resource owner or authorization server denied the request.","admin_authenticator_not_configured":"Access to admin panel is forbidden due to Doorkeeper.configure.admin_authenticator being unconfigured.","credential_flow_not_configured":"Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.","forbidden_token":{"missing_scope":"Access to this resource requires scope \"%{oauth_scopes}\"."},"invalid_client":"Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.","invalid_code_challenge_method":{"one":"The code_challenge_method must be %{challenge_methods}.","other":"The code_challenge_method must be one of %{challenge_methods}.","zero":"The authorization server does not support PKCE as there are no accepted code_challenge_method values."},"invalid_grant":"The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.","invalid_redirect_uri":"The redirect uri included is not valid.","invalid_request":"The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.","invalid_resource_owner":"The provided resource owner credentials are not valid, or resource owner cannot be found","invalid_scope":"The requested scope is invalid, unknown, or malformed.","invalid_token":{"expired":"The access token expired","revoked":"The access token was revoked","unknown":"The access token is invalid"},"resource_owner_authenticator_not_configured":"Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.","revoke":{"unauthorized":"You are not authorized to revoke this token"},"server_error":"The authorization server encountered an unexpected condition which prevented it from fulfilling the request.","temporarily_unavailable":"The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.","unauthorized_client":"The client is not authorized to perform this request using this method.","unsupported_grant_type":"The authorization grant type is not supported by the authorization server.","unsupported_response_mode":"The authorization server does not support this response mode.","unsupported_response_type":"The authorization server does not support this response type."}},"flash":{"applications":{"create":{"notice":"Application created."},"destroy":{"notice":"Application deleted."},"update":{"notice":"Application updated."}},"authorized_applications":{"destroy":{"notice":"Application revoked."}}},"layouts":{"admin":{"nav":{"applications":"Applications","home":"Home","oauth2_provider":"OAuth2 Provider"},"title":"Doorkeeper"},"application":{"title":"OAuth authorization required"}},"pre_authorization":{"status":"Pre-authorization"},"scopes":{"public":"Access your public data","write":"Update your data"}},"emails":{"error_notification":"Subscription not found.","success_subscribed_notification":"You have successfully subscribed.","success_unsubscribed_notification":"You have successfully unsubscribed."},"embed":{"item_not_available":"This item is no longer available."},"enterprise":{"3dContent":{"list":{"assets":"More than 1.6 million readily available 3D assets to choose from","custom":"Custom 3D modeling at scale","manager":"Dedicated customer success managers","search":"Tailored search support and 3D technical support (from advice to a model conversion)"},"title":"Quality 3D content at any scale"},"apply":"Apply","applySuccess":"Successfully applied","company'":{"label":"Company name"},"email":{"label":"Email"},"errors":{"email":"Invalid email","required":"This field is required"},"formTitle":"Apply for a business account by filling your details below","full_name":{"label":"Your name"},"licensing":{"list":{"guidance":"Guidance and advice on licensing and use","licenses":"Only two default licenses with clear terms","modelsSold":"90% of the models are sold on the marketplace with a Royalty-free license","royaltyFree":"Royalty-free license allows a wide range of commercial use"},"title":"Business oriented licensing"},"meta":{"description":"Leveraging our global community of highly skilled 3D designers, CGTrader converts eCommerce 2D images into compelling, high-quality 3D models for immersive product experiences.","t
16518itle":"Transform Your eCommerce Results with 3D Product Visualization"},"payment":{"list":{"digitalAsset":"Digital Asset Management system (cloud-based 3D model storage)","flexiblePayment":"Flexible payment terms and more convenient checkout","offers":"Special offers and discounts","shareAccounts":"Ability to share accounts and simplify payments for your employees"},"title":"Easy payment and sharing"},"review":"CGTrader is a great partner for our needs. We found the Prepaid credit payments very convenient for making purchases on the platform and custom reporting solutions help us a lot with asset management.","reviewAuthor":"Shouyi Lu, Yahaha Studios","subtitle":"3D content source for any type of business - from small design studios to large enterprises","title":"Business Account"},"errors":{"connection_refused":"Oops! Failed to connect to the Web Console middleware.\nPlease make sure a rails development server is running.\n","format":"%{attribute} %{message}","messages":{"accepted":"must be accepted","already_confirmed":"was already confirmed, please try signing in","blank":"can't be blank","carrierwave_direct_allowed_extensions":"Allowed file types are %{extensions}","carrierwave_direct_allowed_schemes":"Allowed schemes are %{schemes}","carrierwave_direct_attachment_missing":"attachment is missing","carrierwave_direct_filename_invalid":"is invalid. ","carrierwave_direct_filename_taken":"filename was already taken","carrierwave_direct_upload_missing":"upload is missing","carrierwave_download_error":"could not be downloaded","carrierwave_integrity_error":"is not of an allowed file type","carrierwave_processing_error":"failed to be processed","confirmation":"doesn't match %{attribute}","confirmation_period_expired":"needs to be confirmed within %{period}, please request a new one","content_type_blacklist_error":"You are not allowed to upload %{content_type} files","content_type_whitelist_error":"You are not allowed to upload %{content_type} files","empty":"can't be empty","equal_to":"must be equal to %{count}","even":"must be even","exclusion":"is reserved","expired":"has expired, please request a new one","extension_blacklist_error":"You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}","extension_whitelist_error":"You are not allowed to upload %{extension} files, allowed types: %{allowed_types}","greater_than":"must be greater than %{count}","greater_than_or_equal_to":"must be greater than or equal to %{count}","inclusion":"is not included in the list","invalid":"is invalid","invalid_vat":"is not a valid %{country_adjective} VAT number","less_than":"must be less than %{count}","less_than_or_equal_to":"must be less than or equal to %{count}","max_size_error":"File size should be less than %{max_size}","min_size_error":"File size should be greater than %{min_size}","mini_magick_processing_error":"Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}","model_invalid":"Validation failed: %{errors}","not_a_number":"is not a number","not_allowed":"It looks like there’s an error because your %{attribute} contains forbbiden words. Please contact [email protected] to update your username","not_an_integer":"must be an integer","not_found":"not found","not_locked":"was not locked","not_saved":{"one":"1 error prohibited this %{resource} from being saved:","other":"%{count} errors prohibited this %{resource} from being saved:"},"odd":"must be odd","other_than":"must be other than %{count}","present":"must be blank","required":"must exist","rmagick_processing_error":"Failed to manipulate with rmagick, maybe it is not an image?","size_too_big":"is too big (should be at most %{file_size}). Please compress the image","size_too_small":"is too small (should be at least %{file_size})","taken":"has already been taken","too_long":{"one":"is too long (maximum is 1 character)","other":"is too long (maximum is %{count} characters)"},"too_short":{"one":"is too short (minimum is 1 character)","other":"is too short (minimum is %{count} characters)"},"vies_down":"Unable to validate your VAT number: the VIES service is down. Please try again later.","wrong_length":{"one":"is the wrong length (should be 1 character)","other":"is the wrong length (should be %{count} characters)"},"wrong_size":"is the wrong size (should be %{file_size})"},"unacceptable_request":"A supported version is expected in the Accept header.\n","unavailable_session":"Session %{id} is no longer available in memory.\n\nIf you happen to run on a multi-process server (like Unicorn or Puma) the process\nthis request hit doesn't store %{id} in memory. Consider turning the number of\nprocesses/workers to one (1) or using a different server in development.\n"},"feature_designer":{"active":"Active","special_campaign_title":"Create here your special featured campaigns","title":"Featured Designers"}
16518,"file_conversion_request":{"not_unique":"You have already requested this format. Please wait until CGTrader team contacts you regarding this conversion.\n","not_unique_dedicated_to_designer":"You have already requested this format. Please wait until designer contacts you regarding this conversion.\n","success":"Thank you for using CGTrader! Our 3D design team will contact you shortly and inform about converted version of this 3D Model.\n","success_designer":"Thank you for using CGTrader! Your file conversion request was successfully sent to designer.\n"},"file_size_validator":{"errors":{"invalid_carrierwave":"A CarrierWave::Uploader::Base object was expected","invalid_key":":%{key} must be a nonnegative Integer","range_format":":in and :within must be a Range","range_unspecified":"Range unspecified. Specify the :within, :maximum, :minimum, or :is option."}},"file_type":{"default_error":"No path to file supplied."},"flash":{"actions":{"create":{"notice":"%{resource_name} was successfully created."},"destroy":{"alert":"%{resource_name} could not be destroyed.","notice":"%{resource_name} was successfully destroyed."},"update":{"notice":"%{resource_name} was successfully updated."}}},"footer":{"bottom-row":{"privacy":"Privacy","terms-link":"Terms \u0026 Conditions"},"links":{"buyLinks":[{"text":"Buy 3D Models","link":"/3d-models"},{"text":"For Business","link":"/discover/business-account"},{"text":"3D Datasets","link":"/discover/datasets"},{"text":"Hire a 3D Designer","link":"/3d-modeling-jobs"},{"text":"Free 3D Models","link":"/free-3d-models"},{"text":"Free 3D Print Models","link":"/free-3d-print-models"}],"communityLinks":[{"text":"Community","link":""},{"text":"Top designers","link":"/designers"},{"text":"Challenges","link":"/challenges"},{"text":"Forum","link":"/forum"}],"companyLinks":[{"text":"Company","link":"/discover/about-us"},{"text":"Blog","link":"/blog"},{"text":"Careers","link":"/pages/careers"},{"text":"Help Center","link":"https://help.cgtrader.com"}],"sellLinks":[{"text":"Sell 3D Models","link":"/discover/sell-3d-models"},{"text":"Grow your sales","link":"/discover/sell-more"},{"text":"3D Market Insights","link":"/discover/3d-market-trends"},{"text":"Top 3D Model Searches","link":"/discover/3d-models-top-searches"},{"text":"Top 3D Print Searches","link":"/discover/3d-print-models-top-searches"}]},"subscribe":{"button-active":"Subscribe","button-disabled":"Subscribed","header":"Subscribe to our news:","placeholder":"Enter your email","terms-link":"Terms of Use","text":"By subscribing you confirm that you have read and accept our"}},"forum":{"board":{"1":{"meta_description":"This is where thousands of 3D designers come to share their thoughts on a variety of topics. Introduce yourself, post your thoughts about the CG industry, discuss software, design news and other ideas. The General Discussion Board is where you can find friends, get tips or simply relax in our welcoming community."},"2":{"meta_description":"Spread great knowledge you found on the web! This is the place where you can share the best tutorials and tricks with others in the CGTrader community. Come here and learn from the best! And do not forget that we also have a dedicated tutorial page on our site, where users can post their own tutorials."},"3":{"meta_description":"Have a question? Ask anything about the CG industry here, or help others find out what you already know. Get to know everything about payments, purchases, refunds and other questions about the marketplace. Don’t be shy – the best and most frequent questions and answers might find their way to our FAQ."},"4":{"meta_description":"This is the most important Board for us. Tell us about your experience on CGTrader. Give feedback about our site and practices. Share your ideas on how we could improve in the future. We are the most designer-oriented 3D marketplace on the web and, with help from our users, we intend to stay that way."},"5":{"meta_description":"We know some projects are not for sale, and we realize you need a place to show your work. This is our haven for artists and show-offs. Got that new high-poly model? Show everyone that slick PBR-textured 3D spaceship. This is where you can get inspired, discuss, give feedback and provide constructive criticism."},"6":{"meta_description":"All things marketplace. Find out the dos and don’ts every new buyer or seller should know. Share your ideas and thoughts about the demand and use of stock 3D models. Tell us if you can’t find something on the marketplace. Talk about the industry, pricing practices, start selling or buying models."},"7":{"meta_description":"Everything 3D printing. Talk about your favorite 3D hardware, materials, software and modeling practices. Share your 3D print models and printing results, talk about service providers or news coming out of the 3D printing industry."},"8":{"meta_description":"Feeling ready for a challenge? Enter our friendly competitions to win cash prizes, software licenses and other great stuff. Learn by competing with the best designers in our themed challenges and awards – there’s something for everyone! Discuss ideas and entries, share your insights about our competitions here."}},"index":{"meta_description":"Hop on our forums and engage a friendly and supportive designer community. This is the place where you can introduce yourself, read design news, ask questions about 3D modeling and 3D printing, showcase your work, give feedback or just have plain old fun chatting with the most interesting people in the design world!"}},"general":{"description":"CGTrader is an online marketplace where 3D artists and designers can buy, sell or share 3D models. It was founded and is being developed by a team of 3D designers and engineers. Our goal is to democratize 3D stock market and change the way 
16518we sell 3D models online. By letting authors earn maximum royalties, we strive to increase their motivation and raise the quality of 3D models.","licenses":{"custom":"applies to this model.","editorial":"The intellectual property depicted in this model is not endorsed by or affiliated with the original intellectual property right holders and must be used under the","general":"applies to this model."},"menu":{"admin":"Admin","dashboard":"Dashboard","inbox":"Inbox","my_feed":"My Feed","my_purchases":"My Purchases","sign_in":"Sign In","sign_out":"Sign Out","upload_model":"Upload 3D Model"},"messages":{"server_not_responding":"Server not responding. Please try again later"},"navigation":{"buy_3d_models":"Buy 3D Models","sell_3d_models":"Sell 3D Models"},"search_placeholder":"Search over 2 million models","site-search__field":"Search %{model_count} 3D models","site_title":"3D Model Store","site_title_postfix":" | CGTrader","update":{"failed":"Failed to update","success":"Successfully updated"}},"generator":{"assets":{"errors":{"file_too_large":"This model's files exceed the %{limit}MB generator limit.","no_compatible_file":"This model has no GLB, glTF, FBX, OBJ or STL file that can be used in the generator."}},"landing_pages":{"three_d_model":{"defaults":{"closing_cta":{"heading":"Make AI Realize Your Dreams","subtitle":"Turn a prompt or a reference image into a production-ready 3D model, then refine, retexture and export it in the format you need."},"faq":{"heading":"Frequently Asked Questions","items":{"commercial":{"answer":"Commercial use of your 3D %{object} model is available on a paid CGTrader AI plan. Please review the CGTrader Terms and Conditions for the licence terms that apply to AI-generated models.","question":"Can I use the generated %{object} model for commercial purposes?"},"formats":{"answer":"The 3D models of %{object} are available in GLB, FBX, OBJ, STL or USDZ format.","question":"In which file format can I get my %{object} model?"},"no_skills":{"answer":"No knowledge of 3D modeling is required. All modeling is done through the %{object} model generator. The system handles the geometry, proportions, and structure when you input your desired prompt or upload an image. Even beginners can create a comprehensive 3D %{object} model in minutes.","question":"Does using a %{object} model generator need any knowledge of 3D modeling?"},"what_is":{"answer":"The CGTrader 3D %{object} Model generator, an AI-based tool, creates a three-dimensional %{object} model from written instructions or reference images. The CGTrader %{object} Model Generator looks at your idea and creates a downloadable model for design, prototyping, and visualization. Our paid subscriptions offer higher monthly limits.","question":"What is a 3D %{object} Model generator?"},"why_cgtrader":{"answer":"Users can trust CGTrader to provide a smooth experience, fast processing times, and high-quality 3D outputs. This tool is suitable for both professionals and beginners who are looking for a trustworthy solution to make 3D %{object} models.","question":"Why use the AI %{object} generator from CGTrader?"}}},"h1":"AI %{object} 3D model generator","hero_alt":"AI %{object} 3D model generator","hero_height":617,"hero_width":1080,"how_to":{"heading":"Generate %{object} From Image or Text Prompts","paragraphs":["It is very easy to design a three-dimensional %{object} model. In the left sidebar, on the top-left, click the button to upload your image of the %{object} to construct a 3D model. Users can also enter a prompt in the \"Describe what you want to create\" field at the bottom. CGTrader AI refines your prompt for you, so a short description is enough to get started.","The right-hand sidebar gives you good control of your 3D %{object} model. Choose settings like the mesh type, material, prompt guidance, and file format. You can also use texturing to customize your %{object} model. This means that you can choose between a textured 3D %{object} model or a texture-free one. After entering your text instructions or image, click \"Generate\". The tool will process your input in a few minutes, depending on system load, material, mesh type, shapes, seeding values, and quality."],"steps":["Write your text description and/or attach a reference image to improve the final accuracy of your 3D %{object} model.","Select one of our presets, or pick an AI Engine, add a custom polygon count, and select the material type for your 3D %{object} model.","Hit Generate to create a 3D %{object} model from your prompt and/or reference image.","Download your created 3D %{object} model in different formats (GLB, FBX, OBJ, STL or USDZ)."]},"intro":"Are you looking for a simple and quick way to create a high-quality 3D %{object} model in minutes? Our advanced 3D %{object} model generator, CGTrader AI, can turn written commands or reference images into fully working models. It runs on production 3D generation engines including Meshy, Hunyuan and Tripo3D. This helps with the quick modeling of 3D models for both design beginners and experts for visualization, experimentation, and prototyping. It supports both text-to-3d and image-to-3d, and is compatible with 3D printers. This means that you can upload an image in a JPG or PNG format. You can also write a description in the form of text. CGTrader AI takes care of the steps for you.","meta_description":"AI %{object} 3D Model Generator by CGTrader ☑️ Generate detailed %{object} 3D models from text or images. Start creating free now.","primary_cta":"Start generating for free","title":"AI %{object}
16518 3d model generator (free) | CGTrader"}}},"rules":{"errors":{"front_view_required":"Front view image is required."}},"runs":{"errors":{"cannot_delete_active":"You can't delete a generation that is still in progress. Wait for it to finish, then delete it.","concurrency_cap_reached":"You have reached the maximum number of concurrent generations for your plan (%{cap}). Wait for a generation to finish before starting another.","generation_not_entitled":"Generating is not available on your current plan. Start a free trial or subscribe to start generating.","marketplace_input_must_stay_private":"This generation used a model you purchased and must stay private.","not_riggable":"Tripo pre-rig check determined this model cannot be rigged for animation.","nsfw_must_stay_private":"This generation is flagged as NSFW and must stay private."}}},"helpers":{"label":{"address":{"first_name":"First Name","last_name":"Last Name","state":"State / Province","street_address":"Street Address","street_address_second":"Street Address Line 2","vatin":"VAT number","zip_code":"Postal / Zip Code"},"print":{"printer_type_id":"Name of the printer"},"printer_page":{"image":"Image (1800x500px)","listing_image":"Printer's image","name":"Printer name"}},"select":{"prompt":"Please select"},"submit":{"create":"Create %{model}","submit":"Save %{model}","update":"Update %{model}"}},"home":{"index":{"3D-projects":{"info-section__cta":"Learn more","info-section__description":"Where people with exciting 3D Projects meet talented 3D designers","info-section__heading_html":"\u003cstrong\u003e3D Projects\u003c/strong\u003e Freelance Hub"},"categories-conatainer":{"browse_all":"Browse all models","browse_all_tooltip":"Browse all 3D models","discounts":"Discounts"},"cgdream":{"cta":"Get started for Free!","heading_html":"\u003cstrong\u003eExplore CGDream,\u003c/strong\u003e\u003c/br\u003e where AI meets 3D models.","text_html":"\u003cstrong\u003eCGDream\u003c/strong\u003e is a cutting-edge application that leverages the power of generative AI and 3D models to produce stunning 2D images. \u003cstrong\u003e Your creative journey starts now.\u003c/strong\u003e"},"community":{"info-section__cta":"Join the community","info-section__heading_html":"Join CGTrader \u003cstrong\u003eCommunity\u003c/strong\u003e","info-section__icon-list_1":{"description":"Showcase your best renders, get feedback and recognition","heading":"Galleries"},"info-section__icon-list_2":{"description":"Learn new skills and teach others","heading":"Tutorials"},"info-section__icon-list_3":{"description":"Find tutorials and discuss other 3D related matters","heading":"Forum"}},"covid":{"timer-strip__item":"Free 3D Printable Resources for COVID-19 Protection","timer-strip__item--button":"Browse 3D Models"},"enterprise":{"info-section__cta":"Learn more","info-section__heading_html":"3D Services for the \u003cstrong\u003eEnterprise\u003c/strong\u003e","info-section__icon-list_1":"We can do the AR implementation for you","info-section__icon-list_2":"More sales conversions, less product returns","info-section__icon-list_3":"Quality ensured with stringent QA"},"header":"3D models for VR / AR, 3D printing and computer graphics","marketplace":{"info-section__description":"Choose from %{models_count} 3D models for applications such as gaming, AR/VR, advertising, animation, movies and 3D printing","info-section__heading_html":"3D Model \u003cstrong\u003eMarketplace\u003c/strong\u003e","info-section__image-list_1":{"description":"Buy professional 3D Models for your project","heading":"3D Models"},"info-section__image-list_2":{"description":"Low-poly PBR 3D Models ready for video games, VR \u0026 AR real-time applications","heading":"Low-poly PBR 3D Models"},"info-section__image-list_3":{"description":"Find 3D Print Designs for your 3D Printer","heading":"3D Printing Models"},"view_all":"View All"},"search_dropdown":{"ai_cards":{"image_to_3d":{"description":"Turn an image into a 3D model","title":"Image to 3D"},"text_to_3d":{"description":"Describe your idea to create a 3D model","title":"Text to 3D"}},"ai_section_title":"Create with AI","aria_label":"Search suggestions","columns":{"cg":{"search_label":"Search in 3D Models","title":"3D Models"},"print":{"search_label":"Search in 3D Print","title":"3D Print Models"}}},"sell":{"info-section__heading_html":"\u003cstrong\u003eSell\u003c/strong\u003e your 3D Models and Find Freelance Projects","info-section__icon-list_1":{"de
16518scription":"Sell your 3D models and earn up to 85% royalties","link":"Sell 3D Models"},"info-section__icon-list_2":{"description":"Work with enterprise clients","link":"Get Qualified"},"info-section__icon-list_3":{"description":"Outsource your skills \u0026 get up to 95% royalties","link":"Browse 3D Modeling Jobs"},"info-section__icon-list_4":{"description":"Build your portfolio and be visible","link":"Browse 3D Designers"}},"seo":{"description":"Explore 2M+ 3D models online for print, game, VR/AR \u0026 design projects. Download, customize, or hire 3D experts - all on CGTrader.","title":"CGTrader - 3D Models And 3D Printing Marketplace"}}},"homepage":{"budget_counter":{"range_0_10":"Under $10","range_10_20":"$10 - $20","range_20_15000":"More than $20"},"quotes":{"quote_0":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Lower commissions taken from the sellers means better prices for the buyers. Check out and compare the prices at CGTrader and the other shops."},"quote_1":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"The most striking advantage of CGTrader is flat-fee activation system which gives us high returns. You are NOT taking a commission percentage of our sales. After the sale, we receive the 100% of the price. Only if we want to continue selling that product, we may re-activate it again by paying $5. Elegant solution."},"quote_10":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"The flat fee activation system is ingenious. I was wondering why no one had thought of this before. After the sale, pay a small fee to activate a product and that's it. The seller is in full control."},"quote_11":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"There are two things I like the most about CGTrader. First, there is no hidden meaning to anything here. You don't need to be careful with CGTrader. Second, this store is By the Artists and For the Artists. They understand you better. Something is bothering you here?... let them know and they will make sure that you feel comfortable here."},"quote_12":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"Flat-fee activation system is the best! (at least I haven't come across anything better than this). Its very realistic and fair to the sellers. They don't care how much profit you make, just pay the fixed rent. BUT if you don't make any sales, you don't have to pay the rent either. Fair CG trade is here!"},"quote_13":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"CGTrader attracts customers even from developing countries, because prices are cheaper than on other stores. At the same time authors can earn more, because there is no such thing as  high commission rates. It is a right way to value the work of 3D artists."},"quote_14":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"Intuitive interface of the publisher and innovative tools like tag recommendation and flexible description forms makes the publishing process easy and fast."},"quote_15":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"CGTrader’s approach of selling 3D is different and have nothing in common with other stores I used to sell. Pay a low fee of 5$ after the sale is a great idea and allow me to offer lowest prices here. Customer and artist are benefited with this way of working."},"quote_16":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"The upload system is easy and clear. It’s good to know that behind the project are people with years of experience in computer graphic matters. Anytime I had a problem, CGTrader team fixed it quickly."},"quote_17":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"CGTrader allows me to link my profile with my personal webpage. Customers can contact me, if they are not certain about a characteristic of an item they want to buy or just want a modification of it. It’s like owning a personal 3D store in a big marketplace!"},"quote_2":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"We have enjoyed a great and responsive support from CGTrader team. The staff kindly offered to do the publishing of all the models for us, which is a great help for professional sellers."},"quote_3":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"On this platform we can offer much better prices to our customers. For example all our products are priced 20% lower than anywhere else and we still get more profits compared to other stores!"},"quote_4":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"This is the BEST system to date. All the other online 3D stores take a % from our profits. This was THE feature that caught our interest in CGTrader from the beginning. Customers, buying on CGTrader, know that the most part of their money go to the author, who in the end will make more and better models if their profits improve."},"quote_5":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"CGTrader is the online 3D store which made the fastest growing sales and profit for us. The flat-fee activation system is the best you can find, as it rewards content creators instead of middlemen. "},"quote_6":{"author":{"name":"Ričardas Marozas","username":"Pumper"},"text":"When I received $160 to my Paypal account, did not know what's going on as it never happened before but then I thought that it might be a CGTrader sale and I was right. Sold the same pack on Turbosquid last month for $189 but after 40% earning rate and 30% tax, all I got was $52.92 - pennies compared to $160 from CGTrader. Plus the customer payed $29 less - now that's a proper business."},"quote_7":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"At first I was skeptical about such a good deal for sellers which is not the norm for major online 3D stores. When I learnt about the flat-fee activation system, I was impressed by such a creative way of selling digital assets. Sell, then activate by paying a $5 fee. It all make sense to me. I uploaded a few to try out and truly, when I got my first sale, it was 100% earnings!"},"quote_8":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"I was so used to many others sites that charge between 30% - 60% commission until I discovered CGTrader. As a seller, I definitely like the high percentage of earning returns."},"quote_9":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"The seller experience is fantastic. Product submission is a breeze. And yes, the system allowed me to save a draft of my product submission every stage of the way."}},"subscription_confirmation":{"body":{"text":"Your notification preference is registered.\nYou will c
16518ontinue to receive notifications emails from our team.\n"},"head":{"text":"Thank you!"}}},"hyperwallet":{"failed_payment_agreement":"Something unexpected happened","success_notification":"Payment Agreement has been updated successfully.","success_payment_agreement":"Payment Agreement has been submitted successfully."},"invoices":{"reverse_charging":"Reverse Charging","seller_info":"UAB \"CGTRADER\"\nAntakalnio g. 17, LT-10312 Vilnius, Lithuania\nCompany code: 302935696\n"},"item-search-filters":{"3d-models-url":"3d-models","3d-print-models-url":"3d-print-models","dedicated-search-page-url":{"animated":"/animated-","free":"/free-","low_poly":"/low-poly-","pbr":"/pbr-","rigged":"/rigged-"}},"item_use_case":{"not_valid":"Invalid file","pundit":{"not_authorized":"You are not authorized to perform this action."},"title":"Use case for %{item_name}"},"items":{"already_purchased":"You have already purchased this item.","filters":{"attributes":"Select attribute","free":"Free","max_value":"$500+","min_value":"$2","reset":"Reset filters","select":"Select"},"footer":{"next":"Next","per_page":"Per page:","previous":"Previous"},"geometry_type_forbidden":"Geometry type forbidden!","last_card":{"button_text":"Request a custom 3D model","text":"Couldn't find what you're looking for?"},"migrations":{"errors":{"invalid_owner":"New owner is not able to take items.","unpublished_items":"All items must be published."}},"new_price_applied":"New prices have been successfully applied to all items.","new_price_applied_to_selected_items":"New prices have been successfully applied to selected items.","new_prices_to_filtered_models":"New prices have been successfully applied to all filtered items.","order_processed":"Order processed.","order_processing":"Order processing.","something_went_wrong":"Something went wrong.","title_error":"Title should not contain non-latin symbols","uniq_user":"has already submitted a use case for this item","upload_error":"Upload error.","validate_license_for_zero_price":"is not valid for free item","validate_minimum_price":"Price offers should not be below the minimum of 50% of the original price","verify_printable_formats_error":"Please upload at least one .stl, .obj or .wrl file.","you_can_not_delete_models":"You can not delete models"},"jobs":{"about":{"hire_designers_page":{"hero":{"description":"3D Projects makes it easy to hire quality freelance 3D artists and solve all your modeling challenges.","heading_html":"Meet the best freelance platform for your \u003cstrong\u003e3D modeling jobs\u003c/strong\u003e"},"sections":{"call_to_action":{"description":"Post your Project for free and see how easy it is to solve your next modeling challenge with 3D Projects.","title":"Hire the best 3D talent today!"},"freelancer_selection":{"title":"Make your next 3D Project a success","widest_selection":{"description":"Don't settle for anything less. Home to 40,000 freelance 3D designers, CGTrader is the largest platform to help you connect with skilled specialists: modelers, animators, riggers, 3D print pros and everything in between.","title":"Widest selection of 3D freelancers"}},"how_it_works":{"steps":{"describe":{"description":"Tell us what you need. Use our simple form, add images and publish your freelance offer in minutes.","title":"Describe your Project"},"pay":{"description":"Securely prepay the budget to CGTrader and let your freelancer know you're ready to start. Chat, share attachments and view Project progress conveniently in your dedicated workspace. Release the payment when you're happy with the end result.","title":"Pay for the result"},"select":{"bullets":["Receive quotes from qualified designers straight to your inbox","Chat with designers in real time","Browse their previous Projects","You can also search, filter and invite freelancers according to 3D skills, ratings and your software or format preferences"],"title":"Select the best freelancer"}},"title":"Get your job done in 3 simple steps"},"payment_quality":{"description":"CGTrader ensures that the freelance services will meet your requirements and be delivered on time. Our QA Team is available whenever you need them.","title":"Quality guaranteed"},"secure_payments":{"description":"Our escrow service means your funds are protected and you only pay when you're happy with the final results.","title":"Secure payments"}
16518,"workspace_convenience":{"description":"Forget the frustration of jumping between tabs and different tools. With the dedicated workspace, you can easily manage everything in one place: multiple projects, invoices, conversations, file sharing and more.","title":"Single screen convenience"}},"shared":{"post_project_cta":"Post project"}},"meta":{"description":{"find_projects":"Browse and find 3D modeling jobs and work from the comfort of your home. New freelance projects daily. Secure payments. Get hired today.\n","hire_designers":"Discover the largest platform to find freelance designers and artists for your next 3D Project. Post a 3D modeling job for free today.\n"},"title":{"find_projects":"Jobs For Freelance 3D Artists","hire_designers":"Hire Freelancers For 3D Modeling Jobs"}},"navigation":{"find_jobs_cta":"Find 3D jobs","hire_freelancers_cta":"Hire 3D freelancers","how_it_works":"How it works","why_projects":"Why 3D Projects"}},"agreed_to_terms_of_use":"You must agree with 3D Projects Terms \u0026 Conditions to continue.","budget_error_message":"is too high","check_designer_uniqueness_error":"Designer can only be invited once","check_ownership_error":"Inviter does not own job","check_status_error":"Designers can be invited only to approved jobs.","create_message_dedicated":"Thank you for starting a 3D Project. %{username} will receive a personal email and a notification about this Project.\n","create_message_private":"Thank you for starting a 3D Project. Designer(s) will receive a personal email and a notification about this Project.\n","create_message_public":"Thank you for creating your Project. You should now be able to see it in our 3D Projects listing page.","deadline_error_message":"Deadline must be in future","discount_code_error":"Sorry, discount code does not exist or is expired.","email_subject":"3D Project opportunity: %{subject}","error_check_application_uniqueness":"You can only apply once","error_check_applier":"You cannot apply to your own 3D Project","error_check_job_availability":"User not authorized to apply","error_check_job_status":"Project applications are on hold until our new moderation process is completed, typically within 3 business days. Your understanding is appreciated!","modeling_request_failed":"Modeling Request failed!","offer_has_been_sent":"Thank you, your offer has been sent!","offer_is_closed":"Offer is closed.","order_status_messages_for_admin":{"paid":"Paid","pending":"Pending","refunded":"Refunded"},"payment_successful":"Payment successfully paid.","payment_under_review":"Payment is under review.","private_job_message":{"initiated_by_designer_html":"Hi %{buyer_username}, \u003cbr\u003e\u003cbr\u003e I invited you to start a 3D Project. Click \u003ca href=\"%{job_template_link}\"\u003ehere\u003c/a\u003e to review the details of the Project and make any adjustments. \u003cbr\u003e\u003cbr\u003e I am looking forward to hearing from you. \u003cbr\u003e\u003cbr\u003e Best, %{designer_username}","private_html":"Hi %{designer_username}, \u003cbr\u003e\u003cbr\u003e I invited you to work on a 3D Project. See my \u003ca href=\"%{application_link}\"\u003erequest\u003c/a\u003e for more details. \u003cbr\u003e\u003cbr\u003e I look forward to hearing from you soon. \u003cbr\u003e\u003cbr\u003e Best, %{buyer_username}"},"share_job_template_message":"Your Project invitation was successfully sent.","share_with_yourself_error":"Sharing Hire Me templates with yourself is not allowed","status_messages":{"approved":"Waiting for applicants","expired":"Expired","finished":"Finished","prepaid":"Job in progress","refunded":"Refunded","started":"Started","unapproved":"Waiting for approval"},"status_messages_for_admin":{"approved":"Approved","canceled":"Canceled","expired":"Expired","finished":"Finished","refunded":"Refunded","started":"Started","unapproved":"Unapproved"},"status_messages_for_client":{"approved":"Waiting for your selection","canceled":"Canceled","confirmed_by_designer":"Confirmed by designer","expired":"Expired","finished":"Finished","in_progress":"In progress","started":"In progress","unapproved":"Waiting for approval","waiting_for_their_confirmation":"Waiting for designer's confirmation","waiting_for_your_approval":"Waiting for your approval","waiting_for_your_confirmation":"Waiting for your confirmation","waiting_for_your_review":"Waiting for your review"},"status_messages_for_designer":{"approved":"Not yet selected","canceled":"Canceled","confirmed_by_designer":"Confirmed","declined":"Declined","expired":"Expired","finished":"Finished","not_selected":"Not selected","started":"Running","unapproved":"Waiting for approval","waiting_for_answer":"Waiting for answer","waiting_for_payment":"Waiting for client's payment","waiting_for_their_review":"Waiting for client's review","waiting_for_your_confirmation":"Waiting for your confirmation","waiting_for_your_review":"Waiting for your review"},"system_messages":{"buyer_cancels_job":"Project was canceled and moved to %{archive_url} tab.","buyer_edits_hire_me_template":"Project details were updated by client.","buyer_edits_job_details":"Project details were updated.","buyer_marks_job_as_finished":"Project was marked as finished and moved to %{archive_url} tab.","buyer_marks_job_as_finished_with_resell":"Client marked the Project as finished and allowed you to sell the results on CGTrader. Make sure to \u003cb\u003e\u003ca href=\"/profile/upload/model\"\u003eupload\u003c/a\u003e\u003c/b\u003e the created model!","buyer_negotiates":"Client negotiated your price offer.","buyer_pays_for_the_job":"Project was prepaid and moved to %{in_progress_url} tab.","designer_accepts_changes":"Designer reviewed updated details.","designer_accepts_offer":"Designer accepted y
16518our offer.","designer_edits_offer":"Designer edited his offer.","designer_negotiates":"Designer negotiated your offer.","designer_withdraws_offer":"Designer withdrawn his offer.","job_expires":"Project expired and was moved to %{archive_url} tab."},"template_error":"No jobs were created!","tooltip":{"approx_revenue_html":"Your approximate revenue is counted after calculating approximate payment \u003cbr/\u003e processing fees and 5% royalties received by CGTrader. Please note that your \u003cbr/\u003e final revenue may vary a little bit due to payment processing fee calculation.\n","confirm_t_a_c_html":"By clicking this button you confirm that you agree with 3D Jobs \u003ca target='_blank' href=\"https://www.cgtrader.com/pages/3d-jobs-agreement\"\u003eTerms \u0026 Conditions\u003c/a\u003e\n","ip_rights_html":"By checking this checkbox you agree that you renounce exclusive rights to content \u003cbr/\u003e created with this 3D job offer, furthermore allow designer to retain the Intellectual \u003cbr/\u003e property rights and publish job results on the CGTrader marketplace.\n","ip_rights_renounced_html":"Buyer renounced exclusive rights to content created with this 3D job offer, \u003cbr /\u003e furthermore you are allowed to retain the Intellectual property rights and \u003cbr /\u003e publish job results on the marketplace.\n","level":"Level shows designer's reputation on CGTrader. It defines his input into 3D modelling community, number of models uploaded, feedback from buyers and many other factors.","private_type":"Only invited designers will see and be able to apply for this job.","public_type_html":"Your job will be posted in public 3D jobs listing page. \u003cbr/\u003e All community designers will be allowed to apply for this job.\n","quality":"Quality is average rating of designer's models on CGTrader. This rating should help you to compare one designer against another.","vat_html":"Value Added Tax (VAT), also known as Sales tax or Goods \u003cbr/\u003e and services tax (GST), is a tax applied at the point of sale \u003cbr/\u003e to the end buyer and paid to the government of the buyer’s country. "},"user_not_exists":"User with that username does not exist.","want_that_job":"I want this job!"},"kodama":{"invalid_request":"Invalid request!"},"landing":{"consent_not_saved":"Consent was not saved","failure_notification":"All required API access request fields should be valid.","success_notification":"Thank you for your request. We will contact you shortly."},"layout":{"chunk_load_notice":"Something went wrong while loading this page. Please refresh.","cookies_policy":{"accept":"Accept","learn_more":"Learn more","text":"Our website uses cookies to collect statistical visitor data and track interaction with direct marketing communication / improve our website and improve your browsing experience. Please see our Cookie Notice for more information about cookies, data they collect, who may access them, and your rights."}},"licenses":{"custom_html":"\u003cp\u003eContent published with the Custom License label shall be deemed applicable if and only if the Seller provides additional license terms on the content.\u003c/p\u003e\n\n%{content}\n","editorial_html":"\u003cp\u003eThis product may be used in an editorial manner, but other uses such as commercial, promotional, advertising or merchandising use may require legal clearances from intellectual property owners.\u003c/p\u003e\n\u003cp\u003eTERMS OF LICENSE AGREEMENT BETWEEN THE SELLER AND THE BUYER\u003c/p\u003e\n\n\u003cp\u003e1.  General Terms of Licensing\u003c/p\u003e\n\n\u003cp\u003e1.1.  Following the payment of any applicable license fee for Product, the Buyer acquires a license in accordance with the terms and conditions of this section. Any license rights relating to the Product are contingent upon the transfer of money (except for the free Products) from the Buyer to the Seller. All license rights terminate immediately and without notice if a sale is reversed for any reason.\u003c/p\u003e\n\n\u003cp\u003e1.2.  Upon uploading the Product to the Site, the Seller shall indicate the terms of license, upon which a respective Product is licensed to the Buyer. If the Seller does not indicate any licensing conditions in his posting, terms of Royalty free license (see section 2 “Royalty Free License”) shall be applicable. Royalty free license shall be applicable to Products that are made available for free download but the Seller’s, unless it is expressly indicated otherwise.\u003c/p\u003e\n\n\u003cp\u003e 1.3.  Certain Products with third party copyrighted or trademarked images, logos, brand names, etc., may require additional licensing, rights, permissions, releases, or clearance for non-editorial use. It is the sole responsibility of the Buyer and their legal advisor to determine, before purchasing, downloading or using any Product, whether additional licensing, rights, permissions, releases, or clearance are necessary for the intended use of Product. It is the Buyer’s sole responsibility to obtain any licensing, rights, permissions, or clearance. Buyers should understand additional licensing, rights, permissions, releases, or clearance may be relevant for Product, if the intended use involves religious groups or affiliations, pharmaceuticals, health care, tobacco, liquor, adult entertainment, personal hygiene, birth control, or any other area that could be considered sensitive, offensive, or immoral.\u003c/p\u003e\n\n\u003cp\u003e1.4.  All licenses of the Products are non-transferrable. Buyers do not own any Product and are only licensed to use it in accordance with terms and conditions of the applicable license. Unless explicitly provided for in custom license terms set forth in the special area “Custom license terms” provided by CG Trader in the Product description section of the Site, the Seller retains copyright in Products purchased or downloaded by any Buyer. \u003c/p\u003e\n\n\u003cp\u003e1.5.  The license to use the Product is non-exclusive, non-transferable and is granted only to you as the original Buyer.\u003c/p\u003e\n\n\u003cp\u003e1.6.  Any Product, returned to CG Trader and (or) the Seller in accordance with applicable law for whatever reason must be destroyed by the Buyer immediately. The license to use any Product is revoked at the time Product is returned. Product obtained by means of theft or fraudulent activity of any kind is not granted a license.\u003c/p\u003e\n\n\u003cp\u003e1.7.  By using the Site and (or) accepting this EULA the Seller agrees that any additional end user license agreements, licenses, custom licenses, or Seller requirements inserted into Seller Products in any area outside that explicitly provided by CG Trader for additional license terms are invalid, void ab initio, and without effect as they relate to those purchases made on the Site.\u003c/p\u003e\n\n\u003cp\u003e1.8.  The license grant contained in this EULA can be terminated using the guidelines below. CG Trader reserves the right to terminate this agreement at any time and without notice if any Content submitted violates the representations and warranties of paragraph three (3) below. This agreement may be terminated at any time by either party upon written notice via email or regular mail. Nothing in this agreement shall be construed to mean that a Seller may not submit or delete Content from the material submitted by such Seller as part of general maintenance of such Seller’s account.\u003c/p\u003e\n\n\u003cp\u003e3.  Editorial License\u003c/p\u003e\n\n\u003cp\u003e3.1.  In addition to the restrictions that are applicable to the Royalty free license, certain Products may be marked “editorial” or “editorial license” and has additional license restrictions. \u003c/p\u003e\n\n\u003cp\u003e3.2.  Buyer’s may only use Products marked “editorial” or “editorial license” for legitimate, editorial purposes on some issue of journalistic, editorial, cultural or otherwise newsworthy value.  Examples of editorial use/fair use may include illustrating issues of general interest, social commentary, reporting news, etc. Editorial uses include use of the Product in a news program, news-related website, or news-related video media.  The Buyer understands and agrees that such Products may contain material that is not released from its rights holders. \u003c/p\u003e\n","royalty_free_html":"\u003cp\u003e Royalty Free License allows you to use copyright material or intellectual property without the need to pay earnings or license fees for each use or per volume sold, or some time period of use or sales. Content published with this license may not be sold, given, or assigned to another person or entity in the form it is downloaded from the site but can be used in your projects multiple times after paying for it just once.\u003c/p\u003e\n\n\u003cp\u003eIV.  TERMS OF LICENSE AGREEMENT BETWEEN THE SELLER AND THE BUYER\u003c/p\u003e\n\n\u003cp\u003e1.  General Terms of Licensing\u003c/p\u003e\n\n\u003cp\u003e1.1.  Following the payment of any applicable license fee for Product, the Buyer acquires a license in accordance with the terms and conditions of this section. Any license rights relating to the Product are contingent upon the transfer of money (except for the free Products) from the Buyer to the Seller. All license rights terminate immediately and without notice if a sale is reversed for any reason.\u003c/p\u003e\n\n\u003cp\u003e1.2.  Upon uploading the Product to the Site, the Seller shall indicate the terms of license, upon which a respective Product is licensed to the Buyer. If the Seller does not indicate any licensing conditions in his posting, terms of Royalty free license (see section 2 “Royalty Free License”) shall be applicable. Royalty free license shall be applicable to Products that are made available for free download but the Seller’s, unless it is expressly indicated otherwise.\u003c/p\u003e\n\n\u003cp\u003e1.3.  Certain Products with third party copyrighted or trademarked images, logos, brand names, etc., may require additional licensing, rights, permissions, releases, or clearance for non-editorial use. It is the sole responsibility of the Buyer and their legal advisor to determine, before purchasing, downloading or using any Product, whether additional licensing, rights, permissions, releases, or clearance are necessary for the intended use of Product. It is the Buyer’s sole responsibility to obtain any licensing, rights, permissions, or clearance. Buyers should understand additional licensing, rights, permissions, releases, or clearance may be relevant for Product, if the intended use involves religious groups or affiliations, pharmaceuticals, health care, tobacco, liquor, adult entertainment, personal hygiene, birth control, or any other area that could be considered sensitive, offensive, or immoral.\u003c/p\u003e\n\n\u003cp\u003e1.4.  All licenses of the Products are non-transferrable. Buyers do not own any Product and are only licensed to use it in accordance with terms and conditions of the applicable license. Unless explicitly provided for in custom license terms set forth in the special area “Custom license terms” provided by CG Trader in the Product description section of the Site, the Seller retains copyright in Products purchased or downloaded by any Buyer. \u003c/p\u003e\n\n\u003cp\u003e1.5.  The license to use the Product is non-exclusive, non-transferable and is granted only to you as the original Buyer.\u003c/p\u003e\n\n\u003cp\u003e1.6.  Any Product, returned to CG Trader and (or) the Seller in accordance with applicable law for whatever reason must be destroyed by the Buyer immediately. The license to use any Product is revoked at the time Product is returned. Product obtained by means of theft or fraudulent activity of any kind is not granted a license.\u003c/p\u003e\n\n\u003cp\u003e1.7.  By using the Site and (or) accepting this EULA the Seller agrees that any additional end user license agreements, licenses, custom licenses, or Seller requirements inserted into Seller Products in any area outside that explicitly provided by CG Trader for additional license terms are invalid, void ab initio, and without effect as they relate to those purchases made on the Site.\u003c/p\u003e\n\n\u003cp\u003e1.8.  The license grant contained in this EULA can be terminated using the guidelines below. CG Trader reserves the right to terminate this agreement at any time and without notice if any Content submitted violates the representations and warranties of paragraph three (3) below. This agreement may be terminated at any time by either party upon written notice via email or regular mail. Nothing in this agreement shall be construed to mean that a Seller may not submit or delete Content from the material submitted by such Seller as part of general maintenance of such Seller’s account.\u003c/p\u003e\n\n\u003cp\u003e2.  Royalty Free License \u
16518003c/p\u003e\n\n\u003cp\u003e2.1.  Product may not be sold, given, or assigned to another person or entity in the form it is downloaded from the Site\u003c/p\u003e\n\n\u003cp\u003e\n  2.2.  The Buyer’s license to Product in this paragraph is strictly limited to Incorporated Product. Any use or republication, including sale or distribution of Product that is not Incorporated Product is strictly prohibited. For illustration, approved distribution or use of Product as Incorporated Product includes, but is not limited to:\n  (1) as rendered still images or moving images; resold as part of a feature film, broadcast, or stock photography;\u003cbr\u003e\n  (2) as purchased by a game’s creators as part of a game if the Product is contained inside a proprietary format and displays inside the game during play, but not for users to re-package as goods distributed or sold inside a virtual world;\u003cbr\u003e\n  (3) as Product published within a book, poster, t-shirt or other item;\u003cbr\u003e\n  (4) as part of a physical object such as a toy, doll, or model.\n\u003c/p\u003e\n\n\u003cp\u003e2.3.  If you use any Product in software products (such as video games, simulations, or VR-worlds) you must take all reasonable measures to prevent the end user from gaining access to the Product. Methods of safeguarding the Product include but are not limited to:\u003c/p\u003e\n\u003cp\u003e(1) using a proprietary disc format such as Xbox 360, Playstation 3, etc.;\u003c/p\u003e\n\u003cp\u003e(2) using a proprietary Product format;\u003c/p\u003e\n\u003cp\u003e(3) using a proprietary and/or password protected database or resource file that stores the Product data;\u003c/p\u003e\n\u003cp\u003e(4) encrypting the Product data.\u003c/p\u003e\n\n\u003cp\u003e2.4.  Without prejudice to subparagraphs 2.1-2.3 of this paragraph, the Seller grants to the Buyer who purchases license rights to Product and uses it solely as Incorporated Product a non-exclusive, worldwide, license in any medium now known or hereinafter invented to:\u003c/p\u003e\n\u003cp\u003e(1) reproduce, post, promote, license, sell, publicly perform, publicly display, digitally perform, or transmit for promotional and commercial purposes;\u003c/p\u003e\n\u003cp\u003e(2) use any trademarks, service marks or trade names incorporated in the Product in connection with Seller material; \u003c/p\u003e\n\u003cp\u003e(3) use the name and likeness of any individuals represented in the Product only in connection with Your material. \u003c/p\u003e\n\n\u003cp\u003e2.5.  Absent a written grant of rights greater than that contained in paragraph 2 “Royalty free license”, all other rights or sub-divisions of rights generally included in copyright are excluded from this license and remain the property of Seller.\u003c/p\u003e\n\n\u003cp\u003e2.6.  The resale or redistribution by the Buyer of any Product, obtained from the Site is expressly prohibited unless it is an Incorporated Product as licensed above.\u003c/p\u003e\n","summary":{"custom_html":"Content published with the %{link} label shall be deemed applicable if and only if the Seller provides additional license terms on the content.","editorial_html":"Content published with the %{link} label may only be used in an editorial manner and may not be used for any commercial, promotional, advertising or merchandising use.","royalty_free_html":"Content published with the %{link} label may not be sold, given, or assigned to another person or entity in the form it is downloaded from the site."}},"licenses_new":{"custom_html":"\u003cp\u003e Custom License allows uses of the product as specified by designer. Such additional license terms shall be applied and interpreted as additions and (or) exceptions to the conditions set by general licensing terms and Royalty Free License. The product published with the Custom License label shall be deemed applicable if and only if the Seller provides additional license terms on the content.\u003c/p\u003e\n\u003cp\u003eHere are additional conditions set by the Seller for model %{model_title}:\u003c/p\u003e\n%{content}\n\u003cp\u003eRefer to legally binding \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' target='_blank'\u003eGeneral Terms and Conditions\u003c/a\u003e to learn more about conditions regarding extracting product from end product created, which applies to all models under any license.\u003c/p\u003e\n","custom_no_ai_html":"\u003cp\u003e Custom License allows uses of the product as specified by designer. Such additional license terms shall be applied and interpreted as additions and (or) exceptions to the conditions set by general licensing terms and Royalty Free License. The product published with the Custom License label shall be deemed applicable if and only if the Seller provides additional license terms on the content.  Licenses marked with \"no AI\" tag do not permit Product use for machine learning or training of neural network models, including generative AI models.\u003c/p\u003e\n\u003cp\u003eHere are additional conditions set by the Seller for model %{model_title}:\u003c/p\u003e\n%{content}\n\u003cp\u003eRefer to legally binding \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' target='_blank'\u003eGeneral Terms and Conditions\u003c/a\u003e to learn more about conditions regarding extracting product from end product created, which applies to all models under any license.\u003c/p\u003e\n","editorial_no_ai_html":"\u003cp\u003eEditorial license gives a permission to use the product only in an editorial manner, relating to events that are newsworthy, or of public interest, and may not be used for any commercial, promotional, advertising or merchandising use. Licenses marked with \"no AI\" tag do not permit Product use for machine learning or training of neural network models, including generative AI models.\u003c/p\u003e\n\u003cp\u003eIn a few instances, you may otherwise have the rights to IP in content that is under Editorial license. For instance, you may be the advertising agency for a brand/IP owner or you may be the brand/IP owner itself purchasing user generated content. Given you have the rights clearance through other means, you may use the content under Editorial License commercially. Every user failing to comply with Editorial Use restrictions takes the responsibility to prove the ownership of IP rights.\u003c/p\u003e\n\u003cp\u003eFor users, who are not brand/IP owners or official affiliates, the restrictions of Editorial
16518-licensed content usage include, but are not limited to, the following cases:\u003c/p\u003e\n\u003cul\u003e\n  \u003cli\u003eProducts may not be used on any item/product created for resale such as, commercials, for-profit animations, video games, VR/AR applications, or physical products such as a merchandise or t-shirt.\u003c/li\u003e\n  \u003cli\u003eProducts may not be used as part of own product promotional materials, billboard, trade show or exhibit display.\u003c/li\u003e\n  \u003cli\u003eThe product may not be incorporated into a logo, trademark or service mark. As an example, you cannot use Editorial content to create a logo design.\u003c/li\u003e\n  \u003cli\u003eProducts may not be used in any insulting, abusive or otherwise unlawful manner.\u003c/li\u003e\n  \u003cli\u003eThe product may not be used for any commercial related purpose.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eRead more about \u003ca href='/pages/terms-and-conditions#editorial-license' target='_blank'\u003eEditorial License\u003c/a\u003e and product usage allowed by different licenses in our legally binding \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' target='_blank'\u003eGeneral Terms and Conditions\u003c/a\u003e.\u003c/p\u003e\n","editorial_royalty_free_html":"\u003cp\u003eRoyalty Free License allows you to use the product without the need to pay earnings or other license fees for multiple uses, per volume sold, or some time period of use or sales. Products published with this license may not be sold, given, or assigned to another person or entity in the form it is downloaded from the site, but can be used in your commercial projects multiple times after paying for it just once.\u003c/p\u003e\n\u003cp\u003eThis is, however, a non-exclusive license and the product remains the property of a seller for further distribution. Please, refer to legally binding \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' target='_blank'\u003eGeneral Terms and Conditions\u003c/a\u003e to learn more about \u003ca href=/pages/terms-and-conditions#royalty-free-license target='_blank'\u003eRoyalty Free License\u003c/a\u003e, other types of licenses and general rules applicable to all products.\u003c/p\u003e\n","royalty_free_html":"\u003cp\u003eRoyalty Free License allows you to use the product without the need to pay earnings or other license fees for multiple uses, per volume sold, or some time period of use or sales. You can use it as long as it is incorporated into the product and the 3rd party cannot retrieve it individually in either the digital or physical form. You cannot resell the model you bought in the digital or printed form, but can use it in your commercial projects multiple times after paying for it just once.\u003c/p\u003e\n\u003cp\u003eThis is, however, a non-exclusive license and the product remains the property of a seller for further distribution. Please, refer to legally binding \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' target='_blank'\u003eGeneral Terms and Conditions\u003c/a\u003e to learn more about \u003ca href=/pages/terms-and-conditions#royalty-free-license target='_blank'\u003eRoyalty Free License\u003c/a\u003e, other types of licenses and general rules applicable to all products.\u003c/p\u003e\n","royalty_free_no_ai_html":"\u003cp\u003eRoyalty Free License allows you to use the product without the need to pay earnings or other license fees for multiple uses, per volume sold, or some time period of use or sales. You can use it as long as it is incorporated into the product and the 3rd party cannot retrieve it individually in either the digital or physical form. You cannot resell the model you bought in the digital or printed form, but can use it in your commercial projects multiple times after paying for it just once. Licenses marked with \"no AI\" tag do not permit Product use for machine learning or training of neural network models, including generative AI models.\u003c/p\u003e\n\u003cp\u003eThis is, however, a non-exclusive license and the product remains the property of a seller for further distribution. Please, refer to legally binding \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' target='_blank'\u003eGeneral Terms and Conditions\u003c/a\u003e to learn more about \u
16518003ca href=/pages/terms-and-conditions#royalty-free-license target='_blank'\u003eRoyalty Free License No AI\u003c/a\u003e, other types of licenses and general rules applicable to all products.\u003c/p\u003e\n","summary":{"custom_html":"Content published with the %{link} label shall be deemed applicable if and only if the Seller provides additional license terms on the content.","editorial_html":"Content published with the %{link} label may only be used in an editorial manner and may not be used for any commercial, promotional, advertising or merchandising use.","editorial_royalty_free_html":"Content published with the %{link} label may not be sold, given, or assigned to another person or entity in the form it is downloaded from the site.","royalty_free_html":"Content published with the %{link} label may not be sold, given, or assigned to another person or entity in the form it is downloaded from the site."},"tooltips":{"custom_html":"Custom License allows uses of the product as specified by designer. Such additional license\u003cbr/\u003e terms shall be applied and interpreted as additions and (or) exceptions to the conditions set\u003cbr/\u003e by general licensing terms and Royalty Free License.\u003cbr/\u003e \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' rel='nofollow' target='_blank'\u003eRead more\u003c/a\u003e\n","custom_no_ai_html":"Custom License allows uses of the product as specified by designer. Such additional license\u003cbr/\u003e terms shall be applied and interpreted as additions and (or) exceptions to the conditions set\u003cbr/\u003e by general licensing terms and Royalty Free License.\u003cbr/\u003e \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' rel='nofollow' target='_blank'\u003eRead more\u003c/a\u003e\n","editorial_html":"The product can only be used in an editorial manner (educational, non-commercializable\u003cbr/\u003e personal project, in newsworthy and of public interest events) and may not be used for any\u003cbr/\u003e commercial, promotional, or merchandising purposes.\u003cbr/\u003e \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' rel='nofollow' target='_blank'\u003eRead more\u003c/a\u003e\n","editorial_no_ai_html":"The product can only be used in an editorial manner (educational, non-commercializable\u003cbr/\u003e personal project, in newsworthy and of public interest events) and may not be used for any\u003cbr/\u003e commercial, promotional, or merchandising purposes.\u003cbr/\u003e \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' rel='nofollow' target='_blank'\u003eRead more\u003c/a\u003e\n","editorial_royalty_free_html":"Products under Royalty Free License can be used without liability to pay any license fees for\u003cbr/\u003e multiple lifelong uses, or sales volume of final product after being paid for once. The product\u003cbr/\u003e may not be sold, given, or assigned to another person or entity in the form it is downloaded from the site.\u003cbr/\u003e \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' rel='nofollow' target='_blank'\u003eRead more\u003c/a\u003e\n","royalty_free_html":"Products under Royalty Free License can be used without liability to pay any license fees for\u003cbr/\u003e multiple lifelong uses, or sales volume of final product after being paid for once. You can use it as\u003cbr/\u003e long as it is incorporated into the product and the 3rd party cannot retrieve it\u003cbr/\u003e individually in either the digital or physical form.\u003cbr/\u003e You cannot resell the model you bought in the digital or printed form.\u003cbr/\u003e \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' rel='nofollow' target='_blank'\u003eRead more\u003c/a\u003e\n","royalty_free_no_ai_html":"Products under Royalty Free License can be used without liability to pay any license fees for\u003cbr/\u003e multiple lifelong uses, or sales volume of final product after being paid for once. You can use it as\u003cbr/\u003e long as it is incorporated into the product and the 3rd party cannot retrieve it\u003cbr/\u003e individually in either the digital or physical form.\u003cbr/\u003e You cannot resell the model you bought in the digital or printed form.\u003cbr/\u003e \u003ca href='/pages/terms-and-conditions#general-terms-of-licensing' rel='nofollow' target='_blank'\u003eRead more\u003c/a\u003e\n"}},"likes":{"like_failure":"You can like up to 20 models or galleries per day. Please come back tomorrow to continue choosing your favorites."},"mailers":{"static_message_body":"Please log in to your account to see the full message."},"meta":{"description":{"boards#index":"Connect with fellow 3D designers on CGTrader forum. Share your 3D models, design knowledge and improve your assets in forum dedicated for 3D experts.\n","boards#show":"Visit %{board_title} board on CGTrader forum. Share your design knowledge and improve your assets in forum dedicated for 3D experts.\n","challenges#index":"Join CGTrader 3D model and 3D print challenges. Take part in creative 3D contests, showcase your skills, win prizes, and get recognition in the global 3D community.","events#index":"Meet us at these industry events near you. Here's your opportunity to discu
16518ss your 3D modelling and AR/VR needs with our seasoned pros.\n","events#show":"%{event_description}\n","faq#show":"Find answers to the most frequently asked questions on CGTrader‘s site. From buying to selling 3D models, information about CGTrader 3D community and more.\n","faq#show_page":"Find answers to the most frequently asked questions on CGTrader‘s site. From buying to selling 3D models, information about CGTrader 3D community and more. %{page_number}\n","galleries#author":"Browse through 3D art portfolio by %{username}, see digital modeling creations the designer made, don‘t hesitate to share your thoughts about author‘s artwork.\n","galleries#category":"Browse through 3D art in %{category_name} category, post and showcase your works, enrich your portfolio, get valuable feedback and inspire fellow 3D designers.\n","galleries#index":"Browse through some of the best 3D art, post and showcase your design works, enrich your portfolio, get valuable feedback and inspire fellow 3D designers.\n","galleries#show":"Take a look at %{project_name} - digital artwork made by %{username}. Find more 3D galleries and designer portfolios on CGTrader.\n","galleries#tag":"Browse through all 3D or 2D work related to by %{tag}, see digital modeling creations and don‘t hesitate to share your thoughts about the artwork.\n","items#top_items":"Browse through the currently trending 3D models that CGTrader marketplace has to offer. Find 3D assets for your VR project, video game or architectural design.\n","items#top_print_items":"Browse through the currently trending 3D models for printing that CGTrader marketplace has to offer. Find 3D printable models and make them into real objects.\n","jobs#index":"Find %{filter} jobs currently available on 3D Projects platform. Search jobs by 3D software, modeling type and more.\n","posts#3d-printing-inspiration":"CGTrader‘s 3D Printing inspiration provides best 3D printing examples, trending works, coolest gadgets in the industry and other must-see 3D printing content.\n","posts#cg-inspiration":"CGTrader‘s CG inspiration provides best 3D modelling examples, realistic 3D renders, expert design works and other great CG examples for fellow 3D designers.\n","posts#cgtrader-news":"Find all CGTrader-related content here. New features and partnership, important notes, latest 3D infographics, and what you need to know about CGTrader.\n","posts#challenges":"Find latest news about 3D modelling, PBR and design challenges going on in CGTrader. Win 3D modeling software and other prizes given away for every challenge.\n","posts#designer-sportlight":"Find interesting, newest and influential 3D designers talking about 3D modeling profession, coming up in 3D industry and difficulties they had to overcome.\n","posts#guest-posting-guidelines":"CGTrader, the leading 3D stock model marketplace, accepts guest posts from qualified industry professionals. Pitch us your written articles, infographics, interviews or opinion pieces and approach one of the largest digital design communities in the world.\n","posts#index":"Read the 3D news, insights and updates into CGTrader marketplace, 3D designer interviews, best industry tutorials - all things regarding 3D modeling.\n","posts#interviews":"Read CGTrader interviews with 3D, 2D, Low-poly and VR modeling experts, industry influencers, software makers and find trending 3D industry news here.\n","posts#tag":"Read CGTrader’s news and posts about %{tag_name} – find more about CGTrader marketplace news, 3D designer interviews or best industry tutorials in CGTrader blog.\n","posts#tutorials-tips-and-tricks":"Learn 3D modeling from industry professionals. Find tutorial lists, tips \u0026 tricks about 3D software, and upgrade your 3D modeling skills to the next level.\n","posts#whats-hot":"Read trending 3D news, interviews with up-and-coming designers and other newest insights into 3D modeling community, 3D industry and CGTrader marketplace.\n","registrations#new":"Join CGTrader – leading 3D model marketplace with over %{model_count} models available, participate in strong 3D designer community and share your 3D knowledge.\n","reputation#index":"Learn about CGTrader reputation system for a fair \u0026 transparent payments, find out how to climb the reputation ladder and make the most of you 3D modeling.\n","scholarship#annual":"Write an essay on a specific 3D, VR or technology based topic, enter the annual CGTrader Scholarship challenge and get funded for your education bills.\n","scholarship#new":"Enter the CGTrader Scholarship 2016 challenge to win $1,000 for your education bills.\n","sessions#new":"Sign in to CGTrader, upload and share your works or purchase 3D models available on the 3D model marketplace and get involved with 3D designer community.\n","topics#show":"%{title} - join CGTrader's forum and discuss 3D model related topics. Get modelling feedback from 3D experts.\n","tutorials#index":"Watch and learn from the best 3D designers out there. Find more about 3D modeling, sculpting, texturing, baking with 3D tutorials available on CGTrader.\n","tutorials#show":"Watch the 3D Tutorial called %{title} created by %{username}. Learn from 3D designers with CGTrader tutorials.\n"},"title":{"events#index":"Meet us in person at the next event near you!","events#show":"Join us at %{event_title}"}},"modals":{"request_to_buy_gallery":{"subtitle":"Are you interested in buying 3D model used in this project? Add your proposal for the author below.","title":"Request to Buy it as 3D model"}},"model":{"already_purchased":"You have already purchased this product. You can download from your \u003ca href='/profile/cart/purchased'\u003eshopping cart\u003c/a\u003e. If you wish, you can purchase it again.","animated":"Animated","artist_profile":"Artist Profile","attributes":{"animated":"Animated","game_ready":"Low-poly","pbr":"PBR","rigged":"Rigged"},"author":"Author","author_get_your_payment":"Author will get 100% of your payment. CGTrader enables artists to earn maximum earnings and motivates to sell high quality content.","author_products":"More products by \u003ca href=\"%{author_url}\"\u003e%{username}\u003c/a\u003e","authorised_seller":"CGTrader's Authorised Seller","authority":"Authority","breadcrumb":{"3d_models":"3D models","home":"Home"},"bulk_download":{"download_all":"Download all files","files_included":{"one":"%{count} file included","other":"%{count} files included"},"too_large":"This model is too large to download as one archive (over %{limit}). Please download the files individually."},"buy":"Buy","cannot_download":"Only users with %{experience_needed} reputation points can download more than %{models} free models. Learn more about levels and reputation by \u003ca href='%{reputation_url}' target='_blank'\u003eclicking here\u003c/a\u003e.","collection":"Pack","comments":{"comment_posted":"Comment has been successfully posted","comment_reported":"Comment has been successfully reported","leave_comment":"Leave a comment","no_comments":"There are no comments for this item yet.","parent_not_exists":"Parent comment not exists","please_enter_comment":"Please enter your comment","posted_by":"Posted by \u003ca href='%{author_profile_url}' target='_blank'\u003e%{author}\u003c/a\u003e \u003cspan class='comment-time'\u003e%{time}\u003c/a\u003e","really_want_report":"Do you really want to report this comment?","reply":"Reply","report":"Report","submit":"Post Comment","title":"Comments","you_must_login":"You must \u003ca href='javascript:;' class='user-form-trigger login'\u003eLog In\u003c/a\u003e to join the conversation."},"contact_author":"Contact Author","customer_support":{"content":"This author is certified CGTrader seller that meets requirements of excellent product quality and customer support. We guarantee you will get the first class technical support from the author and CGTrader team. If the product is unacceptable for your needs or you have other issues, please contact us for assistance.","sub_title":"CGTrader and author support","title":"Customer Support"},"description":"Description","download":"Download","download_limit_exceeded":"You have exceeded your daily download limit. Please try again tomorrow. This limit does not apply to users who have made a purchase in the last 12 months.","download_modal":{"chat_with_us":"Chat with us","contact_support":"Please contact us via chat.","download_file":"Download %{name}","empty":"No files have been found","generic_error":"Something went wrong, please try again","link_error":"We apologize, but there seems to be a minor issue with your download link.","pbr_ready":"PBR-ready","preparing":"Preparing your download...","title":"Download Files"},"format_downloads":"3D Software Format Downloads","game_ready":"Game-ready (lowpoly)","geometry":"Geometry","geometry_types":{"gaussian_splats":"Point-based (Gaussian Splats)","nurbs":"Nurbs","other":"Other","polygon_mesh":"Polygon mesh","subdivision_ready":"Subdivision ready"},"license":"License","low_poly":"Low poly","lowest_price":{"content":"In other words, the author guarantees that the price of this 3D model is %{discount}% lower on CGTrader.com than on any other 3D store, except at authors personal website.","sub_title":"%{discount}% cheaper than anywhere else","title":"Lowest Price Guarantee"},"make_offer":"Make Offer","materials":"Materials","messages":{"no_files":"No uploaded files","no_rating":"No rating available for this model yet"},"model_details":"3D Model Details","offers":{"default_offer_message":"Hi, I would like to make an offer for this model.","make_offer_tooltip":"Send your price offer to the author if you want to buy it at lower price. When author accepts it, you will be notified to your Inbox and email.","make_offer_tooltip_title":"Buy this model at lower price."},"order":{"cant_buy_own_item":"You can't buy your own product","offer_is_not_accepted":"Offer is not accepted"},"pbr_textures":"PBR Textures","plugin_used":"Plugin Used","polygons":"Polygons","price":"Price","print_ready":"3D Print-ready","product_id":"Product ID","product_support":"Product Support","product_type":"Product Type","published":"Published on","quality_guarantee":{"content":"CGTrader guarantees to refund the money for all products purchased from this seller. If you found defects that are not listed in the product description or the product is not suitable for your needs, please contact us within 7 days from purchasing.","sub_title":"Money back guarantee","t
16518itle":"Quality Guarantee"},"rated":{"error":"You have already rated this model","success":"Thank you for rating this model"},"rating":"3D Model Rating","ratings":{"desc":{"negative":"Users who bought this 3D model thought it's of lower quality than expected","positive":"Users who bought this 3D model thought it's of good quality"},"negative":"negative","positive":"positive"},"recently_viewed_models":"Recently viewed models","renderer":"Renderer","report":"Report","report_form":{"cancel":"Cancel","comment":"Comment","messages":{"fill_field":"Please enter comment and/or link","reported":"Product has been successfully reported","success":"Thank you. Your report has been sent."},"optional":"(Optional)","submit":"Submit","url":"Original model"},"rigged":"Rigged","scanned":"Scanned","scripts-plugins":"Scripts \u0026 Plugins","similar_models":"Similar Models","statistics":"Statistics","subscription":{"limit_reached":"You have reached the limit of downloads for this billing period.","limit_reached_detailed":"You've reached your %{limit} download limit for this month. Your limit resets on %{reset_date}. To download more models now, you can purchase them individually.","limit_reached_detailed_no_date":"You've reached your %{limit} download limit for this month. To download more models now, you can purchase them individually."},"support_form":{"cancel":"Cancel","enter_message":"Please fill message field","item_id":"3D Model ID:","message":"Message","message_sent":"Message has been successfully sent","submit":"Send"},"tags":"Tags","textures":"Textures","title":"%{item_title} 3D model","unwrapped_uvs":"Unwrapped UVs","unwrapped_uvs_options":{"mixed":"Mixed","no":"No","non_overlapping":"Non-overlapping","overlapping":"Overlapping","unknown":"Unknown"},"uv_mapping":"UV mapping","vat_notice":"All prices are exclusive of VAT","version":"Version","vertices":"Vertices","vr":"VR models","vr_badges":{"animated":"Animated","game_ready":"Lowpoly","pbr":"PBR","rigged":"Rigged"}},"models":{"error":{"cannot_be_blank":" cannot be blank."},"item":{"unwrapped_uvs":{"mixed":"Mixed","no":"No","non_overlapping":"Non-overlapping","overlapping":"Overlapping","unknown":"Unknown"}}},"navigation":{"3d-models":{"3d-printing":"3D Print Files","discounts":"Discounts","find_designers":"Best designers","free":"Free","pbr":"PBR","scanned_3d-models":"Scanned 3D Models","scripts_apps":"Scripts \u0026 Apps","show-all":"Show all","textures":"CG Textures","title":"3D Models","vr_low-poly":"Low-poly"},"already_added_message":"has already been added to the cart","already_purchased_message":"You have already purchased this item.","aria":{"account":"Account","cart":"Cart","close_menu":"Close menu","logo":"CGTrader - 3D Models / 3D Designers","messages":"Messages","notifications":"Notifications","open_menu":"Open menu","search":"Search","search_by_image":"Search by image"},"budgets":{"below_10":"Under $10","collections":"Packs","free":"Free","more_than_20":"More than $20","more_than_50":"More than $50","sale":"On Sale","subscription":"Subscription","up_to_10":"Up to $10"},"cart_limit_reached":"You've reached the 500-item limit in your cart. To continue shopping, please complete your current order and start a new one for any additional items.","cart_widget":{"continue_shopping":"Continue shopping","discount":"Discount with enterprise coupon: ","discount_applied":"Discounts applied:","final":"Final price: ","have":"You have ","items":" items in your shopping cart.","left_to_unlock_discounts":"{{leftToUnlock}} Left to unlock discounts","potential_discount":"Potential discount:","proceed":"Proceed to Checkout","recently_viewed_empty_text":"Start exploring and your recently viewed items will appear here.","recently_viewed_empty_title":"Nothing here yet","recently_viewed_tab":"Recently viewed","remainer":"To activate CGTrader Points add: ","sale_discounts":"Discounts applied: ","sale_time_left":"Sale ends in","sale_tooltip":"Discounts from our Sale-Off campaigns are only applied when your cart reaches ${{threshold}} or more.","sale_tooltip_text":"This item is on sale, but the discount will only be applied once your cart subtotal reaches ${{threshold}} or more.","text":"Your shopping cart is empty.","title":"Shopping Cart","total":"Total: ","used":"CGTrader Points used: ","view":" View cart","wishlist_empty_text":"Start exploring and save your favorites for later — they’ll show up here.","wishlist_empty_title":"Your wishlist is empty","wishlist_tab":"Wishlist items"},"community":{"awards":"Designer Awards 🏆","blog":"Blog","challenges":"Challenges","community_ratings":"Community ratings","de
16518signers":"3D Designers","digital_art":"Digital Art Competition","forum":"Forum","galleries":"Galleries","invite":"Invite a friend","printlist":"3D PrintList","sell_3d-models":"Sell 3D Models","title":"Community","tutorials":"Tutorials"},"conversations_widget":{"conversations":"Conversations","messages":{"loading":"Loading inbox"},"types":{"cgtrader_support":"CGTrader Support","file_conversion_request":"File conversion request","job_application":"3D Project","model_feedback":"Product Feedback","model_support":"Model Support","offer":"Price Offer","personal":"Personal Message","promotions":"Promotions"}},"create_ai":{"community_gallery":"Community Gallery","free_trial":"Free trial","generate_3d_model":"Generate 3D Model","generation":{"dismiss":"Dismiss notification","error_title":"Generation issue","generating_title":"Your 3D model is being generated","open_model":"Open Model","ready_title":"Your 3D model is ready!"}},"create_with_ai":"Create with AI","credits_guest_widget":{"create":"Create an account now!","discount":" discount","for":" for your ","get":" and get up to ","learn_more":"Learn more about CGTrader Points ","link":"here","purchase":"first purchase!","sign-up":"Sign up","text_below_1":"Points can be used when purchasing \"","text_below_2":"\" sign marked models and cover up to 30% discount of the price."},"credits_user_widget":{"condition_1":"CGTrader Points can be applied toward the purchase of models marked with the \u003ci class=\"custom-font-icon custom-font-icon--small icon-wallet\"\u003e\u003c/i\u003e sign, providing up to a 30% discount off their price.","condition_2":"Points can only be used if your cart's total value is \u003cstrong\u003e$%{minimum}\u003c/strong\u003e or more.","condition_3":"During active sales or promotions, points cannot be used.","condition_4":"\u003cstrong\u003eKeep in mind\u003c/strong\u003e that points have an expiration date - your points will expire in \u003cstrong\u003e%{days}\u003c/strong\u003e days.","earn_rate":"Points earn rate ","first_text_1":"Consider it a gift for your first purchase - you can use points when purchasing \" ","first_text_2":" \" marked models and get up to 30% discount of it's price. Points can only be used if the total value of your cart is equal to or greater than","learn_more":"Learn more","lifetime_spending":"Lifetime spending: ","list_item":"By increasing your lifetime spending on CGTrader, your Points earn rate increases as well.","more_credits":"How do I get more points?","next_level":"Next level: ","notice":"\u003cstrong\u003eNOTE:\u003c/strong\u003e CGTrader Points cannot be used during the ongoing sale, but they will be available again once the sale ends. In the meantime, check out great deals on 3D models \u003ca href=\"https://www.cgtrader.com/3d-models?sale_off=1\"\u003ehere\u003c/a\u003e","par_1":"You have ","par_2":" CGTrader points.","par_2_variation":" in Points that you can spend when no sale off is available.","regular_text_1":"Points can be used when purchasing \" ","regular_text_2":" \" sign marked models and cover up to 30% discount of it's price.","title":"CGTrader Points"},"enterprise":{"management":"3D Asset Management","title":"3D Content Production"},"hire_a_freelancer":"Hire a Freelancer","item_add_message":"has successfully been added to the cart","item_restricted_message":"This item can't be bought due to sanctions placed on designers country(RU/BY).","navbar":{"3d_models":{"about":"About 3D Models","about_print":"About 3D Printing","all_models":"All models","browse_all":"All 3D Models","browse_all_print":"All 3D Printing","budget":"Budget","categories":"Categories","types":"Types"},"actions":{"new":"New","top_selling":"Top Selling","trending":"Trending"},"budgets":{"below_10":"Under $10","free":"Free","more_than_20":"More than $20","more_than_50":"More than $50","sale":"On Sale","up_to_10":"Up to $10"}},"projects":{"3d_customs":"Custom 3D","3d_printing":"3D Printing","about":"About 3D Projects","browse":"Browse Projects","buyer_perspective":"Hire 3D Designers","for_designers":"For Designers","my_projects":"My Projects","seller_perspective":"Freelance 3D Projects","start":"Start a Project","work":"Work with CGTrader"},"sell_3d-models":"Sell 3D Models","sellers_hub":"Sellers Hub","subscription_includes_item_message":"This item is already included in your subscription.","tooltips":{"cart":"Shopping Cart","conversations":"My Conversations","discounts":"CGTrader Points","notifications":"Notifications","publisher":"Publisher"},"user":{"ban_notification":"This user account is suspended. Please contact our support for explanation.","cannot_refer_yourself":"cannot refer yourself","email_confirmation":"Your email address has been verified.","email_reconfirmation":"was already confirmed, please try signing in","ends_at":"Ends at: %{date}","handle_error_tfa_code_missmatch":"Security code you entered is incorrect. Please try again.","handle_error_tfa_code_missmatch_last":"Your security code has expired. If you want to continue, click resend email and enter the new provided code.","handle_error_tfa_default":"Something went wrong, please try again later.","handle_error_tfa_expired":"Your security code has expired.","hundred_percent_royalties_popover":"You are taking part in a temporary 100% earnings campaign: all the sales proceeds from new models will go to you until the campaign's end","invali
16518d_email_message":"Please enter a valid e-mail address to continue.","login":"Log in","meta_description":"Take a look at %{username} CGTrader 3D designer profile, portfolio and 3D models available. Check out best 3D models from top modeling artists in 3D industry.","meta_title_suffix":"3D Designer","not_valid_birthday":"Birthday is not valid.","offline":"Offline","online":"Online","page_title":"Designer profile","rate_models_limit":"You cannot rate more models today.","semantic_description":"Designer's 3D models, artworks and portfolio. Take a look at his creations on CGTrader","semantic_title":"Designer Profile","session_failure":"Wrong username/e-mail or password has been provided","session_password_expired":"Your password has expired due to inactivity. Please reset your password via the email link we’ve sent.","session_two_fa_failure":"Code verification has failed","session_unconfirmed":"Your email address has not been confirmed yet.","token_not_recognized":"Token was not recognized","twitter_shareable_text":"A #3dartist worth following","username_error":"should not contain spaces or non-latin symbols"},"user_menu":{"buyer":{"freelancers":"Find Freelancers","purchases":"My Purchases","sell":"Sell 3D Models","wishlist":"3D Model Wishlist"},"dashboard":"Admin Dashboard","logout":"Log out","public_profile":"Public Profile","seller":{"analytics":"Analytics","find_new_projects":"Find New 3D Projects","freelance":"My 3D Projects","freelance_cgtrader":"Freelance with CGTrader","models":"My models","publish_project":"Post a Project","publishing":"Upload 3D Model","sales":"My sales","subscription":"Subscription","wishlist":"3D Model Wishlist"},"settings":"Account Settings"}},"notification":{"events":{"category":{"Notification::Forum::PostCreatedTemplate":"Forum","Notification::ItemPriceOfferBuyerNegotiatesTemplate":"3D Projects","Notification::ItemPriceOfferDesignerAcceptsTemplate":"3D Projects","Notification::ItemPriceOfferDesignerNegotiatesTemplate":"3D Projects","Notification::ItemUseCaseAcceptedTemplate":"Feedback","Notification::ItemUseCaseRejectedTemplate":"Feedback","Notification::ItemUseCaseSubmittedTemplate":"Feedback","Notification::Job::ApplicationChangesAcceptedTemplate":"3D Projects","Notification::Job::ApplicationCreatedTemplate":"3D Projects","Notification::Job::ApplicationUpdatedTemplate":"3D Projects","Notification::Job::ApplicationWithdrawnTemplate":"3D Projects","Notification::JobCanceledTemplate":"3D Projects","Notification::JobCustomJobRequestedTemplate":"3D Projects","Notification::JobExpiredTemplate":"3D Projects","Notification::JobFinishedTemplate":"3D Projects","Notification::JobPaidTemplate":"3D Projects","Notification::JobRepostedTemplate":"3D Projects","Notification::JobTemplateCreatedTemplate":"3D Projects","Notification::JobTemplateReviewedTemplate":"3D Projects","Notification::JobUpdatedTemplate":"3D Projects","Notification::JobWillExpireTemplate":"3D Projects","Notification::ModelAdultBlockedTemplate":"Feedback","Notification::ModelBlockedTemplate":"Feedback","Notification::ModelFilesUpdatedTemplate":"Purchases","Notification::PublicActivity::ActivityAnnouncementTemplate":"Community","Notification::ReputationLogAdditionalItemOfReferralTemplate":"Referral","Notification::ReputationLogAdditionalPurchaseOfReferralTemplate":"Referral","Notification::ReputationLogAnswerToUserTopicTemplate":"Forum","Notification::ReputationLogCommentVoteTemplate":"Forum","Notification::ReputationLogDownloadedFreeTemplate":"Downloads","Notification::ReputationLogFirstItemOfReferralTemplate":"Referral","Notification::ReputationLogFirstPurchaseOfReferralTemplate":"Referral","Notification::ReputationLogForumTopicAnswerTemplate":"Forum","Notification::ReputationLogMyLikeTemplate":"My activity","Notification::ReputationLogNegativeItemRatingTemplate":"Feedback","Notification::ReputationLogNewCommentTemplate":"Feedback","Notification::ReputationLogNewFollowerTemplate":"Following","Notification::ReputationLogNewForumTopicTemplate":"My activity","Notification::ReputationLogNewItemTemplate":"My activity","Notification::ReputationLogNewLikeTemplate":"Feedback","Notification::ReputationLogNewProjectTemplate":"My activity","Notification::ReputationLogPositiveForumTopicVoteTemplate":"Forum","Notification::ReputationLogPositiveItemRatingTemplate":"Feedback","Notification::ReputationLogReferralUserRegistrationTemplate":"Referral","Notification::ReputationLogSaleTemplate":"Sales","Notification::ReputationLogTutorialTemplate":"My activity","Notification::ReputationLogTutorialVoteTemplate":"Feedback","Notification::TagPromotionApprovedTemplate":"Feedback","Notification::TagPromotionDeclinedTemplate":"Feedback","Notification::TagPromotionExpiredTemplate":"Feedback"},"errors":{"user_and_event_triggered_are_not_same":"User who triggered the event cannot be the same as the event's user."}}},"number":{"currency":{"format":{"delimiter":",","format":"%u%n","precision":2,"separator":".","significant":false,"strip_insignificant_zeros":false,"unit":"$"}},"format":{"delimiter":",","precision":3,"round_mode":"default","separator":".","significant":false,"strip_insignificant_zeros":false},"human":{"decimal_units":{"format":"%n%u","units":{"billion":"b","million":"m","quadrillion":"q","thous
16518and":"k","trillion":"t","unit":""}},"format":{"delimiter":"","precision":3,"significant":true,"strip_insignificant_zeros":true},"storage_units":{"format":"%n %u","units":{"byte":{"one":"Byte","other":"Bytes"},"eb":"EB","gb":"GB","kb":"KB","mb":"MB","pb":"PB","tb":"TB"}}},"nth":{"ordinalized":{},"ordinals":{}},"percentage":{"format":{"delimiter":"","format":"%n%"}},"precision":{"format":{"delimiter":""}}},"orders":{"errors":{"download_link_expired":{"guest":"Your download link has expired. Please confirm your email and finish registration to download your product. You should have your confirmation link in your inbox or you can ask to resend it in this page.\n","user":"Your download link has expired. Please Sign in to download your product."}}},"owly":{"failed_set_pending":"Failed to set pending.","failed_to_approve":"Failed to approve.","failed_to_suspend":"Failed to suspend.","failed_to_update_license":"Failed to update license.","failed_to_update_status":"Failed to update approve status."},"payments":{"agreement":{"address":"Street Address / ZIP","attachments":"Files uploaded","bank_account":"Bank account (IBAN)","bank_name":"Bank name","city":"City","company_address":"Street Address / ZIP","company_city":"City","company_code":"Company code","company_country":"Country","company_name":"Company name","country":"Country","datebirth":"Date of Birth","name":"Name","paypal_address":"PayPal email address","person_id":"Personal Identification Number","surname":"Surname","swift":"SWIFT (BIC)","tooltips":{"company_registration_number":"The business registration number is a unique number given to a company when it is incorporated, formed or registered with the state, province or country. The number will appear on any registration or corporate documents. It may also be referred to as the Company Number or Entity Number.","company_vat":"If the business is registered for VAT (Value Added Tax) in the European Union (EU), please indicate your VAT Identification (registration) number here.","email":"This email address will be used for activation of Hyperwallet Pay portal and sending payout and other notifications from Hyperwallet, which is a payout service provider for CGTrader.","legal_name":"The name should match the official business name in its incorporation documents.","minimum_available_balance":"For example, if you wish to get paid only if your balance is greater than $100 please enter this amount in the field above.","payout_option":"Sellers getting paid to Payoneer may choose payout frequency.","person_id":"Personal Identification Number (national ID, personal code, social security number, etc.) is assigned to you by the state and used for identification purposes. The number should be printed on your national identification document that you will have to upload. If you haven’t got a Personal Identification Number please use the number of your national identification document (passport, ID-card, etc.).","tax_residence_country":"Country of tax residence is the country to which you are responsible for paying taxes.","vat":"If you are registered for VAT (Value Added Tax) in the European Union (EU), please indicate your VAT Identification (registration) number here."},"vat":"VAT Identification Number","vat_id":"VAT identifier","webmoney_wallet":"WebMoney purse (WMZ)"}},"payoneer":{"service_unavailable":"Payoneer service is temporarily unavailable, please try again later or contact \u003ca href='mailto:[email protected]'\[email protected]\u003c/a\u003e for more information.\n","status_message":{"approved":"Your account was successfully approved by Payoneer","declined":"Payoneer application has been declined. Please choose another payment method or contact \u003ca href='https://payoneer.custhelp.com/' target='_blank'\u003ePayoneer\u003c/a\u003e for further information.\n","registered":"Thank you for submitting your information. You'll be contacted by Payoneer team when your account is activated.\n"}},"price_offers":{"status":{"accepted":"Accepted","buyer_offer":"Buyer proposed an offer","declined":"Declined","in_progress":"In Progress","seller_offer":"Seller responded","withdrawn":"Withdrawn"},"status_for_buyer":{"accepted":"Approved","buyer_offer":"Waiting for answer","declined":"Declined","in_progress":"Waiting for answer","seller_offer":"Waiting for your approval","withdrawn":"Withdrawn"},"status_for_seller":{"accepted":"Approved","buyer_offer":"Waiting for your approval","declined":"Declined","in_progress":"Waiting for your approval","seller_offer":"Waiting for answer","withdrawn":"Withdrawn"}},"print_models":{"browse_all_makes":"Browse all 3D Makes","browse_all_models":"Browse all models","description":"Discover trending 3D print-ready models, printable assets, and STL files for gaming, art, home projects, and more.","makes_description":"Explore the latest 3D Makes uploaded by the community - real printed examples showing how 3D models look in the physical world.","makes_title":"New 3D Makes","new_description":"Check out the newest 3D prints added to our collection - fresh designs ready to download and print for your next project.","new_title":"New 3D print models","title":"Trending 3D print models"},"product":{"description":"Download %{title} 3D model or browse %{similar_items}
16518 similar %{short_name} 3D models. Available in max, obj, fbx, 3ds and other formats. Browse 140000+ 3D Models on CGTrader.","money_guarantee":{"text":"You are buying from a trusted seller - this means you will get back your money if you are not satisfied with the product.","title":"Money Back Guarantee"},"sidebar":{"details":{"Collection_id":"Pack ID","Model_id":"Model ID","animated":"Animated","game_ready":"VR / AR / Low-poly","geometry_type":"Geometry","materials":"Materials","pbr":"PBR","plugins_used":"Plugins used","polygons":"Polygons","published_at":"Publish date","rigged":"Rigged","textures":"Textures","unwrapped_uvs":"Unwrapped UVs","uv_mapping":"UV Mapping","vertices":"Vertices"},"tab":{"download":"Download","get_printed":"Get printed","print":"Use my 3D printer"}},"store_credits_discount_html":"\u003cdiv class=\"u-text-center\"\u003e\n  %{discounted} discount from your remaining %{available}\n  \u003ca href='https://www.cgtrader.com/pages/cgtrader-loyalty-discounts' target='_blank'\u003eCGTrader points\u003c/a\u003e. \u003cbr\u003e\n  The points can be used for up to 30% discount for each purchased model.\n\u003c/div\u003e\n","verified":"Your 3D model has been reviewed and received a verification score. This score measures the quality of your model, with a higher score indicating more successful standard checks. The date of verification is displayed below the score. You can now re-upload your model after making changes and track how it affects the score. Below you can see the specific factors that contribute to your model's score.","verified_info":"\u003cdiv class=\"cgt-verified-notice__text cgt-legacy-text-tiny cgt-legacy-color-typography-secondary\"\u003e\n  Verified models are of higher quality as they have passed CGT Standard technical and visual checks, making them more professional-grade 3D assets.\n  \u003cspan class=\"cgt-verified-notice__link\"\u003e\u003ca class=\"cgt-legacy-text-caption\" target=\"__blank\" href=\"https://help.cgtrader.com/hc/en-us/sections/12602641776913-PBR-model-standard\"\u003eMore details\u003c/a\u003e\u003c/span\u003e\n\u003c/div\u003e\n"},"profile":{"account_settings":{"account":{"email":"Email","submit":"Save","title":"Profile","update":{"updated":"Profile updated"},"username":"Username"},"email_settings":{"descriptions":{"ai_studio_notifications":"Notifications about your CGTrader AI generations, including when a model you have not opened yet is ready","communication":"Information about your communication with other CGTrader users, including personal messages, new price and custom job offers, feedback and product support requests","news":"Information about CGTrader products, new feature announcements and community news","onboarding_and_edu":"Information about your onboarding process and tips to help you get the most out of CGTrader","push_notifications":"Notifications about important account activity and updates","sales_and_offers":"Information about special offers, 3D model Sales and CGTrader bonus credits","seller_sold_item":"Information about your sold 3D models and received payments"},"privacy_policy_html":"Please read our \u003ca href='https://www.cgtrader.com/pages/privacy-policy' 'target='_blank'\u003ePrivacy Policy\u003c/a\u003e for more information. ","titles":{"ai_studio_notifications":"CGTrader AI notifications","communication":"Communication (Personal messages, 3D model feedback, community activity, offers)","news":"3D News / CGTrader updates","onboarding_and_edu":"Onboarding \u0026 Education","sales_and_offers":"Sales \u0026 special offers","seller_sold_item":"Sales \u0026 Payments information"},"tracking_attributes":{"ai_studio_notifications":"CGTrader AI notifications","communication":"Communication","news":"3D News / CGTrader updates","onboarding_and_edu":"Onboarding \u0026 Education","push_notifications":"Push Notifications","sales_and_offers":"Sales \u0026 special offers","seller_sold_item":"Sales \u0026 Payments information"},"update":{"updated":"Your email has been updated successfully."}},"gdpr_success_notification":{"opt_in":"Thank you! You have successfully re-subscribed to our emails.","opt_out":"You have successfully unsubscribed from our newsletters. You can change this preference anytime by visiting your profile settings.\n"},"location_tooltip_text":"\u003cdiv\u003eThis checkbox enables showing location on the Profile page.\u003c/div\u003e \u003cdiv\u003eNote that in any case due to compliance reasons your location will be shown to your buyers (if you are a seller) and to the sellers you bought from.\u003c/div\u003e","moneybookers":{"account":"Skrill Account"},"password":{"current_password":"Current password","new_password":"New password","submit":"Change Password","title":"Password","updated":"Your password has been updated successfully. You can now use your new password to log in.","verify_password":"Verify password"},"paypal":{"account":"Paypal Account","submit":"Save","title":"Paypal Settings","update":{"updated":"Your Paypal settings were updated"}},"personal_info":{"areas_of_expertise":"Areas of Expertise","country":"Country","description":"A few sentences about yourself","full_name":"Full name","languages":"Languages","preffered_tools":"Preferred Tools","submit":"Save","title":"Personal and professional information","update":{"updated":"Your information was successfully updated"},"website":"Website"},"personalization":{"mute":"Hide","no_muted_title":"No hidden designers","title":"Hidden Designers","tooltip":"3D models of listed designers are excluded from your search results.","unmute":"Unhide","unmute_designer":{"confirm":"Are you sure?"}},"professional_info":{"allow_private_job_requests":"Buyers will be able to send you custom 3D job requests.","buyer_engagement_program_html":"Attract more customers by letting them purchase your models with CGTrader CGTrader Points. \u003ca href='https://www.cgtrader.com/pages/cgtrader-loyalty-discounts-explained' target='_blank'\u003eMore information\u003c/a\u003e\n","pbr_program":"The lower earning rate is applied only when the converted file is downloaded","pbr_program_agreement":"You have agreed to take part in the PBR program for 24 months on %{date}. See Terms \u0026 Conditions for more details. Please contact our support should you have any questions.","product_analytics_participation_html":"I participate in \u003ca href='https://www.cgtrader.com/profile/analytics'target='_blank'\u003eAnalytics\u003c/a\u003e and agree with their Terms and Conditions\n","product_analytics_participation_tooltip_html":"You have agreed to participate in Analytics and agreed with Analytics Terms and Conditions (\u003ca href='https://www.cgtrader.com/pages/cgtrader-analytics-terms-of-service'target='_blank'\u003elink\u003c/a\u003e) at %{accepted_at}. If you want to terminate your participation, please contact CGTrader support\n"},"security":{"allow_mfa":"Enable 2-step verification to protect your account","use_authenticator_app":"Use an authenticator app for 2-step verification","use_authenticator_app_tooltip":"Scan the QR code with your authenticator app (Google Authenticator / Authy) to generate verification codes.","use_email":"Use email for 2-step verification"},"sidebar":{"general_settigns":"General Settings","member_since":"Member Since","paypal":"PayPal","title":"Account Info"},"title":"My Account"},"archiver_download":{"request_error":"Something has gone wrong with your request. Please, try again later.","request_received":"Your download request was received. You will be notified by email when it's ready."},"cart":{"messages":{"order_deleted":"Order has been successfully deleted","rating_saved":"Your rating has been successfully saved"},"product_kind_label":{"cg":"3D model"}},"conversations":{"conversations":"Conversations","messages":{"loading":"Loading inbox"},"types":{"cgtrader_support":"CGTrader Support","file_conversion_request":"File conversion request","job_application":"3D Project","model_feedback":"Product Feedback","model_support":"Model Support","offer":"Price Offer","personal":"Personal Message","promotions":"Promotions"}},"dashboard":{"feed":{"scopes":{"all":"All activities","announcements":"Announcements","following":"Following","forum":"Forum","notifications":"Notifications"}},"latest_activity":"Reputation","latest_sales":{"buyer":"Buyer","price":"Price","product":"Product","purchase_date":"Purchase date","royalties":"Royalties","title":"Latest Sales","upload_first_model":"Upload your first model now"},"offers":{"accept":"Accept","buyer_offers_new_price":"Buyer offers a new price","no_offers":"You do not have any offers.","reject":"Reject","title":"Offers","update_offer":"Update Offer","waiting_response_from_buyer":"Waiting response from buyer (Offered price: $%{price})"},"sidebar":{"authority":"Authority","average_price":"Average price","inactive_models":"Inactive Models","number_models":"Number of models","royalty_rate":"Your Earning rate","title":"Profile Info","total_items":"Total 3D Models","total_sales":"Total Sales"}},"meta":{"description":"Explore high-quality 3D models by %{username} on CGTrader. Browse unique assets for games, VR/AR, and visualization.","title":"%{username} 3D Model Designer"},"my_products":{"errors":{"empty":"This field cannot be empty","max_duration_limit":"Duration must be between 1 and %{max} days","max_fixed":"Please enter a value less than or equal to 10000","max_percent":"Please enter a value less than or equal to 99","max_reached":"Price cannot be greater than 10000","max_title":"Product title should not be longer than 64 characters","min_3dp_fixed":"Please enter a value greater than or equal to 3","min_fixed":"Please enter a value greater than or equal to 2","min_percent":"This value cannot be less than 0","min_reached":"Price cannot be less than 2","min_t
16518itle":"Product title should be at least 5 characters long","numeric":"This field should contain numeric values only","promotion_limit":"You already have 3 active campaigns using the tag \"%{tag}.\" To promote another item with this tag, please wait for an existing campaign to finish or choose a different tag.","promotion_rejected":"You're not allowed to promote this item because it contains adult content or copyrighted material."},"status_tooltips":{"pending":{"adult":"Your model contains adult content and must be approved by our team before it is published on the marketplace.","copyright":"Your model is pending for review, because it possibly contains trademarked or copyrighted material."},"suspended":{"adult":"Contains adult or violent content.","copyright":"Contains potential trademark or copyright infringement.","reported":"The model was reported and must be approved by our team before it is published on the marketplace again."}},"suspension_appeal":{"accepted":"Appeal accepted","appeal":"Appeal","modal":{"acknowledgement":"I confirm this model does not violate the policy above, and understand that submitting a false appeal may affect my account standing.","cancel":"Cancel","error":"Failed to submit your appeal. Please try again.","exception":"Clearly non-sexual figures - such as untextured base meshes, mannequins, or dolls - are permitted.","exception_label":"Exception:","footer":"Each suspension may be appealed once. Reviews take up to 5 business days. Decisions are final.","header":"Appeal a Content Suspension","intro":"This listing was suspended for adult or sexually suggestive content, which cannot be displayed under search engine safety requirements. Submit an appeal if you believe this is a mistake.","note":"Our Support team can't review or handle suspension appeals sent by email or support ticket. Sending an email to Support won't start or speed up the review of your appeal.","note_label":"Note:","policy_intro":"Appeals only succeed if the model contains no sexual or suggestive content, including:","policy_items":["Suggestive themes or poses","Revealing or partially removed clothing","Partial or implied nudity"],"reason_field_label":"Tell us why this listing does not violate the policy above","reason_placeholder":"Describe what the model depicts and why it contains no suggestive content.","reason_word_limit":"Reason must be %{count} words or fewer","submit":"Submit Appeal","success":"Your appeal has been submitted."},"pending":"Appeal pending","reasons":{"copyright":"Copyright violation","designers_request":"Suspended at designer's request","gun":"Prohibited print-gun content","missing_kyc":"Verification required","not_available":"N/A","nsfw":"NSFW content","reported_model":"Reported model","spam_scam":"Spam / Scam","stolen_model":"Stolen model"},"rejected":"Appeal rejected"},"types":{"cad":"CAD Model","cg":"CG Model","printable":"Printable Model"}},"my_sales":{"followers":{"actions":"Actions","date":"Follows since","no_followers":"You don't have any followers yet.","no_followers_heading":"Oops, no followers!","username":"Username"},"latest_downloads":{"created_at":"Download Date","downloader":"Downloader","file_name":"File name","no_downloads":"Unfortunately nobody downloaded your models (at least in the past 6 months). %{link} to improve that!","no_downloads_heading":"Oops, no downloads!","price":"Price","product":"Product","product_id":"Product ID"},"latest_sales":{"buyer":"Buyer","country":"Country","evermotion_id":"Evermotion ID","no_sales":"Ah, unfortunately you don't have any sales yet. We wish you best of luck in getting them soon!","no_sales_heading":"Oops, no sales!","order_id":"OID","original_price":"Original Price","payment_processing":"Payment Processing","payment_processing_tooltip_html":"Transaction fees are applied for every purchase. You can find more about transaction fees by \u003ca href='https://help.cgtrader.com/hc/en-us/articles/33947843875345-Payment-processing-fees' target='_blank'\u003eclicking here\u003c/a\u003e.","payout_transaction_id":"Transaction ID","price":"Sold For","price_tooltip_html":"This tab shows the price that the buyer paid after applying coupon code, price offer, sales campaign discount or Customer Loyalty discount.","price_without_fees":"After tax","price_without_fees_tooltip_html":"Earnings are calculated after applying payment processing fees to the sales price.","product":"Product","product_id":"Product ID","purchased":"Purchased","royalty":"Earning rate","royalty_amount":"Final Earnings","status":"Status","title":"Latest Sales","vat_tooltip":"%{vat}% VAT should be charged for buyers from this country"},"menu_tabs":{"latest":"Latest","monthly":"Monthly"},"monthly_sales":{"date":"Date","downloads":"Downloads","revenue":"Revenue","royalty":"Avg. Earning rate","sales":"Sales","title":"Monthly Sales"},"payouts":{"fees":{"no_fees":"There are no previous payout fees.","payoneer":"Payouts for Payoneer users are free! You will only have to pay a conversion fee when withdrawing earnings from Payoneer to your local bank account.\n","paypal":"A money transfer to your Paypal account is processed through \u003ca href='https://www.paypal.com/lt/webapps/mpp/ua/useragreement-full#16' target='_blank' rel='nofollow'\u003ePaypal Mass Payments\u003c/a\u003e. \u003c/br\u003e The amount of payout fee may vary based on your location.\n","refunds":"Click to see list of refunds.","webmoney":"A money transfer to Webmoney system is processed through Paysera and costs €0.29 (approximately $0.31 based on exchange rate as on Dec 30, 2016). Also, a standard 0.8% Webmoney fee (calculated from the total payout fee) is applied. More details can be found \u003ca href='https://www.paysera.com/v2/e
16518n-GB/fees/transfers-in-other-currencies' target='_blank' rel='nofollow'\u003ehere\u003c/a\u003e.\n","wire":"A money transfer to your Wire account is processed through Paysera and costs from €7.00 to €30.00 depending on the country of your bank. More details can be found \u003ca href='https://www.paysera.com/v2/en/fees/additional-transfers-services-fees' target='_blank' rel='nofollow'\u003ehere\u003c/a\u003e.\n"},"status":{"all_time":"Yet to be paid","limit_not_reached":"To be paid when $200 minimum is reached","ongoing":"Earned this month","paid":"Paid","pending":"To be paid until 20th of this month","revoked":"Revoked","to_be_paid":"To be paid","upcoming":"To be paid until 20th of next month"}},"refunds":{"default_reason":"This item purchase was refunded. For more details please contact our support."}},"purchases":{"author":"Author","author_v2":"By","download":"Download","download_files":"Download Files","item":"Product","no_items":"You don't have purchased products.","order_id":"Order ID","price":"Price","rate_purchases":"Rate Your Purchases","support":"Support","title":"My Purchases"},"referrals":{"actions":{"uploaded_model":"Uploaded model","user_registration":"Registered"}},"settings_updated":"Your settings have been updated successfully."},"publisher":{"geometry_types":[{"value":null,"label":"Choose geometry"},{"value":"gaussian_splats","label":"Point-based (Gaussian Splats)"},{"value":"polygon_mesh","label":"Polygon mesh"},{"value":"subdivision_ready","label":"Subdivision-ready"},{"value":"nurbs","label":"Nurbs"},{"value":"other","label":"Other"}],"licenses_free":[{"value":"royalty_free_no_ai","label":"Royalty free, no AI"},{"value":"custom_no_ai","label":"Custom, no AI"},{"value":"editorial_no_ai","label":"Editorial, no AI"}],"licenses_paid":[{"value":"royalty_free","label":"Royalty free"},{"value":"royalty_free_no_ai","label":"Royalty free, no AI"},{"value":"custom","label":"Custom"},{"value":"custom_no_ai","label":"Custom, no AI"},{"value":"editorial_no_ai","label":"Editorial, no AI"}],"messages":{"filetypes":{"unsupported":{"any":"File type not supported.","printable":"File type not supported, please upload .stl, .obj or .wrl files only."}},"size":{"over_the_limit":"Image size must be less than 15 megabytes."},"url":{"not_allowed":"Image URL is not allowed."}},"payment_agreement_blocker":{"cta":"Complete Payment Agreement","dismiss":"Continue editing","message":"Before you can publish your model, you need to complete and verify your Payment Agreement. This ensures you can receive payments for your sales.","pre_activation_message":"Your Hyperwallet account is not activated. Please activate and verify your Hyperwallet account by \u003cstrong\u003efollowing the instructions sent to your email address\u003c/strong\u003e to become eligible to publish models and receive payments for your sales."},"units":[{"value":"mm","label":"millimeters (mm)"},{"value":"cm","label":"centimeters (cm)"},{"value":"in","label":"inches (in)"},{"value":"m","label":"meters (m)"}],"unwrapped_uvs":[{"value":"unknown","label":"Unknown"},{"value":"non_overlapping","label":"Non-overlapping"},{"value":"overlapping","label":"Overlapping"},{"value":"mixed","label":"Mixed"},{"value":"no","label":"No"}]},"publishing":{"banned_error":"Your account has been banned for publishing spam content. If you believe this is a mistake, please contact [email protected].","files_not_finalized":"One or more files haven't finished uploading and can't be published. Please remove them and upload again before publishing.","spam_suspect":"Your account is not authorized to publish this model. If you believe this is a mistake, please contact [email protected]."},"rating":{"anonymous_buyer":"Anonymous Buyer","errors":{"not_verified_buyer":"Only verified buyers can leave a review"}},"reputation":{"additional_item_of_referral":"Model upload of your referral","additional_purchase_of_referral":"Purchase of your referral","answer_to_user_topic":"Answer to your topic","approved_challenge_item":"Model participating in a challenge","comment":"Posted new comment on model","comment_on_your_content":"Comment on your model, gallery or tutorial","comment_vote":"Like of your comment","community_rating":"Community rating with 80% accuracy","community_rating_high_accuracy":"Community rating with 85% accuracy or more","custom_points":"Custom Points","downloaded_free":"Free model download","first_item_of_referral":"First model upload of your referral","first_purchase_of_referral":"First purchase of your referral","forum_topic_answer":"Reply to topic","forum_topic_vote":"Thread vote","item_purchase":"Make new purchase","job_finished":"Job closed","my_like":"Like of a model, gallery or tutorial","negative_item_rating":"Negative rating after model purchase","neutral_item_rating":"Neutral 3D model rating","new_comment":"New comment","new_follower":"New follower","new_forum_topic":"New topic in forum","new_item":"New model upload","new_like":"Like of your model, gallery or tutorial","new_project":"Gallery project accepted","new_tutorial":"Tutorial published","positive_first_topic_answer":"First positive topic answer","positive_forum_topic_vote":"Like of your topic","positive_item_rating":"Positive rating after model purchase","positive_topic_answer":"Positive topic answer","purchase":"Make new purchase","referral_uploaded_model":"Model upload of your referral","referral_user_registration":"Referral Registration","sale":"Model sold","social_model_like":"Social like of your model","social_profile_like":"Social like of your profile","tutorial":"New tutorial","tutorial_vote":"Like of your tutorial"},"routes":{"3d-models":"3d-models","3d-models-new":"3d-models/new","3d-models-trending":"3d-models/trending","3d-modles-top-selling":"3d-models/top-selling","3d-print-models":"3d-print-models","3d-print-models-new":"3d-print-models/new","3d-print-models-top-selling":"3d-print-models/top-selling","3d-print-models-trending":"3d-print-models/trending","animated-3d-models":"animated-3d-models","discover":"discover","free-3d-models":"free-3d-models","free-3d-print-models":"free-3d-print-models","low-poly-3d-models":"low-poly-3d-models","pbr-3d-models":"pbr-3d-models","pbr-textures":"pbr-textures","rigged-3d-models":"rigged-3d-models"},"sale_off":{"ends_at_before_starts_at":"End date must be later than the start date.","ends_at_in_past":"End date cannot be in the past.","invali
16518d_condition_format":"Line %{index} has an invalid condition format.","invalid_condition_key":"Line %{index} has invalid condition key '%{key}'.","invalid_condition_value":"Line %{index} has invalid %{key} value '%{value}'.","invalid_discount_value":"Line %{index} has invalid discount value '%{value}'.","invalid_price_range":"Line %{index} has an invalid price range (gt must be less than lt).","invalid_webp_format":"Must be a .webp file.","no_overlapping_campaigns":"Campaign dates overlap with another existing sale campaign.","overlapping_price_rules":"Discount rules overlap for the same conditions (lines %{first} and %{second}).","parse_discount_rules_text":"Line %{index} missing required 'discount:'","starts_at_in_past":"Start date cannot be in the past."},"sale_page":{"meta":{"description":"Quality 3D models available with loyalty discounts of up to 30% on CGTrader","title":"Buy quality 3D models with loyalty discounts. Up to 30% off"},"twitter_shareable_text":"Quality 3D models available with loyalty discounts of up to 30% on CGTrader #discounts #3d #3dmodels #cgtrader\n"},"scholarships":{"annual_scholarship":{"description_html":"\u003ca href='/' target='_blank'\u003eCGTrader\u003c/a\u003e is the leading 3D content provider globally, running an online 3D model marketplace alongside with custom modeling platform and mass 3D modeling solutions for enterprise customers. \u003ca href='/' target='_blank'\u003eCGTrader\u003c/a\u003e gives talented 3D designers the opportunity to showcase their work, get valuable feedback from the community, learn from their peers and interact in a designer-friendly environment. Every year \u003ca href='/' target='_blank'\u003eCGTrader\u003c/a\u003e challenges students to dig into the field of technology by writing an essay on how innovative technologies are transforming our lives. Enter the \u003ca href='/' target='_blank'\u003eCGTrader\u003c/a\u003e Scholarship 2019 challenge to win $3,000 for your education bills. The best submission will be awarded $2,000 while the two runners-up will receive $500 each.\n","meta":{"title":"Annual scholarship"}},"meta":{"title":"Scholarship"}},"search":{"not_found":"Search subscription not found","subscription_created":"Search subscription created","successfully_unsubscribed":"You have successfully unsubscribed from the search subscription"},"settings":{"payment_agreement_lock":"We are currently processing payouts. Once done, you will be able to change your payout method for the next month.\n","payment_agreement_lock_no_agreement":"We are currently processing payouts. Once done, you will be able to fill your payment agreement to start receiving your earned income.\n"},"subscription_opt_ins":{"lock_period_error":"Some items cannot be opted out before the six-month lock period ends.","missing_items_error":"Items not found: %{items}."},"subscriptions":{"extra_usage_payment_form":{"item_bullets":{"first":"25 extra generations","second":"Don't expire or reset monthly","third":"Used after you've reached your monthly generation limit"},"item_title":"Extra Usage purchase (one-time payment)"},"flash":{"active_subscription":"You already have an active subscription.","premium_upgrade_unavailable":"Upgrading to Premium is temporarily unavailable. Please try again later.","update_payment_method":"Your subscription has a payment issue. Please update your payment method instead of buying a new subscription."},"item_page":{"essentials":{"bullets":{"first":"Designed to meet your everyday needs","second":"Download up to %{downloads} models per month","third":"Access a library of %{count} premium models"}},"premium":{"bullets":{"first":"Download up to %{downloads} models per month","second":"Access a library of %{count}+ total accessible models","third":"Unlock professional-grade models not included in Essentials"}}},"payment_form":{"current_price":"%{price}","features":{"first":"%{downloads} assets / month — download models or generate with AI","second":"Instant access to %{models_count}+ essential 3D models","third":"Royalty-free, No AI"},"from_price_label":"from","original_price":24.99,"per_model_label":"per model","plan_name":"CGTrader Essentials Subscription — annual plan, billed monthly","premium":{"features":{"first":"%{downloads} assets / month — download models or generate with AI","fourth":"Royalty-free, No-AI license","second":"Instant access to %{models_count}+ total accessible models","third":"Professional-grade models not included in Essentials"},"original_price":34.99,"plan_name":"CGTrader Premium Subscription — annual plan, billed monthly"},"price_unit":"/month","vat_amount":"%{vat_amount}"}},"support":{"array":{"last_word_connector":", and ","two_words_connector":" and ","words_connector":", "}}
16518,"tag":{"description":{"anime":"%{found_items_count} anime 3D models available for download. Looking for free anime 3D models? We have them as well. All models are ready for animation, games and VR / AR projects. Use filters to find rigged, animated, low-poly or free 3D models. Available in any file format including FBX, OBJ, MAX, 3DS, C4D","bottle":"Scientists say that bottle of wine a day is not bad for you, but when it comes to stronger drinks like whiskey or bourbon, we should really watch our alcohol intake. Obviously, a glass of champagne at the bar looks way cooler than a plastic bottle of water, but think about the possibility of living a longer life and beer will seem like a reasonable sacrifice. This category offers a wide selection of bottle 3D models, ranging from alcohol-related objects like wine racks and corkscrews to all types of bottle 3D models (from shampoo to olive oil).","brain":"The brain is one of the largest and most complex organs in the human body. Due to its intricate structure and function, the anatomy of the brain is also very complex. However, scientists dig deeper into the architecture of human brain, uncovering mind-boggling secrets. A wide range of realistic and detailed brain 3D models that are ready for computer graphics applications like advertising, 3D visualization, 3D games, web and any other field related to 3D design is available for download.","building":"It's unclear where modern architects take their inspiration from, but some of the world famous buildings are so cutting edge that they look like visions from the future. And we don't just mean the exterior part... In capable hands a standard building construction becomes a breathtaking architectural creation with innovative interior design regardless of whether it's an office or an urban building. Our selection of building 3D models is ready for download and can be applied in various fields - 3D animation, graphic design, advertising, 3D games or web.","camera":"Photography and filming is currently one of the most popular career fields among young people, but not everyone with a professional camera is an artist or a photographer. With the quality of phone cameras improving year on year, we see people taking pictures in every step and those who have the newly-developed image editing skills are truly celebrated. Life does look a little different when you're on the other side of the lens, but let's not get carried away... A wide range of detailed camera 3D models is available for download to be used by graphic designers, animation and game developers and professionals working in other 3D-related fields.","car":"Looking at these very detailed and highly realistic 3D car models, you can almost hear the engine roar! A wide range of vehicle 3D models can make your life so much easier, if you're grappling with computer graphics, animation or any other 3D-related field.","cell":"Scientists have estimated that human body contains from 5 billion to 200 million trillion cells. Plants and animals have cells, too. We don't want to bore you with this biology stuff, but the wide range of cell 3D models that we have covers everything from blood, virus and DNA to bone marrow and cell walls. Isn't that...nerve-racking?","chair":"We take sitting in chairs for granted and don't always notice how much charm they can add to our home or office interior. Chair is actually one of the central pieces of furniture in any household. Here you can find high quality and eye-catching design chair 3D models that can be applied in various fields - from advertising and 3D visualization to 3D games.","city":"City life is an eccentric game: streets bursting with racing taxis, modern buildings, pedestrians in perfectly tailored office clothes, sleeping bridges and parks... From Shanghai, to New York (Manhattan!), to London or Paris - every city has its own magic. You can choose from this rich selection of detailed city 3D models that can be used in a number of fields - advertising, computer graphics, animation, 3D visualization or games. Explore the urban life.","dragon":"Dragon is probably the most popular creature living in the fantasy world. It has featured in so many myths of various cultures, animations, movies and books that it has become legendary. It is especially loved by game developers, as it adds that magical charm to dark caves and dungeons. A wide range of realistic, rigged dragon 3D models that are ready for computer graphics applications like advertising, 3D visualization, 3D games, web and any other field related to 3D design is available for download.","earth":"The level of detail in the recently added earth view in Google maps has shocked a number of people. You can explore the whole planet - oceans, mountains and deserts, discover cities and hidden roads without leaving your house. That's some incredible things Mother Earth hasn't experienced before. A selection of realistic earth 3D models that are ready for computer graphics applications like advertising, 3D visualization, 3D games, web and any other field related to 3D design is available for download.","engine":"Some people are able to name the engine by simply listening to the sound it makes, and that is truly amazing. Regardless of whether it's a jet engine or a car engine, they're extremely complicated machines that we use so often, but don't spend too much time thinking about it. A motor is a motor (until it stops working). This category has a range of different types of engine 3D models, and it also includes engine parts like propeller or cylinder, so we're confident you will find what you need.","female":"It's no joke that there are significant differences between male and female brain, which means that it is normal that an average man and woman behave, feel and respond in completely different ways. But it's not all about the anatomy. No love story, film or game ever pulled it off without a strong girl character, cause the power of lady charm is undeniable. A rich selection of realistic female 3D models can offer you everything you need - from nude 3D models to female respiratory system.","hand":"The reason we use our hands for so many things is their extraordinary anatomy. The thumb alone is controlled by nine separate muscles. It's hard to believe, but early hands were more exotic than any hand today - some species had seven fingers. Human body has significantly evolved. An arm or a hand as we see it today looks nothing like its early versions. A wide range of realistic, rigged hand 3D models that are ready for computer graphics applications like advertising, 3D visualization, 3D games, web and any other field related to 3D design is available for download.","head":"In human anatomy, the head is the upper portion of the human body. Modelling a 3D human head can prove a little complicated, because (even though there are so many different shapes and sizes) we appreciate symmetric, realistic-looking heads - no funny eyes, ears, nose, lips or jaw line, please. Face is a sensitive area and it would just be plain rude to have fun with that. Our selection encompasses realistic male, female, child and even story characters like Harry Potter and Batman or celebrities like Tom Cruise and Scarlett Johansson heads that are ready for download.","helmet":"Always remember that health \u0026 safety comes first, so protecting your head is a sound idea. Medieval knights, Daft Punk and even the Ironman wear helmets, so regular superheroes like cyclists or builders should not worry about looking uncool. This collection of helmet 3D models that is available for download will prove useful if you're working in the fields of graphic design, 3D animation, 3D games or visualization.","house":"It's an old truth that a man's home is his castle, but when it comes to building that perfect house or buying an ideal apartment, we do tend to lose our heads over it. Luckily, this wide spectrum of house 3D models that encompasses everything from architectural details, interior and exterior, to furniture, will help you create modern 3D visualizations to ease that pain.","human":"When you start exploring the human anatomy, you realize that people are such intricate and complex machines that it takes your breath away. Every body part, muscle, joint, organ or bone combination is connected into one system, nothing works in isolation! These super realistic 3D human models will allow you to examine anatomical structures from every angle.","lamp":"If you look around your home and count the types of lamps that you own, you will be surprised: reading, table, wall, ceiling, desk, tripod lamps... It's enough to make your head spin! Not to mention the styles that they come in - from extravagant modern lamps to retro or stylish futuristic. In this category we have all kinds of lamp 3D models, so browse and download to use in graphic design, 3D visualizations or any other 3D-related field.","light":"Have you ever wondered why a light bulb is the symbol of a brilliant idea? Surely, a bright idea can enlighten the architecture of the mind, but that is not it. A street light, a chandelier or any other type of lamp would never stand a chance to take that spot. If the next time when you look up at the ceiling and stare at some kind of architectural light the genius idea doesn't hit you, it might be because you're staring at a wrong lightning system... Our selection of high quality light 3D models will prove handy if you work in the fields of architecture, computer graphics, 3D animation or design.","machine":"Imagine if all the machines suddenly stopped working: no coffee, no convenient laundry washing, no cash points on a corner of the street, no gym time, no overpriced vending machines... It does sound depressing, doesn't it? We are so used to these home appliances and electronic gadgets that it would literally stop the world turning! Luckily, our selection of detailed machine 3D models covers everything from machine guns to electricity generators that will prove useful to de
16518signers working with computer graphics, advertising, 3D animation, 3D games or web.","man":"A vast collection of man 3D models is available for download. Sitting man, simple man, flying man or average man, we've got everything - except that perfect man that everyone's after. He's a fishy character and we hate to bring this up, but it might be a myth... But hey, human beings are born to be awesome, not perfect.","robot":"R2D2 is probably the most famous robot in the world, but it's certainly not the only one. From industrial to toy to human robots, we see an increasingly wider choice of these mechanical machines, which is both wonderful and a little worrying. It's not too difficult to understand the people who insist that computers, technology, robots are making a too significant impact on human beings. A wide range of realistic, rigged robot 3D models that are ready for computer graphics applications like advertising, 3D visualization, 3D games, web and any other field related to 3D design is available for download.","ship":"Ships are powerful vessels and come in all sizes, colors and shapes. They can be classified as cargo ships, warships or passenger ships, but they all operate in deep sea waters. If you have experienced the discomfort of motion sickness, it may not be a your favorite means of transport, but many people regard boat trips as quite classy and enjoyable. Our selection of ship 3D models is ready for download and will meet the standards of even the most demanding captain.","skeleton":"The human skeleton or skeletal system (anatomy) is the internal framework of the body that is composed of 270 bones at birth. It provides the body structure, protection and movement and is one of the most complex skeletal systems. A rich selection of anatomically correct, realistic and scaled skeleton 3D models that are perfect for use in 3D animation, graphic design, advertising, 3D games and web is available for download.","skull":"The oldest most complete, genetically intact human skull was discovered surrounded by a variety of extinct animals more than 40 meters below sea level. It is one of the oldest skeletons found in the New World and it also includes all of the major bones of the body and an intact cranium and set of teeth. Human skulls have recently become a thing in fashion and design, but when you actually give it some thought, they kind of make us all look like anatomy freaks... This category offers a wide range of skull 3D models to be used in graphic design, 3D animation, 3D games or advertising.","sofa":"Luxury or simple, modern or antique, leather or minimalist design? How do you like your sofa? The range of sofa 3D models that we have will satisfy even the worst perfectionist. When you stop to think about it, sofa is the center of every living room, so you must be creative when choosing the design!","table":"The newest furniture trends are so diverse that choosing a modern, fashionable table is becoming too stressful. There used to be a time when you had a simple dinner table for your modest feasts and that was enough, but today - table is supposed to be a statement or at least an eye-catching interior accent. This selection gives you a vast range of table 3D models to cater to every whim.","tower":"Every city has a special tower that everyone's really proud of. Think Eiffel tower or Pisa tower, or London tower bridge, let alone all the modern skyscrapers, futuristic buildings and the urban landscape that tourists admire. However, when it comes to the industrial parts of the city, water or electrical towers, everyone tend to turn their eyes away...But we should learn to appreciate every architectural creation, no matter ancient, industrial or modern. A range of detailed tower 3D models are available for download and ready for computer graphics applications like advertising, 3D visualization, 3D games, web and any other field related to 3D design.","tree":"If you're on a mission to plant some trees or exotic plants, you've come to the right place. This category offers a range of easy-to-use, very realistic 3D tree models to CG artists and architects, who are striving to add some greenery to their projects. If you appreciate good quality, we're confident this selection of 3D tree plant models will meet your standards!","truck":"Driving a truck is a tough job, especially if you need to find your way through a city traffic jam. Regardless of whether it's a firetruck, garbage truck, food truck or a generic one, it takes a great skill to maneuver these giant vehicles. We are confident that this selection of realistic, high quality truck 3D models are perfect for use in 3D animation, graphic design, advertising or any other 3D-related field.","tv":"There used to be a time when fireplace was the centerpiece of every respectable home, now TV set with players and speakers is the king of the living room (and even the bedroom). In recent years television has caught up with 
16518newest technologies. As a result, we have smart 3D TV, flat and plasma screens, million new functions and touchscreen design. No wonder television is pushing cinema out of the market. A wide range of realistic, detailed TV 3D models that are ready for computer graphics applications like advertising, 3D visualization, 3D games, web and any other field related to 3D design is available for download.","woman":"%{found_items_count} woman 3D models available for download. Looking for free woman 3D models? We have them as well. All models are ready for animation, games and VR / AR projects. Use filters to find rigged, animated, low-poly or free 3D models. Available in any file format including FBX, OBJ, MAX, 3DS, C4D"}},"tag_promotion":{"days_left_error":"must be between 1 and 30","no_other_pending_promotions_for_same_item_tag":"already has a pending promotion"},"three_d_studio":{"flash":{"run_unavailable":"This 3D model is no longer available."},"seo":{"gallery":{"description":"Explore AI-generated 3D models created by the CGTrader community. Browse, download and create AI 3D assets in FBX, OBJ, STL and other formats.","title":"AI 3D Models Generated By Community | CGTrader AI"},"images":{"description":"Explore AI-generated images created by the CGTrader community. Browse, download and turn AI images into 3D models with CGTrader AI.","title":"AI Images Generated By Community | CGTrader AI"},"index":{"description":"Use text prompts or image references to generate production-ready 3D models for any use case.","title":"3D AI Generator | CGTrader"},"landing":{"description":"AI 3D model generator that WORKS! Generate production-ready 3D models in seconds with CGTrader AI. Turn text prompts or images into detailed 3D assets.","title":"AI 3D Model Generator - Text \u0026 Image to 3D | CGTrader"},"output":{"description":"AI-generated %{name} 3D model. Download or remix this asset created by CGTrader AI using text prompts or image references.","title":"%{name} 3D model | CGTrader AI"}}},"time":{"am":"am","formats":{"datetime":"%Y-%m-%d %H:%M","default":"%Y-%m-%d","long":"%B %d, %Y","short":"%b %d"},"pm":"pm"},"time_zones":[{"value":"UTC − 12:00","label":"UTC − 12:00"},{"value":"UTC − 11:00","label":"UTC − 11:00"},{"value":"UTC − 10:00","label":"UTC − 10:00"},{"value":"UTC − 09:30","label":"UTC − 09:30"},{"value":"UTC − 09:00","label":"UTC − 09:00"},{"value":"UTC − 08:00","label":"UTC − 08:00"},{"value":"UTC − 07:00","label":"UTC − 07:00"},{"value":"UTC − 06:00","label":"UTC − 06:00"},{"value":"UTC − 05:00","label":"UTC − 05:00"},{"value":"UTC − 04:00","label":"UTC − 04:00"},{"value":"UTC − 03:30","label":"UTC − 03:30"},{"value":"UTC − 03:00","label":"UTC − 03:00"},{"value":"UTC − 02:00","label":"UTC − 02:00"},{"value":"UTC − 01:00","label":"UTC − 01:00"},{"value":"UTC ± 00:00","label":"UTC ± 00:00"},{"value":"UTC + 01:00","label":"UTC + 01:00"},{"value":"UTC + 02:00","label":"UTC + 02:00"},{"value":"UTC + 03:00","label":"UTC + 03:00"},{"value":"UTC + 03:30","label":"UTC + 03:30"},{"value":"UTC + 04:00","label":"UTC + 04:00"},{"value":"UTC + 04:30","label":"UTC + 04:30"},{"value":"UTC + 05:00","label":"UTC + 05:00"},{"value":"UTC + 05:30","label":"UTC + 05:30"},{"value":"UTC + 05:45","label":"UTC + 05:45"},{"value":"UTC + 06:00","label":"UTC + 06:00"},{"value":"UTC + 06:30","label":"UTC + 06:30"},{"value":"UTC + 07:00","label":"UTC + 07:00"},{"value":"UTC + 08:00","label":"UTC + 08:00"},{"value":"UTC + 08:45","label":"UTC + 08:45"},{"value":"UTC + 09:00","label":"UTC + 09:00"},{"value":"UTC + 09:30","label":"UTC + 09:30"},{"value":"UTC + 10:00","label":"UTC + 10:00"},{"value":"UTC + 10:30","label":"UTC + 10:30"},{"value":"UTC + 11:00","label":"UTC + 11:00"},{"value":"UTC + 12:00","label":"UTC + 12:00"},{"value":"UTC + 12:45","label":"UTC + 12:45"},{"value":"UTC + 13:00","label":"UTC + 13:00"},{"value":"UTC + 14:00","label":"UTC + 14:00"}],"tips":{"tip":{"build_links":"Build links from other sites to your models on CGTrader. It’s the best way to increase sales","popular_formats":"Export your models to MAX, FBX, OBJ, STL file formats. These are the most popular file formats.","preview_images":"Render at least 5 final and 3 wireframe preview images. It will increase your sales by 5.5x times.","rich_descriptions":"Write informative descriptions. Models with rich descriptions sell up to 1.6x more than without descriptions."}},"tooltips":{"buyer_loyalty_program_current_level_html":"Currently you have spent %{total_price_paid} on CGTrader and your Points earn rate is %{buyer_loyalty_rate}. \u003cbr/\u003e It means that you will get %{buyer_loyalty_rate} of points from each purchase.\n","buyer_loyalty_program_html":"This is the percentage of how much you will get from your purchases as new points. \u003cbr/\u003e It will increase up to 30% as your lifetime spending on CGTrader increase. \u003cbr/\u003e Currently your Points earn rate is %{buyer_loyalty_rate}.\n","buyer_loyalty_program_next_level_html":"Next level %{next_lvl_expenses} will increase your Points earn rate up to %{next_lvl_expenses_rate}. \u003cbr/\u003e You need to purchase for $%{left_until_next_lvl} more to reach next level.\n","buyers_sign_in_page":{"bonus_credits_tooltip_html":"These points will take priority over your permanent Points - meaning they will be counted first. \u003cbr/\u003e \u003ca href=\"/pages/cgtrader-loyalty-discounts\" class='js-tooltip-link' target='_blank'\u003eLearn more\u003c/a\u003e\n","cgtrader_credits_tooltip_html":"Every dollar spent on CGTrader brings you money back for future purchases. \u003cbr/\u003e \u003ca href=\"/pages/cgtrader-loyalty-discounts\" class='js-tooltip-link' target='_blank'\u003eLearn more\u003c/a\u003e\n","pro_tip_tooltip_html":"You can also earn Bonus Points by adding more models to the cart. \u003cbr/\u003e \u003ca href=\"/pages/cgtrader-loyalty-discounts\" class='js-tooltip-link' target='_blank'\u003eLearn more\u003c/a\u003e\n","sale_campaign_tooltip_html":"However, your loyalty level and points earn rate can be increased. \u003cbr/\u003e \u003ca href=\"/pages/cgtrader-loyalty-discounts\" class='js-tooltip-link' target='_blank'\u003eLearn more\u003c/a\u003e\n"},"common_credits_used_tooltip_html":"\u003cdiv class=\"u-text-left\"\u003e\n  %{storeCreditsUsedText}\n  %{temporaryCreditsUsedText}\n  Total: \u003cstrong\u003e%{totalCreditsUsed}\u003c/strong\u003e\u003cbr/\u003e\n  \u003cbr/\u003e\n  Points can be used when purchasing \"$\" sign marked models and cover up to 30% discount of model's price.\n\u003c/div\u003e\n","low_cart_value_html":"Points can only be used if the total value of your cart is equal to or greater than %{minCartValue}\n","no_credits_used_html":"\u003cdiv class=\"u-text-center\"\u003e\n  Points can be used when purchasing \"$\" sign marked models and cover up to 30% discount of model's price.\n\u003c/div\u003e\n","product_analytics":{"description_html":"A good description can make or break the deal - it lets your customer get an informative explanation about the model technical details before purchasing your asset. Make sure you have as detailed description as possible (100 - 200 words is recommended).\n","formats_html":"File Formats are very important for sales as different customers use different software. The most demanded formats are FBX, OBJ, MAX, STL, ZTL. We recommend uploading at least 3 different formats with FBX and OBJ included.\n","images_html":"Images are essential and have very high impact on sales and conversion. Our data shows that models with 20+ images sell 5 - 10 times better than those with just 1. Render as much images as possible and include at least several wireframe images that show topology.\n","models_score_html":"The Publishing Score is calculated by taking the number of unique file formats, number of images, model tags, word count in description. The scope of the score is 0-10 - with 10 being the highest.\n","tags_html":"Model tags help your assets show up in the search and attract traffic. Provide at least 10-20 related tags s
16518o that customers could find your model.\n"},"publisher":{"adult_content":"All models with nudity or violent content will be reviewed prior to publishing by CGTrader within 1–3 business days. CGTrader uses RSAC standards to evaluate content. Level 0 content is available to everyone. Models classified as Level 1-2 can be published under an age lock. Models classified as Level 3-4 are not approved for publishing on the marketplace.\n","description":"Write a proper description with relevant technical information about your product to drive more traffic and customers from around the web and increase buyers' confidence.\n","embeds":"Products with embedded video previews are sold significantly more often. If your asset is animated, make sure to embed a demo of those animations – it will make your model even more irresistible.\n","gaussian_splats":"Gaussian Splats point-based scenes only. Raw scan point clouds → 3D scanned models.","images":"Good preview images earn more sales. Add detailed preview images of your product, including wireframe preview images, from all angles. Add lighting and shadows to let buyers see the final look of the model. Your preview images must not contain any assets or details not included in the product. Use a neutral background to focus the buyers’ attention on the product.\n","license_html":"\u003cul\u003e\n  \u003cli\u003e\u003cstrong\u003eEditorial license\u003c/strong\u003e should be applied when you do not want your model to be used in commercial projects and only educational, journalistic and otherwise newsworthy uses are allowed.\u003c/li\u003e\n  \u003cli\u003e\u003cstrong\u003eRoyalty Free License\u003c/strong\u003e allows customers purchasing/downloading your product to use it for both commercial and noncommercial purposes for unlimited time and with no further royalty and other licensing fees to be paid to you. The buyer, however, is not allowed to sell, give, or assign your product to another person or entity in the form it is downloaded from the site.\u003c/li\u003e\n  \u003cli\u003e\u003cstrong\u003eCustom license\u003c/strong\u003e should be applied when you want to impose extra terms on the usage of product and must contain exact conditions. If no terms are provided, Royalty Free License will be assumed.\u003c/li\u003e\n\u003c/ul\u003e\n","tags":"Add specific words that target your model, broad words which will generalize it and note its qualities or characteristics (like color, material, condition, etc). Do not add unrelated tags to your product.\n","technical_details":"Help buyers decide to purchase your product – state the software, polycount, maps, scripts and plugins you used to create the product.\n","title":"Write a relevant and specific title, which clearly represents and distinguishes your model. Keep the title length at 4-6 words: note the general category (e.g. ‘car engine’) and distinguish its type (e.g. ‘vintage V8’).\n"},"store_credits_used_html":"\u003cstrong\u003e%{storeCreditsUsed}\u003c/strong\u003e points\u003cbr/\u003e\n","temporary_credits_used_html":"\u003cstrong\u003e%{temporaryCreditsUsed}\u003c/strong\u003e bonus points (%{bonusCreditsTitle})\u003cbr/\u003e\n"},"trending_headers":{"models":"Check other trending 3D models","print":"Check other trending 3D print models"},"tutorial":{"aasm_state":{"draft":"Draft","published":"Published","under_review":"Under review"},"destroy":"Tutorial was successfully destroyed.","submit_for_review_error":"Please fill in all required fields.","submitted":"Thank you for submitting your tutorial. It will be reviewed and published shortly."},"users":{"active_user_not_found":"Active user could not be found.","banned":"User has been banned successfully","cannot_be_deleted":"Account cannot be deleted. Please contact our support team at [email protected].","confirmation":"Thanks for completing your registration. Please check your inbox and confirm your email address to continue.\n","content":{"cg":"3D Models","free":"Free 3D Models","low_poly":"VR / Low-Poly models","printable":"3DP Models"},"deleted":"Account has been deleted successfully","email":{"admin_changed":{"success":"Email has been successfully changed"}},"expertise":{"categories":"Categories","skills":"Expertise","software":"Preferred Tools"},"instructions":{"account_settings":{"subtitle":"Review your sales settings","text":"Let customers know whether you accept price offers or file conversion requests.","title":"Account settings"},"analytics_about_page":{"subtitle":"Marketplace Analytics","text":"View real-time insights about the market from millions of searches, purchases and models available on CGTrader.","title":"Analytics About page"},"batch_uploader":{"subtitle":"3D Model batch","text":"Upload multiple models or even your whole portfolio in one sitting!","title":"Batch uploader"},"empty_search":{"subtitle":"Browse other models","text":"See what other designers are selling on the marketplace.","title":"Empty search"},"payment_agreement_page":{"subtitle":"Sign your payment agreement","text":"Enter your payment details and prefered payment methods.","title":"Payment agreement page"},"projects_listing_page":{"subtitle":"Freelance 3D Projects","text":"Apply for unique 3D Projects – build your portfolio and earn extra money.","title":"3D Projects listing page"},"publisher":{"subtitle":"Upload another model","text":"Become more visible on the marketplace.","title":"Publisher"},"seller_handbook":{"subtitle":"Seller Handbook","text":"Find information about everything from earning rates to model presentation guidelines.","t
16518itle":"Seller handbook"}},"location_updated":"Location has been updated successfully","password":{"messages":{"send_failure":"We were not able to send you an e-mail with password recovery instructions! Please contact our support","send_success":"Password recovery instructions have been sent to your e-mail"}},"payout_options":{"monthly":"Monthly","undecided":"Undecided","weekly":"Weekly"},"removal":{"missing_params":"Unable to remove messages, please provide all required parameters","scheduled":"Records are scheduled for removal (type `%{type}`)"},"stats":{"followers_count":"Followers","items_count":"Models","likes_count":"Likes","message_response_rate":"Response Rate","message_response_time_avg":"Average Response Time","pageviews":"Views","reputation":"Reputation Score"},"suspended_messaging":"Due to continuous breaching of CGTrader communication policy, your ability to send messages was suspended. If you want to solve this issue, please contact CGTrader support.\n","unbanned":"User has been unbanned successfully","username":{"admin_changed":{"success":"Username has been successfully changed"},"changed":{"success":"Your username has been changed successfully."}}},"valvat":{"country_adjectives":{"at":"Austrian","be":"Belgian","bg":"Bulgarian","cy":"Cypriot","cz":"Czech","de":"German","dk":"Danish","ee":"Estonian","es":"Spanish","eu":"European","fi":"Finnish","fr":"French","gb":"British","gr":"Greek","hu":"Hungarian","ie":"Irish","it":"Italian","lt":"Lithuanian","lu":"Luxembourgian","lv":"Latvian","mt":"Maltese","nl":"Dutch","pl":"Polish","pt":"Portuguese","ro":"Romanian","se":"Swedish","si":"Slovenian","sk":"Slovakian"}},"wildcat_referrals":{"invitation_template_html":"Hi %{recipient_username},\n\nYour friend %{sender_username} is inviting you to join CGTrader’s freelance platform wildcat - take a look at some of the requests that should fit your type of work in %{specialization}!\n\n%{invitation_link}\n\nBest,\nCGTrader and %{sender_username}\n"},"will_paginate":{"container_aria_label":"Pagination","next_aria_label":"Next page","next_label":"Next \u0026#8594;","page_aria_label":"Page %{page}","page_entries_info":{"multi_page":"Displaying %{model} %{from} - %{to} of %{count} in total","multi_page_html":"Displaying %{model} \u003cb\u003e%{from}\u0026nbsp;-\u0026nbsp;%{to}\u003c/b\u003e of \u003cb\u003e%{count}\u003c/b\u003e in total","single_page":{"one":"Displaying 1 %{model}","other":"Displaying all %{count} %{model}","zero":"No %{model} found"},"single_page_html":{"one":"Displaying \u003cb\u003e1\u003c/b\u003e %{model}","other":"Displaying \u003cb\u003eall\u0026nbsp;%{count}\u003c/b\u003e %{model}","zero":"No %{model} found"}},"page_gap":"\u0026hellip;","previous_aria_label":"Previous page","previous_label":"\u0026#8592; Previous"}},"es":{"activerecord":{"attributes":{"content_report":{"categories":{"adult":"Adulto o contenidos violentos","ai_generated":"Contenido generado por IA","copyright":"infracción de copyright"}}},"errors":{"messages":{"embedded_script":"Este archivo no es compatible y no se puede subir. Prueba con otro archivo."}}},"api":{"profile":{"actions":{"contact":"Contactar","follow":"Seguir","follow_tooltip":"Start following this designer to get updates about his content and easily filter favorite designers in Search results","hide":"Ocultar","hire_me":"Contrátame","invite_client":"Iniciar un proyecto","mute_tooltip":"Hide this designer’s models in the Search results","take_over":"Take over","unfollow":"Dejar de seguir","unhide":"Mostrar","view_all_models":"View all Designer's Models"},"private_project_review":"Private Project"}},"browser":{"3d_models":"modelos 3D","3d_print":"impresión 3D","active_filters":{"polygons":"%{range} polígonos"},"adult_content":"Ver Edad +","animated":"Animado","breadcrumb":{"browse_by_type":"Explorar por tipo","collection":"packs 3D","free":"gratis","free_models":"Modelos en 3D gratuitos","free_print_models":"Modelos en 3D gratuitos de impresión","low_poly":"bajo poli","low_poly_models":"VR / AR / bajas-poli 3D Modelos","low_poly_print_models":"Modelos 3D de impresión Low-Poly","model_3d":"modelo 3d","model_3dp":"modelo de impresión 3D","models":"Modelos 3D","models_3d":"modelos 3D","models_3dp":"modelos de impresión 3D","models_variant_0":"Modelos 3D","models_variant_1":"Recursos 3D","models_variant_2":"Modelos VR","models_variant_3":"Modelos CG","models_variant_4":"Tienda de modelos 3D","models_variant_5":"Objetos 3D","print_models":"Archivos de impresión 3D","print_models_variant_0":"Modelos para impresión 3D","print_models_variant_1":"Modelos para impresoras 3D","print_models_variant_10":"Modelos 3D para impresión","print_models_variant_11":"Modelos 3D para imprimir","print_models_variant_12":"Archivos 3D para impresión","print_models_variant_13":"Modelos para impresora 3D","print_models_variant_14":"Modelos para impresión 3D","print_models_variant_15":"Modelos de impresión 3D","print_models_variant_16":"Archivos 3D imprimibles","print_models_variant_2":"Modelos 3D para imprimir","print_models_variant_3":"Modelos de impresión 3D","print_models_variant_4":"Archivos de impresión 3D","print_models_variant_5":"Archivos para impresión 3D","print_models_variant_6":"Archivos de impresora 3D","print_models_variant_7":"Modelos 3D imprimibles","print_models_variant_8":"Modelos 3D para imprimir","print_models_variant_9":"Modelos 3D impresos"},"buyer_engagement_program":"descuentos","categories":"categorías","category":{"meta":{"3d_models":"%{count} %{category} 3D modelos disponibles para su descarga en cualquier formato de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.%{page}","3d_print_models":"Descarga %{count} %{category} 3D modelos de impresión, disponible para su descarga en STL, OBJ y otros formatos de archivo, listo para imprimir con FDM, SLS y otras impresoras 3D.%{page}","animated_3d_models":"%{count} %{category} de animación en 3D modelos disponibles para su descarga en cualquier formato de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.%{page}","free_3d_models":"Descarga %{count} %{category} gratuitas 3D modelos, disponibles en MAX, OBJ, FBX, 3DS, formatos de archivo C4D, listos para VR / AR, animación, juegos y otros proyectos en 3D.%{page}","free_3d_print_models":"Descarga %{count} %{category} modelos libres de impresión 3D listos para la FDM, SLA, SLS y otras impresoras 3D, formatos disponibles STL, OBJ, 3DM y más.%{page}","lowpoly_3d_models":"Descargar %{count} VR / AR / bajo poli %{category} de modelos 3D listo para la realidad virtual, juegos y otras aplicaciones en tiempo real, disponible en FBX, OBJ, MAX, 3DS, formatos de archivo C4D. %{page}","rigged_3d_models":"%{count} %{category} aparejado 3D modelos disponibles para su descarga en cualquier formato de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.%{page}"}},"collection":"pack 3D","discounts":"descuentos","ext":{"meta":{"3d_models":"Descarga %{rounded_count} modelos 3D en formato %{format_upper}. Archivos %{format_lower} gratis y premium.","3d_print_models":"Descarga %{count}% {ext} archivos - %{software_name} modelos de impresión 3D listos para VR / AR, diseño 3D, animación, juegos y aplicaciones en tiempo real%{page}."}}
16518,"filter":"Filtrar","filter_button":"aplicar filtros","filters":{"adult_content_only":"Solo contenido para adultos","cgt_standard_only":"CGT Standard","collections_only":"Solo paquetes","content_filters":"Filtros de contenido","designer":"Diseñador","exclude_3d_print":"Excluir impresión 3D","exclude_adult_content":"Excluir contenido para adultos","exclude_ai_generated":"Excluir contenido generado por IA","exclude_editorial_license":"Excluir licencia editorial","exclude_free":"Excluir los gratuitos","file_format":"Formatos de archivo","following":"Siguiendo","free":"Gratis","has_marmoset":"Con visor 3D","has_video":"Con video","include_ai_generated":"Incluir contenido generado por IA","label":"Filtros","poly_count":"Cantidad de polígonos","print_ready":"Impresión 3D","stl_files":"Archivos STL","subscription":"Suscripción","type":"Tipo"},"formats":"formatos","free":"Gratis","free_models":"Mostrar sólo los modelos gratuitas","free_to_premium":{"main":"modelos 3D de %{category}","postfix":"en el listado premium.","prefix":"Necesitas algo especial? Puedes encontrar una mejor calidad"},"gallery":{"preview":"vista previa"},"game_ready":"juego listo","high_poly":"De alta poli","items_page_title":"Modelos 3D","license":{"exclude_branded":"Excluir Branded","exclude_editorial":"Excluir editorial","prompt":"Licencia","tooltip_more":"Lee mas","tooltip_text":" No mostrar los modelos que sólo pueden ser utilizados en fines no comerciales, educativos.","tooltip_title":"Excluir editorial:"},"low-poly":"Low-poli","low_poly":"Low-poli","meta":{"3d_models":"Descarga %{rounded_count} modelos 3D profesionales. Modelos gratis y premium para juegos, cine, VFX, AR/VR e impresión 3D. FBX, OBJ, BLEND, GLB y más.","3d_print_models":"Descarga %{rounded_count} modelos 3D para imprimir. Archivos STL gratis y premium para impresora 3D: figuras, miniaturas, piezas, decoración y más.","animated_3d_models":"Descarga %{rounded_count} modelos 3D animados con animaciones incluidas. Personajes y objetos game-ready en FBX, BLEND y GLB.","free_3d_models":"Descarga %{rounded_count} modelos 3D gratis, libres de derechos. Objetos 3D y archivos gratuitos en FBX, OBJ, BLEND y GLB para tus proyectos.","free_3d_print_models":"%{rounded_count} archivos STL gratis para impresión 3D. Descarga figuras y modelos 3D para imprimir gratis en tu impresora 3D.","lowpoly_3d_models":"Descarga %{rounded_count} modelos 3D low poly para juegos, AR/VR y render en tiempo real. Gratis y premium.","metaverse":"Ingrese el Metaverse con nuestra colección de modelos 3D de alto y bajo poli manipulado, animado y disponible en cualquier formato de archivo.","omniverse":"Construya su mundo virtual con nuestros modelos 3D listos para Omniverse. Elija entre modelos animados, manipulados y PBR con recuento de polígono alto y bajo.","page_prefix":"Página %{page_number} con","pbr_3d_models":"Descarga %{rounded_count} modelos 3D con texturas PBR para Unity, Unreal y motores en tiempo real.","rigged_3d_models":"Descarga %{rounded_count} modelos 3D riggeados con esqueleto, listos para animar en Blender, Maya, 3ds Max, Unity y Unreal.","snap-ar":"Con nuestros modelos 3D, puede unirse a un millón de Snapchatters usando Snap AR para crear experiencias de realidad aumentada divertidas.","spark-ar":"Alejuee su Facebook e Instagram con una variedad de modelos Spark AR 3D que están manipulados, animados y listos para usar.","stream_3d_print_models":"Navegar a través de una variedad de categorías corriente 3D modelo de impresión para descubrir algunos de los más extraordinarios diseños imprimibles 3D de 3D profesionales creadores.","textures":"%{count} activos Texturas 3D y materiales disponibles para su descarga en varios formatos incluyendo .tga .sbsar .jpeg .png .tiff y otra. %{page}","tiktok":"Haga que sus efectos de video sean más impresionantes con los modelos 3D Tiktok. Nuestra biblioteca puede satisfacer todas sus necesidades creativas y se actualiza constantemente.","unity":"Encuentre modelos 3D para su proyecto Unity en cualquier formato de archivo y cualquier recuento de poli. Todo lo que necesita para crear un entorno virtual único.","unreal":"Cree visualizaciones impresionantes con modelos 3D compatibles con un motor Unreal en cualquier formato de archivo. Nuestra biblioteca tiene modelos bajos y de alto poder de ca
16518da categoría."},"optimized":"Low-poli / PBR","page_title":{"animated_3d_models":"Modelos 3D Animados – Gratis y Premium","format_page":"Modelos 3D %{format_upper} – Archivos %{format_lower} Gratis y Premium","free_3d_models":"Modelos 3D Gratis – Descarga Libre de Derechos","free_3d_print_models":"Archivos STL Gratis – Modelos 3D para Imprimir","lowpoly_3d_models":"Modelos 3D Low Poly – Gratis y Premium","pbr_3d_models":"Modelos 3D PBR – Gratis y Premium","rigged_3d_models":"Modelos 3D Rigged (Riggeados) – Gratis y Premium","software_page":"Modelos 3D para %{software} – Descarga %{ext_file}","tag":{"3d_merged_premium":"%{tag_cap} 3D – Modelos 3D Gratis y Premium","3d_merged_premium_print":"%{tag_cap} para Imprimir en 3D – Archivos STL","3d_models":"%{tag_cap} 3D – Modelos 3D Gratis y Premium","3d_print_models":"%{tag_cap} para Imprimir en 3D – Archivos STL"}},"pbr":"PBR","per_page":"Por página","polygons_options":{"custom":"Rango personalizado","gt_250k":"250k +","lt_5k":"Hasta 5k","prompt":"recuento de poli","range_0_5000":"Hasta 5K","range_100k_250k":"100k a 250k","range_10k_50k":"10k a 50k","range_50k_100k":"50k a 100k","range_5k_10k":"5k a 10k"},"price":"Precio: $%{from}-%{to}","price_range":"Precio","print_ready":"3D de impresión listo","printable":"impresión 3D","reset_button":"Restablecer filtros","rigged":"Equipado","rounded_count":{"delimiter":".","exact":"%{n}","million":"%{n} M+","separator":",","thousand":"%{n}+"},"sale_off":"En oferta","search_placeholder":"Buscar palabras clave","show_all_formats":"Mostrar todos los formatos","simulation_ready":"Listo para simulación","software":{"meta":{"3d_models":"Descarga %{rounded_count} modelos 3D para %{software}. Archivos %{ext_file} gratis y premium, listos para importar."}},"sort_by":"Ordenar por","sort_by_options":{"best_match":"Mejor partido","highest_price":"Precio más alto","lowest_price":"precios má bajo","newest":"El más nuevo","oldest":"Más antiguo","sales":"Más vendidos","trending":"Tendencias"},"subcategory":{"meta":{"3d_models":"%{count} %{category} 3D modelos disponibles para su descarga en cualquier formato de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.%{page}","3d_print_models":"Descarga %{count} %{category} 3D modelos de impresión, disponible para su descarga en STL, OBJ y otros formatos de archivo, listo para imprimir con FDM, SLS y otras impresoras 3D.%{page}","animated_3d_models":"%{count} %{category} de animación en 3D modelos disponibles para su descarga en cualquier formato de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.%{page}","free_3d_models":"Descarga %{count} %{catgeory} gratuitas 3D modelos, disponibles en MAX, OBJ, FBX, 3DS, formatos de archivo C4D, listos para VR / AR, animación, juegos y otros proyectos en 3D.%{page}","free_3d_print_models":"Descarga %{count} %{category} modelos libres de impresión 3D listos para la FDM, SLA, SLS y otras impresoras 3D, formatos disponibles STL, OBJ, 3DM y más.%{page}","lowpoly_3d_models":"Descargar VR / AR / baja poli %{tag} modelos 3D listo para la realidad virtual, juegos y otras aplicaciones en tiempo real, disponible en FBX, OBJ, MAX, 3DS, formatos de archivo C4D.%{page}","rigged_3d_models":"%{count} %{category} aparejado 3D modelos disponibles para su descarga en cualquier formato de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.%{page}"}},"subtitle":{"aircraft":"Modelos %{count} aviones en 3D están disponibles para su descarga en nuestro mercado. CG %{airplanes_link} encajará en simulaciones, visualizaciones, anuncios y videos. %{drone_link} cuerpos y partes harán las delicias de los amantes de los vehículos voladores pequeños. Y los %{rigged_link} modelos están listos para ser importados en los motores de juego y ser usado para los proyectos de VR / AR animación o. Juegos de vuelo, simuladores y videos del CG tanto regulares como los de bajo poli pueden ser llenados con modelos 3D en la mayoría de los formatos más populares, incluyendo FBX, OBJ, MAX, 3DS, C4d.","animals":"%{count} de animales CG 3D - %{cats_link}, %{horses_link}, %{bears_link}, %{rhinos_link}, %{eagles_link}, %{lions_link},ficticia %{dragons_link} y más - están disponibles aquí para su descarga. La colección de la fauna en 3D va a satisfacer las necesidades de los anuncios o las escenas estáticas. También tenemos %{rigged_link} modelos para juegos, animación, producción de video y VR / AR proyectos. Use filtros para encontrar manipuladas, animada, bajo poli o 3D modelos gratuitas disponibles en la mayoría de formatos de archivo aceptados en la industria, incluyendo FBX, OBJ, MAX, 3DS, C4d, y otros.","animated":"Descarga %{rounded_count} modelos 3D animados con movimientos listos para usar: personajes, animales y objetos para juegos, cine y proyectos en tiempo real.","architectural":"%{count} de los modelos arquitectónicos 3D disponibles para su descarga contiene elementos y detalles de la arquitectura, construcción y diseño de ingeniería. Para comprar o descargar un modelo 3D gratis, navegar por las categorías anteriormente. Puede utilizar modelos para animaciones o juegos - algunos de ellos se aparejan y poli baja para servir VR / AR necesidades del proyecto. modelos en 3D están disponibles en formatos de archivo como FBX, OBJ, MAX, 3DS, C4d.","car":"Modelos %{count} coche en 3D están disponibles para su descarga. versiones de bajo poli de los cuales serán atractivos para los diseña
16518dores de juegos o VR / AR desarrolladores de aplicaciones, otros - para cineastas, animadores o anunciantes. De los coches clásicos y antiguos a las carreras y concepto visto en las películas, hay una gran variedad de modelos de automóviles 3D esencialmente en cualquier formato (FBX, OBJ, MAX, 3DS, C4d, etc.) que pueda necesitar. Utilice animados 3D y gratuitas modelos de prototipo y el tiempo de desarrollo se acortan.","character":"%{count} caracteres 3D modelos están disponibles para su descarga, por lo que navegar por esta categoría se encuentran los modelos de 3D %{human_link}, personas, figuras míticas, ciencia ficción %{robots_link}, ropa y otras cosas relacionadas con los personajes. De %{heart_link} y para hand_link se detalla realista %{male_link} o %{female_link} modelos. Varios de los modelos están manipuladas y podría ser fácilmente añadidos a su juego, VR / AR proyecto, vídeo o animación. Ambos modelos de baja poli alta poli y se pueden encontrar en una serie de formatos, incluyendo FBX, OBJ, MAX, 3DS, C4d.","custom":"Aquí se puede comprar o descargar libres %{tag} modelos 3D. %{tag_heading_subtitle}","electronics":"%{count} electrónica 3D modelos disponibles para su descarga. Esto es una categoría para arquitectos, decoradores de interiores, diseñadores de niveles, los responsables de escena y otros creativos. modelos 3D de %{smartphones_link}, %{laptops_link}, %{cameras_link}, %{tvs_link} y más ayudarán a capturar con precisión la obsesión de la vida moderna con los dispositivos y la tecnología. Hay modelos 3D disponibles en formatos de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d. Descargar gratis o comprar modelos de primera calidad y el uso de animación, juegos y proyectos VR / AR.","exterior":"%{count} de los modelos exteriores de arquitectura 3D disponibles para su descarga gama de fachadas, edificios urbanos, objetos paisaje, casas residenciales a construcciones industriales, edificios de infraestructura y %{city_link} modelos a escala real. Para comprar o descargar un modelo 3D gratis, navegar por las categorías anteriormente. Usar los filtros para encontrar aparejado, animado, bajo poli, libre de 3D o modelos ordenar por formato necesario, incluyendo FBX, OBJ, MAX, 3DS, C4d. Algunos de los activos son de baja poli y podrían ayudar a completar el medio ambiente en su juego o aplicación VR / AR.","food":"%{count} modelos 3D de alimentos están disponibles para download..Food sirve bien para crear escenas realistas de la vida doméstica. 3D frutas, verduras, bebidas y comidas son todos aquí para anuncios completas para los restaurantes, las aplicaciones de RA para una alimentación saludable, y visualizaciones arquitectos y diseñadores de interiores están trabajando. Ya sea que usted está buscando en 3D %{pizza_link} y %{ketchup_link} modelos o un modelo 3D de una boda %{cake_link}, están aquí. Varias versiones de baja poli están disponibles para aquellos en el juego, el desarrollo VR / AR. modelos 3D se pueden encontrar en FBX, OBJ, MAX, 3DS, formatos C4D.","formats":"Descarga %{rounded_count} modelos 3D en formato %{format_upper}, gratis y premium. Todos los archivos de esta página están disponibles en %{format_upper} nativo, sin conversión.","free_general":"Descarga %{rounded_count} modelos 3D gratis para proyectos personales y comerciales. Modelos 3D gratuitos y libres de derechos en FBX, OBJ, BLEND y GLB, sin costos ocultos.","free_no_tag":"%{model_count} 3D gratis %{tag} modelos listo para la impresión en 3D. Descargar %{tag} modelos de impresión en 3D gratuitos, formatos de archivo disponibles, incluyendo STL, OBJ, 3DM, 3DS, MAX. modelos 3D están listos para ser utilizado con FDM, SLA, SLS y otros tipos de impresoras 3D.","free_printable":"Descarga %{rounded_count} archivos STL gratis: modelos 3D para imprimir gratis, probados por la comunidad. Figuras, miniaturas, piezas útiles y decoración para tu impresora 3D.","free_tag":"Gratis 3D %{tag} modelos disponibles para su descarga. Disponible en varios formatos de archivo, incluyendo MAX, OBJ, FBX, 3DS, STL, C4d, mezcla, MA, MB. Encuentra profesional %{tag_link} para cualquier proyecto de diseño en 3D, como la realidad virtual (VR), la realidad aumentada (AR), juegos, visualización en 3D o animación.","furniture":"Modelos %{count} muebles 3D disponibles para su descarga incluyen mesas, sillas, sofás, camas y otros muebles en una variedad de texturas (madera, metal, etc.) y estilos (%{retro_link}, %{modern_link}, etc. ). Esta categoría es para cualquier persona involucrada con la arquitectura y diseño de interiores, así como los anunciantes y los animadores. activos en tiempo real pueden ayudar a juegos y aplicaciones VR / AR a los desarrolladores de proyectos completos. Usar los filtros para encontrar aparejado, animada, bajo poli o 3D modelos libres. Disponible en cualquier formato de archivo FBX incluyendo, OBJ, MAX, 3DS, C4d.","general":"Descarga o compra %{rounded_count} modelos 3D profesionales: diseños 3D y objetos 3D para juegos, cine, VFX, AR/VR e impresión 3D. Gratis y premium, en FBX, OBJ, BLEND, GLB y más de 100 formatos.","high_poly":"Comprar o descarga de animación 3D Profesional modelos listos para su uso en proyectos de CG, producción de cine y vídeo, animación, visualizaciones y otros. Los activos están disponibles para su descarga en muchos formatos aceptados por la industria, incluyendo MAX, OBJ, FBX, 3DS, UE4 y otra. Si estás en busca de activos de alta calidad en 3D poli, tenemos una biblioteca de arte digital líder para todas sus necesidades.\n","household":"Modelos %{count} 3D hogar disponibles para su descarga se pueden utilizar para dar detalles a las salas. Es posible que desee poner un juego de cena en la mesa, tener recipientes pimienta y sal a interior de la cocina completa, o poner un poco de %{lights_link} y %{flowers_link}
16518  cerca de la cama para crear sensación de lujo. Si usted es un diseñador, animador, VR application / AR o juego interior creador, esta categoría es para usted. Usar los filtros para encontrar aparejado, animada, bajo poli o 3D modelos libres. Disponible en cualquier formato de archivo FBX incluyendo, OBJ, MAX, 3DS, C4d.","industrial":"%{count} modelos industriales 3D disponibles para su descarga - materiales de construcción, %{engineering_link} herramientas, maquinaria y %{containers_link} -, permitirán a los detalles y elementos interactivos, manipuladas para crear entornos animados. Esta es una categoría ideal para simulaciones / VR / AR o los desarrolladores que crean %{construction_link} nivel de sitio en el juego. Los modelos 3D servirán profesionales de la construcción visualizaciones precisas que trabajan con el software variados como modelos en formatos tales como FBX, OBJ, MAX, 3DS, C4d están disponibles.","interior":"%{count} modelos de interiores en 3D están disponibles para su descarga e incluyen elementos y detalles utilizados en la arquitectura, la construcción y la ingeniería, el diseño de interiores. Para comprar prima o descargar un modelo de libre 3D, navegar por las categorías mencionadas anteriormente. Puede utilizar modelos para animaciones o juegos - algunos de ellos se aparejan, animada y poli baja. Disponible en una variedad de formatos de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.","low_poly":"Descarga %{rounded_count} modelos 3D low poly: assets ligeros y game-ready para juegos, AR/VR y proyectos móviles.","military":"%{count}
16518 3D modelos militares de armas, las armas, %{swords_link} y la artillería moderna y histórica utilizados por las fuerzas militares y de defensa personal están disponibles para su descarga. Puede utilizar modelos para juegos o contenido de VR / AR - algunos de ellos son polígonos optimizados y listos para ser añadido a un motor de juego. modelos CAMBIAR LAS animados pueden facilitar su trabajo más. Seguramente encontrará activos relevantes, ya que hay muchos modelos 3D disponibles en cualquier formato de archivo que pueda necesitar (FBX, OBJ, MAX, 3DS, C4d, etc.).","not_custom":"Descarga %{rounded_count} modelos 3D de %{tag_low} para tu proyecto. Modelos de %{tag_low} gratis y premium, realistas y estilizados, en FBX, OBJ, BLEND y GLB.","optimized":"Los modelos que tienen un menor número de polígonos son los más utilizados en aplicaciones en tiempo real que requieren un procesamiento rápido, como la realidad virtual (VR), la realidad aumentada (AR), la realidad mixta (MR), la realidad cruz (XR) y los juegos, especialmente los juegos móviles. Elija entre nuestra colección de modelos CAMBIAR LAS animados de manera fácil utilizarlos en sus aplicaciones en tiempo real. Obtener 3D activos para entornos, recoger objetos o accesorios, comprar completas modelo 3D colecciones, paquetes y paquetes con todo lo que su necesidad de juego de fuerzas. Guardar el tiempo de desarrollo y los costos, las experiencias prototipo realizar o utilizar modelos 3D como marcadores de posición en su proyecto. Físicamente representación basada, o PBR, denota modelos que se vuelven precisa y consistente en cualquier condición de iluminación debido a la texturización flujos de trabajo usados ​​en el proceso de creación.\n","pbr":"Descarga %{rounded_count} modelos 3D con texturas PBR (renderizado basado en física): materiales realistas y consistentes para Unity, Unreal y otros motores en tiempo real.","plant":"%{count} Plant 3D modelos disponibles para su descarga contienen flora organismos que crecen como plantas domésticas y en la naturaleza, tales como flores, %{trees_link}, hierba, enredaderas, verdor, hierbas, plantas vivaces, plántulas, brotes, arbustos, notas, coles, vides, malas hierbas. Para comprar prima o descargar un modelo 3D gratis, navegar por las categorías de arriba para encontrar el mejor ajuste entre los modelos disponibles. Use filtros para encontrar manipuladas, animada, bajo poli (para su uso en juegos y aplicaciones de VR / AR) modelos, o para filtrar los formatos requeridos (en modelos FBX, OBJ, MAX, 3DS, C4d y más están disponibles).","printable":"Descarga %{rounded_count} modelos 3D para imprimir en tu impresora 3D. Archivos STL gratis y premium probados en impresoras FDM y de resina: figuras, miniaturas, piezas y decoración.","printable_merged_tag":"Descarga %{rounded_count} figuras y modelos de %{tag_low} para imprimir en 3D. Archivos STL gratis y premium para impresoras FDM y de resina.","printable_tag":"Descarga %{rounded_count} figuras y modelos de %{tag_low} para imprimir en 3D. Archivos STL gratis y premium para impresoras FDM y de resina.","rigged":"Descarga %{rounded_count} modelos 3D rigged (riggeados): personajes, animales y criaturas con esqueleto, listos para animar en Blender, Maya, 3ds Max, Unity y Unreal.","scanned":"%{count} escaneado 3D modelos están disponibles para su compra o descarga gratuita. Modelos 3D examinados están preparados para la animación, juegos y proyectos VR / AR. Puede encontrar modelos 3D de objetos que se encuentran en la naturaleza tales como rocas y superficies naturales, humanos y otros objetos de la vida real aquí ... y no se puede estar más realista que esto. Usar los filtros para encontrar aparejado, animada, bajo poli o 3D modelos libres. Disponible en cualquier formato de archivo FBX incluyendo, OBJ, MAX, 3DS, C4d.","science":"%{count} ciencia 3D modelos disponibles para su descarga en esta categoría van desde la medicina, la biología, la física, la química y la otra campos mundo real objetos relacionados con la ciencia y la tecnología: equipos de laboratorio, los organismos microscópicos y el mund
16518o subatómico. No busque más,%{cell_link}, %{skull_link} y %{skeleton_link} 3D modelos se puede encontrar aquí! Para comprar o descargar gratis 3D activos, navegar por las categorías anteriormente. Puede utilizar modelos manipuladas para los de animación o de bajo-poli para juegos y proyectos VR / AR. Ambos modelos 3D de alta calidad libres y se pueden encontrar en una serie de formatos de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.","scripts_plugins":"%{count} 3D Scripts / Complementos están disponibles para su descarga. 3D Scripts / Complementos pueden ser añadidos a distintos programas informáticos y se utilizan para hacer su trabajo de manera más eficiente la tubería. Filtrar por formatos para encontrar herramientas de adaptación de su software preferido.\n","software_page":"Descarga %{rounded_count} modelos 3D para %{software}, gratis y premium. Los archivos %{ext_file} nativos se importan directamente; otros formatos se convierten en pocos clics.","space":"%{count} modelos de espacio en 3D están disponibles para su descarga en CGTrader. El espacio es una categoría completa de poli baja, los modelos de juego listo de la ciencia ficción %{spaceships_link}. También hay %{asteroids_link}, planetas (incluyendo la Luna y Marte, a la colonización simular) y el espacio realista %{shuttles_link} para el inclinado de forma más científica. Los modelos sin duda se pueden utilizar en juegos de simulador de realidad virtual, espacio de aplicaciones de RA y animaciones para demostrar al mundo exterior del planeta %{earth_link}. Los filtros están aquí para ayudarle a buscar, animados, activos o modelos de bajo-poli manipuladas en formatos específicos, incluyendo FBX, OBJ, MAX, 3DS, C4d.","sports":" %{count} Sports 3D modelos disponibles de equipos juntos nido que se utiliza para actividades recreativas, entretenimiento, ejercicio, ocio, pasatiempo placentero, divertido, así como juegos, juguetes infantiles y una amplia gama de instrumentos musicales. Para comprar o descargar un modelo 3D gratis en cualquier formato de archivo necesario, incluyendo FBX, OBJ, MAX, 3DS, C4d, navegar por las categorías anteriormente. Puede utilizar modelos de animación, juegos, proyectos VR / AR - muchos de ellos estén provistas, bajo poli o ya animado.","test":{"airplane":"Entusiastas de la aviación, escuchen, nunca ha sido tan fácil de obtener el modelo perfecto 3D avión impresa para su juego de mesa o colección modelo. Buscar a través de modelos de aviones imprimible %{model_count} 3D y encontrar un modelo que sólo tiene que tener en ese estante. modelos reales de avión descubrir o los confeccionados. Buscar nuevos diseños o algo que una cuenta atrás desde el tiempo de los hermanos Wright. Es también un regalo perfecto para cualquier niño que sueña con ser un piloto cuando sea grande. Comprar y descargar un modelo de impresión 3D, imprimir por sí mismo o someter el modelo a uno de nuestros proveedores de servicios de impresión en 3D para tenerlo impreso y enviado a usted en ningún momento!\n","anatomy":"%{model_count} modelos 3D anatomía disponibles para su descarga en nuestro mercado. Explora la anatomía humana con alta - hueso poli, músculo, modelos de órganos. Perfecto para cualquier proyecto educativo o científico. Utilizar modelos para imprimir en 3D para mejorar la experiencia de la educación. Crear impresionantes animaciones y juegos de video con nuestra\n amplia biblioteca de modelos manipuladas. Utilice nuestro sistema de filtrado avanzado para encontrar fácilmente poli baja, animado, PBR,\n modelos fraudulentas o cualquier cosa que se adapte mejor a sus necesidades. Elegir entre una gran cantidad de formatos de archivo, incluyendo la\n la mayoría de los más populares como FBX, OBJ, MAX, 3DS, C4d.\n","castle":"Su propia asombrosa 3D castillo impresa es un clic de distancia! Buscar varios modelos del castillo 3D listos para la impresión 3D. Crear una fortaleza de fantasía único para la sesión de D \u0026 D o cualquier otro juego de mesa RPG medieval. Estas fortalezas medievales son un regalo increíble para cualquier entusiasta del tablero de juego o una gran adición a su propia cole
16518cción. Encontrar el modelo 3D perfecta para pintar aficionados o una parte de una especie de juguete para los niños. La compra y descarga tu diseño del castillo 3D elegido, imprimirlo con su propia impresora, o someter el modelo a uno de nuestros proveedores de servicios de impresión en 3D para tenerlo impreso y enviado a usted.\n","dice":"La suerte no se acabará con 3D impreso dados! Si quieres juegos de dados de partida ideal para la noche tablero de juego, no busque más. Aquí se puede elegir entre %{model_count} gratuitas modelos dados en 3D listos para la impresión 3D. Cuando el juego ha terminado, utilice estas hermosas modelos como accesorio. Descargar gratis 3D modelos dados imprimibles, formatos de archivo disponibles, incluyendo STL, OBJ, 3DM, 3DS, MAX. Nuestra modelos 3D están listos para ser utilizado con FDM, SLA, SLS y otros tipos de impresoras 3D. Hacer su juego un poco más interesante y obtener sus dados impresos en 3D!\n","drone":"3D imprimir y construir su propio avión no tripulado o encontrar piezas de reemplazo. 3D impreso aviones no tripulados son fáciles de montar y reparación después de un aterrizaje de emergencia. Descubre un modelo con la posición de montaje de la cámara perfecta o solicitud de alteraciones mínimas. Perfecto para actividades de ocio diversión, la fotografía aérea, videografía o mapeo. Encontrar y comprar un modelo de avión no tripulado 3D de diseño único que se adapte a todas sus necesidades. Imprimir por sí mismo o someter el modelo a uno de nuestros proveedores de servicios de impresión en 3D para tenerlo impreso y enviado a usted en ningún momento! Obtener su propio avión no tripulado 3D impreso y estudiar los cielos azules de ancho!\n","pokemon":"Pikachu, yo te elijo! Elegir entre un montón de monstruos de bolsillo listos para la impresión 3D. Gran para ampliar su colección de pokemon impresa en 3D. Buscar y tropezar con los mejores diseños, pokemon detallada para su próximo juego de mesa con amigos. La compra y descarga tu Pokémon en 3D, imprimirlo con su propia impresora 3D, o someter el modelo a uno de nuestros proveedores de servicios de impresión en 3D para tenerlo impreso y enviado a usted. Después de todo, esta es una de las mejores cosas a la impresión 3D!\n","ring":"Joyas impresa en 3D es el nuevo negro en la industria, como son impresos en 3D anillos! Buscar una enorme cantidad de %{model_count} modelos de anillos para imprimir gratis 3D. Navegar a través de diseños únicos y encontrar algo hecho sólo para usted. No se puede encontrar nada por sí mismo en la tienda? O tal vez quiere un accesorio exactamente como la que su personaje de la película favorita tiene? Elegir entre una gran cantidad de formatos de archivo, incluyendo STL, OBJ, 3DM, 3DS, MAX. modelos 3D están listos para ser utilizado con FDM, SLA, SLS y otros tipos de impresoras 3D, por lo que navegar a través de nuestra selección y tratar en su nuevo anillo favorito.\n","shoes":"Las grandes marcas están a bordo con 3D impreso zapatos y ahora se puede compartir la experiencia también. Examinar y busque diseños libres del zapato imprimibles - zapatillas de deporte de los talones de moda hay una gran cantidad de modelos de listas para imprimir a la espera de que los pruebe. Hacer el perfecto resultado mediante la selección de materiales, el ajuste de la suavidad y el tamaño. Elija entre 146 modelos de zapatos libres 3D y descargarlos en formatos de archivo disponibles, incluyendo STL, OBJ, 3DM, 3DS, MAX. Los diseños están listos para ser utilizado con FDM, SLA, SLS y otros tipos de impresoras 3D. Ponte los zapatos impresos en 3D hoy en día!\n","textures":"Buscar a través de una vasta cantidad de %{model_count} CG texturas disponibles para su descarga, incluidos los materiales hechos bellamente. Navegar a través de la categoría y encontrar tejido sin costuras, madera, metal, plástico, texturas de ladrillo hasta el final ayuda a su proyecto más rápido. Utilice nuestra búsqueda avanzada para encontrar texturas manipuladas y animados para el juego o la creación de vídeo. Nuestros materiales están disponibles para su descarga en varios formatos, incluyendo TGA, SBSAR, JPG, PNG, TIFF y otros, así que no pierda tiempo y encontrar texturas CG-listas para usar para casi cualquier imaginable proyecto.\n"},"various":"%{count} diferentes modelos en 3D que no encuentran fácilmente su lugar en otras categorías están disponibles para su descarga. %{rocks_link}, %{clouds_link}, %{logos_link}, %{cigarette_link} y otros modelos no categorizado en 3D que pueda necesitar para completar su juego, animación o proyecto VR / AR están aquí. Use filtros para encontrar manipuladas, animada, bajo poli o 3D modelos libres, o bien ordenar a cabo por los formatos disponibles, tales como FBX, OBJ, MAX, 3DS, C4d.","vehicle":"3D modelos %{count} de vehículos disponibles para descarga incluyen camiones, %{ambulances_link}, autobuses, %{bikes_link} y moto - Esta categoría es para las personas que quieren que sus calles se llenen de vehículos ya sea realista o de ciencia ficción. Aquellos que hacen los juegos de simulación o aplicaciones VR / AR, creativos en la producción de vídeo o incluso el diseño de carteles más probable es encontrar formatos de archivo más populares, tales como FBX, OBJ, MAX, 3DS, C4d utilizable. Otros vehículos de transporte terrestre como modelos %{tractor_link} 3D tren o también se pueden encontrar en esta vasta colección. Usar los filtros para encontrar aparejado, animada, bajo poli o libre 3D modelos más rápido.","watercraft":"%{count} motos acuáticas 3D modelos disponibles para su descarga incluyen %{boats_link}, %{yachts_link}, %{ships_link} y otros vehículos acuáticos pasando de todo tipo. Los modelos de baja manipuladas 3D-poli va a hacer bien en los videojuegos, VR / AR proyectos, así como animaciones, mientras que los modelos CG están aquí para sus escenas y otras aplicaciones en las que la fidelid
16518ad visual es más importante que polycount. Los modelos están disponibles en cualquier formato de archivo, incluyendo, pero no limitado a FBX, OBJ, MAX, 3DS, C4d."},"tag":{"meta":{"3d_merged_premium":"Descarga %{rounded_count} modelos 3D de %{tag_low}, gratis y premium. En FBX, OBJ, BLEND, GLB y más, para juegos, cine, AR/VR e impresión 3D.","3d_merged_premium_print":"Descarga %{rounded_count} archivos STL de %{tag_low} para imprimir en 3D, gratis y premium, probados en impresoras FDM y de resina.","3d_models":"Descarga %{rounded_count} modelos 3D de %{tag_low}, gratis y premium. En FBX, OBJ, BLEND, GLB y más, para juegos, cine, AR/VR e impresión 3D.","3d_print_models":"Descarga %{rounded_count} archivos STL de %{tag_low} para imprimir en 3D, gratis y premium, probados en impresoras FDM y de resina.","animated_3d_models":"%{count} %{tag} de animación en 3D modelos disponibles para su descarga en cualquier formato de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.%{page}","free_3d_models":"Descarga %{count} %{tag} libres 3D modelos, disponibles en MAX, OBJ, FBX, 3DS, formatos de archivo C4D, listos para VR / AR, animación, juegos y otros proyectos en 3D.%{page}","free_3d_print_models":"Descarga %{count} %{tag} gratis modelos de impresión 3D listos para la FDM, SLA, SLS y otras impresoras 3D, formatos disponibles STL, OBJ, 3DM y más.%{page}","lowpoly_3d_models":"Descargar %{count} VR / AR / bajo poli %{tag} modelos 3D listo para la realidad virtual, juegos y otras aplicaciones en tiempo real, disponible en FBX, OBJ, MAX, 3DS, formatos de archivo C4D.%{page}","rigged_3d_models":"%{count} %{tag} aparejado 3D modelos disponibles para su descarga en cualquier formato de archivo, incluyendo FBX, OBJ, MAX, 3DS, C4d.%{page}"}},"title":{"download":"| descargar","files":"archivos 3D","formats":"Modelos 3D %{format_upper}","free":"Modelos 3D Gratis","free_printing":"Archivos STL Gratis","free_tag":"Libres %{selected_tag} ​​modelos 3D","general":"Modelos 3D","high_poly":"Comprar o descargar %{selected_filter}Modelos 3D","landing":{"animated_3d_models":"Modelos 3D Animados","lowpoly_3d_models":"Modelos 3D Low Poly","pbr_3d_models":"Modelos 3D PBR","rigged_3d_models":"Modelos 3D Rigged"},"low_poly":"%{selected_filter}3D Modelos","newest_cg":"Nuevos y últimos modelos 3D","newest_print":"Nuevos y últimos modelos de impresión 3D","page_prefix":"Página %{page_number} de ","pbr":"PBR 3D Modelos","printing":"Modelos 3D para Imprimir – Archivos STL","sales_cg":"Modelos 3D más vendidos","sales_print":"Modelos de impresión 3D más vendidos","software_page":"Modelos 3D para %{software}","tag_heading":{"cg":"Libres %{tag} modelos de impresión 3D","filter":"%{selected_filter}%{tag} Modelos 3D","general":"%{tag_cap} 3D – Modelos 3D","merged_premium_print_tag":"%{tag_cap} para Imprimir en 3D","merged_premium_tag":"%{tag_cap} 3D – Modelos 3D","printable":"%{tag_cap} para Imprimir en 3D"},"trending_cg":"Modelos 3D de tendencia","trending_print":"Modelos de impresión 3D de tendencia"}},"cart":{"request_invoice_failed":"Vaya. Algo salió mal.","request_invoice_successful":"La solicitud de factura se ha enviado correctamente.","shareable_cart_created":"El carrito se ha compartido correctamente.","shareable_cart_failed":"Vaya. Algo salió mal.","subscription_covered_items_removed":"Eliminado de tu carrito: %{titles}. Ya está incluido en tu suscripción.","subscription_covered_order_refused":"Tu suscripción ahora incluye %{titles}, por lo que no te hemos cobrado. Descárgalo desde tu suscripción."},"categories":{"cg":{"header_title_html":"Modelos 3D","text":"Encuentra el contenido 3D exacto para tus necesidades, incluyendo AR/VR, juegos, publicidad, entretenimiento y más."},"head-title":{"cg":"Modelos 3D – Descarga Gratis y Premium","cg-models":{"sort-by":{"newest":"Los modelos 3D más nuevos","top-selling":"Los modelos 3D más vendidos","trending":"Modelos 3D de tendencia"}},"print":"Modelos 3D para Imprimir – Archivos STL","print-models":{"sort-by":{"newest":"Archivos de impresión 3D más nuevos","top-selling":"Archivos de impresión 3D más vendidos","trending":"Archivos de impresión 3D de tendencia"}}
16518,"product-type":{"plural":{"capital":{"cg":"Modelos 3D","cg-merge-premium":"Modelos 3D: descargas gratuitas y premium","print":"Archivos y Modelos de Impresión 3D","print-merge-premium":"%{tag} Archivos de impresión 3D: modelos STL gratuitos y premium"},"lower":{"cg":"modelos 3d","print":"archivos y modelos de impresión 3D"}},"singular":{"capital":{"cg":"Modelo 3D","print":"Modelo 3D Printing"},"lower":{"cg":"modelo 3d","print":"modelo de la impresión 3d"}}}},"index":{"browse_all":"Ver todos los modelos","info-section__description_1_html":"Compra o descarga gratuita de modelos 3D profesionales listos para usar en proyectos CG, producción de video, animación, visualizaciones, juegos, VR/AR y más. Los archivos están disponibles en \u003ca href='https://www.cgtrader.com/blog/3d-file-formats' target='_blank' rel='noopener noreferrer'\u003eformatos ampliamente aceptados\u003c/a\u003e como MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB y otros. Si buscas modelos de alta calidad o listos para tiempo real, tenemos una biblioteca digital líder para cubrir cualquier necesidad.","info-section__description_1_printable_html":"Archivos, impresiones y modelos 3D profesionales listos para imprimir en casa con impresoras 3D FDM de escritorio o SLS industriales, disponibles en muchos formatos industriales, incluidos OBJ, \u003ca href=\"https://www.cgtrader.com/3d-print-models/stl-files\"\u003eSTL\u003c/a\u003e, WRL. Si eres un aficionado a la impresión 3D, creador, educador o una empresa que crea prototipos mediante impresión 3D, nuestras decoraciones para el hogar, accesorios de gadgets de moda, piezas de moda llamativas, joyería única, piezas mecánicas y una variedad de miniaturas seguramente te darán un impulso de inspiración.","info-section__heading_1_html":"Explorar \u003cstrong\u003ecategorías\u003c/strong\u003e de modelos 3D","info-section__heading_1_printable_html":"Explora Modelo 3D de impresión \u003cstrong\u003e Categorías \u003c/strong\u003e","info-section__heading_2_html":"\u003cstrong\u003eMercado\u003c/strong\u003e de modelos 3D","info-section__heading_2_printable_html":"¿Qué \u003cstrong\u003emás\u003c/strong\u003e?"},"marketplace":{"3d-printing":{"link_text":"Navegar en 3D de impresión Modelos","text":"Encontrar modelos de impresión 3D para su impresora 3D.","title":"Los modelos de impresión 3D"},"blender_text":"Descubre miles de modelos 3D Blender disponibles en formato mezcla.","browse":"Vistazo","cinema-4d_text":"Destape miles de modelos Cinema 4D de alta calidad disponibles en el formato de archivo de C4d.","discounted":{"link_text":"Buscar modelos 3D con descuento","text":"Comprar modelos 3D profesionales con un descuento.","title":"Modelos en 3D con descuento"},"low-poly":{"link_text":"Modelos en 3D Navegar bajo poli","text":"3D modelos de poli bajas listo para videojuegos, VR y AR aplicaciones en tiempo real.","title":"Poli bajas 3D Modelos"},"pbr":{"link_text":"Modelos 3D Navegar PBR","text":"Físicamente modelos basados ​​en la representación 3d.","title":"Modelo 3D PBR"},"scripts_apps":{"link_text":"Ver todas las","text":"scripts y plugins 3D para hacer su tubería de modelado 3D más eficiente.","title":"Scripts y Aplicaciones"},"sketchup_text":"Navegar por la amplia selección de modelos de SketchUp 3D para sus arquitectónicos interiores y diseños de videojuegos,.","textures":{"link_text":"Recorrer las texturas","text":"texturas listas para su uso para ahorrar algo de tiempo la preparación de sus modelos en 3D.","title":"Texturas"},"zbrush_text":"Encuentra formato de archivo ZTL 3D activos para su escultura digital, 3D modelado, texturizado y proyectos de pintura."},"meta-description":{"cg":"Descarga %{rounded_count} modelos 3D profesionales. Modelos gratis y premium para juegos, cine, VFX, AR/VR e impresión 3D. FBX, OBJ, BLEND, GLB y más.","print":"Descarga %{rounded_count} modelos 3D para imprimir. Archivos STL gratis y premium para impresora 3D: figuras, miniaturas, piezas, decoración y más."},"more":{"designers":{"link_text":"Lista de exploración de diseñadores 3D","text":"Recorrer miles de carteras de diseño 3D y encontrar exactamente lo que necesita para su aplicación.","title":"Encontrar mejores diseña
16518dores 3D"},"projects":{"link_text":"Más información sobre Proyectos en 3D","text":"Contratar en calidad independientes 3D artistas y resolver todos los desafíos de modelado personalizados.","title":"CGTrader Proyectos 3D"},"visualization":{"link_text":"Aprende más","text":"Convertir sus productos en modelos 3D fotorrealistas para el comercio electrónico y otras aplicaciones empresariales.","title":"La visualización en 3D para el comercio electrónico"}},"navbar":{"aircraft":"Aeronave","animal":"Animales","animals":"Animales","architectural":"Arquitectónico","art":"Arte","car":"Coche","character":"Personaje","exterior":"Exterior","fashion":"Moda","food":"Comida","furniture":"Mueble","gadgets":"Artilugio","games-toys":"Juegos y Juguetes","hobby-diy":"Manía y bricolaje","house":"Casa","household":"Casa","industrial":"Industrial","interior":"Interior","jewelry":"Joyería","military":"Militar","miniatures":"Miniaturas","plant":"Planta","science":"Ciencias","space":"Espacio","vehicle":"Vehículo","watercraft":"Motos Acuáticas"},"print_models":{"hero_title_html":"Modelos 3D para Imprimir – Archivos STL","subtitle":"Encontrar el derecho exacto contenido de la impresión 3D para sus necesidades","title_html":"Comprar Profesional \u003cstrong\u003e 3D modelos para impresión \u003c/strong\u003e"},"profile":{"collection":"packs 3D","free":"Modelos 3D gratuitos","high_poly":"Modelos 3D de alta poligonización","optimized":"Modelos 3D Low-Poly/PBR","printable":"Modelos para impresión 3D"},"seo":{"cg":{"aircraft":"Esta categoría abarca los aviones 3D. CG { \"slug\": \"avión\", \"title\": \"aviones\"} encajará en simulaciones, visualizaciones, anuncios y videos. { \"slug\": \"drone\", \"title\": \"Drone\"} cuerpos y partes harán las delicias de los amantes de los vehículos voladores pequeños. Y el { \"slug\": \"aeronaves\", \"title\": \"aparejado modelos\", \"active_filter\": \"rigged\"} están listos para ser importados en los motores de juego.\n","animals":"La colección de la fauna 3D - { \"slug\": \"gato\", \"title\": \"gatos\"}, { \"slug\": \"caballo\", \"title\": \"caballos\"}, { \"slug\": \"oso\" , \"title\": \"osos\"}, { \"slug\": \"rinoceronte\", \"title\": \"rinocerontes\"}, { \"slug\": \"águila\", \"title\": \"águilas\"}, { \"slug\" : \"león\", \"title\": \"leones\"} y más - se pueden encontrar aquí. Los animales CG 3D va a satisfacer las necesidades de los anuncios o las escenas estáticas. También tenemos { \"slug\": \"animal\", \"title\": \"amañadas animal\", \"active_filter\": \"rigged\"} modelos para juegos.\n","architectural":"{ \"slug\": \"escalera\", \"title\": \"Escalera\"}, { \"slug\": \"chimenea\"}, 3D modelos, así como decoraciones como { \"slug\": \"marco\"}, { \"slug\": \"almohada\"}, o { \"slug\": \"lámpara\"} se pueden encontrar en la amplia categoría del detalle del coche arquitectónica contiene 3D modelos de automóviles, { \"slug\": \"coche\", \"title\": \"low poly\", \"active_filter\": \"low_poly\"} biblioteca de modelos. Interior y exterior pueden ser utilizados para construir las dos cocinas digitales y dormitorios, así como habitaciones del castillo y mazmorras si usted es un arquitecto, ingeniero o desarrollador de juegos independiente.\n","car":"Categoría de coches del automóvil 3D contiene modelos, versiones \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e baja poli \u003c/a\u003e de los cuales serán atractivos para los diseñadores de juegos, otros - a los directores de cine o anunciantes. De los coches clásicos a las carreras de coches y pistas a los conceptuales, hay una gran variedad de modelos de automóviles en 3D.\n","character":"En esta categoría se encuentran los modelos 3D de { \"slug\": \"humana\"}, la gente, la ciencia ficción, ropa y otros. Desde { \"slug\": \"corazón\"} y { \"slug\": \"mano\"} detallada realista {\"slug\": \"macho\"} o {\"slug\": \"hembra\"} modelos. Algunos de los modelos son manipuladas y podría ser fácilmente añadidos a su juego o animación.\n","electronics":"La electrónica es una categoría para arquitectos, decoradores de interiores, diseñadores de niveles, los responsables de escena y otros creativos. modelos 3D de { \"slug\": \"teléfono inteligente\", \"title\": \"teléfonos inteligentes\"}, { \"slug\": \"portátil\", \"title\": \"portátiles\"}, { \"slug\": \"cámara\", \"título \": 'cámaras'}, {\"slug\": 'TV', 'title': 'TV'}
16518 y más ayudará a capturar con precisión la obsesión de la vida moderna con los dispositivos y la tecnología.\n","engineering-parts":"Ventanas, tuberías, herrajes para puertas correderas y otros bits que son necesarios para proyectos CAD e incluso planes de bricolaje se incluyen en esta categoría. Aquí es donde se pueden encontrar modelos 3D de los fabricantes mundiales como ANVER, Dynatec y la FCI.\n","exterior":"Exteriores arquitectónicos Examinar en fachadas de edificios urbanos, a los objetos paisaje, casas residenciales, construcciones industriales, y la escala completa en 3D { \"slug\": \"ciudad\"} modelos. Algunos de los activos son poli baja y podría ayudarle a completar su juego o aplicación VR / AR.\n","food":"Alimentos ayuda a crear escenas realistas de la vida doméstica. 3D frutas, verduras, bebidas y comidas están todos aquí. Los anuncios para los restaurantes, las aplicaciones de RA para una alimentación saludable. Ya sea que estabas buscando en 3D { \"slug\": \"pizza\"} o una boda { \"slug\": \"torta\" } modelo, ya están aquí.\n","furniture":"3D tablas, sillas, sofás, camas en una variedad de texturas (madera, metal, etc.) y estilos ({ \"slug\": \"retro\"}, { \"slug\": \"modernas\"}, etc.) son On- mano. Esta categoría es para cualquier persona involucrada con la arquitectura y diseño de interiores, así como los anunciantes.\n","furniture-set":"Si necesita una silla antigua por igual buscando y sofá mirando, o conjunto completo de menaje, este es un lugar de imitar a este conjuntos de categorías de cobro de modelos de muebles en 3D de cocina y sala de estar a la oficina y comedor sala de juegos de muebles. Esto ayuda a los interiores de diseño o nivel de habitaciones especialista en construcción con elementos coincidentes.\n","household":"Modelos artículo de la casa 3D se utilizan para dar información a las salas. Se puede poner un conjunto de cena 3D sobre la mesa, o tienen contenedores pimienta y sal al interior de la cocina completa, tal vez poner un poco { \"slug\": \"luz\", \"title\": \"luces\"} y { \"slug\": \"flores\"} cerca de la cama para crear sensación de lujo.\n","industrial":"Los materiales de construcción, { \"slug\": \"ingeniería\"} herramientas, maquinaria y { \"slug\": \"contenedores\", \"title\": \"contenedores\"} que proporcionan ambos detalles ambientales e interactivos, elementos manipuladas. Esto es perfecto para los desarrolladores de juegos de simulación o la gente que construye { \"slug\": \"construcción\"} planta de jardin.\n","interior":"Esta categoría contiene 3D elementos y detalles de la arquitectura, la construcción y la ingeniería de diseño de interiores realistas. Puede utilizar estos modelos en 3D para animaciones o juegos - algunos de ellos se aparejan y poli baja.\n","military":"En esta categoría se pueden comprar en 3D modelos de armas, brazos, { \"title\": \"espadas\", \"slug\": \"espada\"} y de artillería a la vez moderno e histórico utilizado por las fuerzas militares y de defensa personal. Puede utilizar modelos para juegos - algunos de ellos son polígonos optimizados y listos para motores de juego.\n","plant":"Esta categoría contiene 3D modelos de flora organismos tales como flores, { \"slug\": \"árbol\", \"title\": \"árboles\"}, hierba, enredaderas, verdor, hierbas, plantas vivaces, plántulas, brotes, arbustos, resbalones, coles, vides, malas hierbas. Para comprar o descargar un modelo 3D gratis, navegar por las categorías de abajo.\n","scanned":"Modeladores crean estos modelos en 3D mediante el uso de tecnologías de escaneado, como REALSENSE. Tocones de árboles, estatuas, incluso hombres y mujeres son muy populares aquí. El producto está listo para ser utilizado en las escenas y animaciones, que ofrece niveles incomparables de realismo.\n","science":"Esta categoría contiene 3D modelos relacionados con la medicina, la biología, la física, la química y la tecnología, tales como equipos de laboratorio, los organismos microscópicos y el mundo subatómico. No busque más, { \"slug\": \"célula\"}, { \"slug\": \"cráneo\"} y { \"slug\": \"esqueleto\"} 3D modelos se pueden encontrar aquí.\n","scripts-plugins":"Scripts y plug-ins que proporcionan atajos de softw
16518are. Modelado, renderizado, texturizar - todos estos se pueden hacer más fácil el uso de plug-ins que vienen de todo tipo de software como Maya y V-Ray.\n","space":"El espacio es una categoría completa de poli baja, los modelos de juego listo de la ciencia ficción { \"slug\": \"nave espacial\", \"title\": \"naves espaciales\"}, { \"slug\": \"asteroide\", \"title\": \"asteroides \"}, planetas (incluyendo {\"slug\": \"tierra\", \"title\": \"tierra\"} y el espacio realista {\"slug\": \"lanzadera\", \"title\": \"lanzaderas\"} para el inclinado de forma más científica 3D modelos espaciales son útiles para juegos de simulador de realidad virtual espacio.\n","sport-hobby":"Esta categoría encompases todo lo que está relacionado con { \"slug\": \"campo\"} eventos, deportes y aficiones en general. Desde { \"slug\": \"el fútbol\"} y las pistas de esquí a { \"slug\": \"guitarra\"} y { \"slug\": \"piano\"}, los cuales son útiles en 3D modelos para anuncios, escenas y animación. versiones poli-bajos están ahí para los videojuegos de deportes.\n","textures":"Navegar a través de una biblioteca de hecho muy bien en 3D-materiales y encontrar los precios, listos para su uso texturas adecuados para preparar \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e listo para jugar \u003c/a\u003e o modelos fotorrealistas, actualizar su biblioteca de activos, y mejorar sus trabajos en 3D.\n","various":"Estos son los modelos 3D que no encajan fácilmente en otras categorías, por lo que creamos una que aceptar a todos. { \"title\": \"rocas\", \"slug\": \"rocas\"}, { \"slug\": \"nubes\"}, { \"title\": \"logos\", \"slug\": \"logo\"}, { \"título\" : \"paraguas\", \"slug\": \"paraguas\"} y otros modelos no categorizado 3D están aquí.\n","vehicle":"Camiones, { \"title\": \"ambulancias\", \"slug\": \"ambulancia\"}, autobuses, trenes, tractores, { \"title\": \"bicicletas\", \"slug\": \"bicicleta\"} y motocicletas - Esta categoría es para las personas que quieren que sus calles se llenen de vehículos realistas y de ciencia ficción, y para aquellos que hacen juegos de simulación o formatear carteles.\n","watercraft":"Cubiertas de embarcaciones { \"title\": \"barcos\", \"slug\": \"barco\"}, { \"title\": \"yates\", \"slug\": \"yate\"}, { \"title\": \"naves\", \"slug\" : \"barco\"} y otros vehículos acuáticos. El aparejado 3D modelos se hacen bien en los juegos de video, animación CG mientras que los modelos están ahí para sus escenas u otras aplicaciones en las que la fidelidad visual es más importante que polycount.\n"},"forwarded":{"cg":{"aircraft":"Esta categoría incluye modelos 3D de aviones. Los aviones digitales son ideales para simulaciones, visualizaciones, anuncios y videos. Los drones y sus partes encantarán a los fans de los vehículos aéreos pequeños. También hay modelos con rig listos para usar en motores de juego.","animals":"Aquí encontrarás modelos 3D de fauna: gatos, caballos, osos, rinocerontes, águilas, leones y más. Perfectos para escenas estáticas o publicitarias. También hay modelos riggeados para juegos.","architectural":"Escaleras, chimeneas, decoraciones como marcos, cojines o lámparas están disponibles en esta extensa biblioteca de detalles arquitectónicos. Ya sea para cocinas, dormitorios, castillos o mazmorras, estos modelos sirven tanto para arquitectos, ingenieros como desarrolladores de juegos independientes.","car":"Incluye modelos 3D de automóviles. Las versiones low poly son ideales para juegos; otras para películas o publicidad. Desde autos clásicos y de carrera hasta conceptuales, hay una gran variedad disponible.","character":"Aquí puedes adquirir modelos 3D de armas, espadas y artillería, tanto modernas como históricas, usadas por fuerzas armadas o para defensa personal. Muchos están optimizados para juegos y listos para motores gráficos.","electronics":"La electrónica es una categoría para arquitectos, decoradores de interiores, diseñadores de niveles, los responsables de escena y otros creativos. modelos 3D de { \"slug\": \"smartphone\", \"title\": \"teléfonos inteligentes\"}, { \"slug\": \"laptop\", \"title\": \"portátiles\"}, { \"slug\": \"camera\", \"title\" : \"cámaras\"}, {\"slug\": \"TV\", \"title\": \"TV\"}
16518 y más ayudará a capturar con precisión la obsesión de la vida moderna con los dispositivos y la tecnología.\n","engineering-parts":"Ventanas, tuberías, herrajes para puertas correderas y otros bits que son necesarios para proyectos CAD e incluso planes de bricolaje se incluyen en esta categoría. Aquí es donde se pueden encontrar modelos 3D de los fabricantes mundiales como ANVER, Dynatec y la FCI.\n","exterior":"Explora exteriores arquitectónicos: fachadas, edificios urbanos, paisajes, casas residenciales, construcciones industriales y ciudades en 3D a escala real. Algunos modelos son low poly y pueden integrarse fácilmente en proyectos VR/AR o videojuegos.","food":"Modelos 3D de frutas, verduras, bebidas y platos listos. Útiles para anuncios de restaurantes o apps AR sobre alimentación saludable. ¿Buscas una pizza en 3D o una tarta de boda? Están aquí.","furniture":"Mesas, sillas, sofás, camas en diferentes texturas (madera, metal, etc.) y estilos (retro, moderno, etc.). Esta categoría es ideal para diseñadores de interiores, arquitectos y publicidad.","furniture-set":"Si necesita una silla antigua por igual buscando y sofá mirando, o conjunto completo de menaje, este es un lugar de imitar a este conjuntos de categorías de cobro de modelos de muebles en 3D de cocina y sala de estar a la oficina y comedor sala de juegos de muebles. Esto ayuda a los interiores de diseño o nivel de habitaciones especialista en construcción con elementos coincidentes.\n","household":"Modelos 3D de objetos domésticos para agregar realismo a las habitaciones. Añade vajillas a la mesa, saleros en la cocina o lámparas y flores junto a la cama para crear un ambiente lujoso.","industrial":"Materiales de construcción, herramientas, maquinaria y contenedores que ofrecen tanto detalle visual como elementos interactivos riggeados. Perfecto para desarrolladores de simuladores o niveles de construcción.","interior":"Contiene elementos realistas de arquitectura, construcción y diseño interior. Algunos modelos están riggeados y optimizados en polígonos, ideales para animaciones o videojuegos.","military":"Aquí puedes adquirir modelos 3D de armas, espadas y artillería, tanto modernas como históricas, usadas por fuerzas armadas o para defensa personal. Muchos están optimizados para juegos y listos para motores gráficos.","plant":"Incluye modelos 3D de flores, árboles, césped, enredaderas, arbustos, brotes y más. Puedes comprar o descargar modelos gratuitos en las subcategorías.","scanned":"Modeladores crean estos modelos en 3D mediante el uso de tecnologías de escaneado, como REALSENSE. Tocones de árboles, estatuas, incluso hombres y mujeres son muy populares aquí. El producto está listo para ser utilizado en las escenas y animaciones, que ofrece niveles incomparables de realismo.\n","science":"Esta categoría contiene 3D modelos relacionados con la medicina, la biología, la física, la química y la tecnología, tales como equipos de laboratorio, los organismos microscópicos y el mundo subatómico. No busque más, { \"slug\": \"cell\", \"title\": \"célula\"}, { \"slug\": \"skull\", \"title\": \"cráneo\"} y { \"slug\": \"skeleton\", \"title\": \"esqueleto\"} 3D modelos se pueden encontrar aquí.\n","scripts-plugins":"Scripts y plug-ins que proporcionan atajos de software. Modelado, renderizado, texturizar - todos estos se pueden hacer más fácil el uso de plug-ins que vienen de todo tipo de software como Maya y V-Ray.\n","space":"Modelos low poly listos para juegos de naves espaciales, asteroides, planetas y transbordadores. Muy útiles para simuladores espaciales en VR.","sport-hobby":"Esta categoría encompases todo lo que está relacionado con { \"slug\": \"field\", \"title\": \"campo\"}
16518 eventos, deportes y aficiones en general. Desde { \"slug\": \"football\", \"title\": \"el fútbol\"} y las pistas de esquí a { \"slug\": \"guitar\", \"title\": \"guitarra\"} y { \"slug\": \"piano\", \"title\": \"piano\"}, los cuales son útiles en 3D modelos para anuncios, escenas y animación. versiones poli-bajos están ahí para los videojuegos de deportes.\n","textures":"Explora una biblioteca de materiales 3D y encuentra las texturas listas para usar que mejor se adapten a tus proyectos: modelos para videojuegos, visuales fotorealistas o para ampliar tu colección.","various":"Estos son los modelos 3D que no encajan fácilmente en otras categorías, por lo que creamos una que aceptar a todos. { \"title\": \"rocas\", \"slug\": \"rocks\"}, { \"slug\": \"clouds\", \"title\": \"nubes\"}, { \"title\": \"logos\", \"slug\": \"logo\"}, { \"title\" : \"paraguas\", \"slug\": \"umbrella\"} y otros modelos no categorizado 3D están aquí.\n","vehicle":"Camiones, ambulancias, autobuses, trenes, tractores, bicicletas y motocicletas. Para quienes desean calles urbanas con vehículos realistas o de ciencia ficción. Ideal para juegos de simulación o carteles promocionales.","watercraft":"Incluye barcos, yates, naves y otros vehículos acuáticos. Los modelos riggeados son ideales para videojuegos y animaciones; los modelos CG para escenas donde la calidad visual es más importante que el conteo de polígonos."},"printable":{"art":"Aquí es donde las impresiones artísticas en 3D van. Buscando diseño de la escultura de fantasía que recrea obras clásicas y estatuas de personajes históricos, o inspirar figuritas, tal vez incluso logotipos 3D imprimibles? Esa es la categoría.\n","fashion":"Cosas bonitas y sofisticadas que se pueden usar se encuentran en esta categoría. Ver si se puede encontrar ningún uso para la era espacial de fantasía { \"slug\": \"footwear\", \"title\": \"calzado\"}, novedad de los vidrios y cosplay { \"slug\": \"accessories\", \"title\": \"accesorios\"} encontrar aquí.\n","gadgets":"Todo { \"title\": \"iPhone\", \"slug\": \"iphone\"}, { \"title\": \"IPAD\", \"slug\": \"ipad\"} (y otros dispositivos inteligentes) de los casos se puede encontrar en la categoría de gadget. Desde simples 3D complementos para piezas complejas y “hacks” para sus artículos, esta es la categoría de los mensajes publicitarios, y los amantes de la tecnología.\n","games-toys":"Juegos y juguetes es la categoría que cubre extrañas o monstruo imprimibles miniaturas 3D, pequeño { \"title\": \"tanques\", \"slug\": \"tank\"}, cobarde { \"slug\": \"chess\", \"title\": \"ajedrez\"} pedazos, cascos personaje de la película, afición parafernalia y accesorios. Esta es la categoría a-go para todas las cosas geek.\n","hobby-diy":"Si usted es nuestro próximo gran inventor y quiere construir su proyecto de bricolaje o conseguir algunas piezas de repuesto para su { \"slug\": \"drone\", \"title\": \"drone\"}, este es el lugar para estar. Además, cuenta con un bajo número de cosas para las personas que quieren tomar sus propias figuritas o réplicas de armas de plástico.\n","house":"Todo tipo de baratijas se pueden encontrar aquí. { \"title\": \"Lámparas\", \"slug\": \"lamp\"}, artístico { \"title\": \"copas\", \"slug\": \"bowl\"}, y todo tipo de { \"title\": \"titulares\", \"slug\": \"holder\"} si no se puede encontrar un lugar adecuado para su teléfono, mantener el orden de los palillos de dientes, pila de plumas o joyas en su escritorio.\n","jewelry":"La joyería es donde se va a conseguir las pulseras y los colgantes o anillos con diamantes. Mientras que las impresoras 3D no son lo suficientemente buenos para hacer diamantes - sin embargo - aún puede imprimir un anillo metálico como base para su anillo de TI propuesta friki.\n","miniatures":"3D imprimible { \"title\": \"caracteres\", \"slug\": \"character\"}, gente famosa, { \"title\": \"puntos de referencia\", \"slug\": \"landmark\"}, animales, { \"title\": \"Hombre\", \"slug\": \"male\"}, { \"slug\": \"female\", \"title\": \"hembra\"}, { \"title\": \"monstruos\", \"slug\": \"monster\"}, { \"title\": \"extraterrestres\", \"slug\": \"alien\"} - todo está aquí. Tanto si imprime vehículos utilitarios para poner en su mesa o la creación de miniaturas para sus juegos de mesa, es su categoría.\n","scie
16518nce":"Lo que puede ser clasificado como ciencia? Así, los modelos de esqueleto { \"slug\": \"bones\", \"title\": \"los huesos\"}, {médicos \"slug\": \"tools\", \"title\": \"herramientas\"} y astrofísica relacionados con el 3D modelos imprimibles que se pueden utilizar en muchos procesos de estudio.\n"}},"printable":{"art":"Aquí es donde las impresiones artísticas en 3D van. Buscando diseño de la escultura de fantasía que recrea obras clásicas y estatuas de personajes históricos, o inspirar figuritas, tal vez incluso logotipos 3D imprimibles? Esa es la categoría.\n","fashion":"Cosas bonitas y sofisticadas que se pueden usar se encuentran en esta categoría. Ver si se puede encontrar ningún uso para la era espacial de fantasía { \"slug\": \"calzado\"}, novedad de los vidrios y cosplay { \"slug\": \"accesorios\"} encontrar aquí.\n","gadgets":"Todo { \"title\": \"iPhone\", \"slug\": \"iPhone\"}, { \"title\": \"IPAD\", \"slug\": \"ipad\"} (y otros dispositivos inteligentes) de los casos se puede encontrar en la categoría de gadget. Desde simples 3D complementos para piezas complejas y “hacks” para sus artículos, esta es la categoría de los mensajes publicitarios, y los amantes de la tecnología.\n","games-toys":"Juegos y juguetes es la categoría que cubre extrañas o monstruo imprimibles miniaturas 3D, pequeño { \"title\": \"tanques\", \"slug\": \"tanque\"}, cobarde { \"slug\": \"ajedrez\"} pedazos, cascos personaje de la película, afición parafernalia y accesorios. Esta es la categoría a-go para todas las cosas geek.\n","hobby-diy":"Si usted es nuestro próximo gran inventor y quiere construir su proyecto de bricolaje o conseguir algunas piezas de repuesto para su { \"slug\": \"drone\"}, este es el lugar para estar. Además, cuenta con un bajo número de cosas para las personas que quieren tomar sus propias figuritas o réplicas de armas de plástico.\n","house":"Todo tipo de baratijas se pueden encontrar aquí. { \"title\": \"Lámparas\", \"slug\": \"lámpara\"}, artístico { \"title\": \"copas\", \"slug\": \"tazón\"}, y todo tipo de { \"title\": \"titulares\", \"slug\": \"titular\"} si no se puede encontrar un lugar adecuado para su teléfono, mantener el orden de los palillos de dientes, pila de plumas o joyas en su escritorio.\n","jewelry":"La joyería es donde se va a conseguir las pulseras y los colgantes o anillos con diamantes. Mientras que las impresoras 3D no son lo suficientemente buenos para hacer diamantes - sin embargo - aún puede imprimir un anillo metálico como base para su anillo de TI propuesta friki.\n","miniatures":"3D imprimible { \"title\": \"caracteres\", \"slug\": \"caracteres\"}, gente famosa, { \"title\": \"puntos de referencia\", \"slug\": \"punto de referencia\"}, animales, { \"title\": \"Hombre\", \"slug\": \"macho\"}, { \"slug\": \"hembra\"}, { \"title\": \"monstruos\", \"slug\": \"monstruo\"}, { \"title\": \"extraterrestres\", \"slug\": \"extranjero\"} - todo está aquí. Tanto si imprime vehículos utilitarios para poner en su mesa o la creación de miniaturas para sus juegos de mesa, es su categoría.\n","science":"Lo que puede ser clasificado como ciencia? Así, los modelos de esqueleto { \"slug\": \"los huesos\"}, {médicos \"slug\": \"herramientas\"} y astrofísica relacionados con el 3D modelos imprimibles que se pueden utilizar en muchos procesos de estudio.\n"},"titles":{"aircraft":"Modelos 3D de aviación","animal":"Modelos 3D de animales","animals":"Modelos 3D de animales","architectural":"Modelos 3D arquitectónicos","car":"Modelos 3D de coches","character":"Modelos 3D de personajes","exterior":"Modelos 3D de exteriores","food":"Modelos 3D de comida","furniture":"Modelos 3D de muebles","household":"Modelos 3D del hogar","industrial":"Modelos 3D industriales","interior":"Modelos 3D de interiores","military":"Modelos 3D militares","plant":"Modelos 3D de plantas","space":"Modelos 3D del espacio","textures":"Modelos 3D de texturas","vehicle":"Modelos 3D de transporte","watercraft":"Modelos 3D de transporte acuático"}},"title":{"cg":" Modelos 3D","print":" Archivos de impresión 3D"}}
16518,"competition":{"digital-art-competition":{"categories":{"number":"seis","page_description":"Participar en el Concurso de Arte Digital CGTrader mediante la presentación de sus trabajos de ilustración de carácter, carácter concepto de diseño, la ilustración de Medio Ambiente, Medio Ambiente concepto de diseño, objetos de diseño, y objetos de categorías concepto de diseño. Si usted tiene varias grandes obras que se adapten a diferentes categorías, no dude en aportar a las categorías correspondientes. Seleccione una categoría para ver los premios correspondientes a esa categoría."}}},"conversations":{"action_button_confirmation":"¿Realmente quieres reabrir esta solicitud?","reply_posted":"La respuesta ha sido publicada.","with_yourself":"no puede ser el mismo que el remitente."},"date":{"month_names":[null,"Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"]},"devise":{"failure":{"too_many_requests":"Ya solicitaste un correo para restablecer la contraseña. Por favor, espera unos minutos antes de intentarlo de nuevo."}},"disabled_cookies_modal":{"button":"Cancelar","text_1":"Usted no será capaz de explorar millones de grandes modelos 3D, añadir a la cesta o Comprar.","text_2":"Si quieres experimentar CGTrader totalmente, le recomendamos habilitar las cookies en este sitio.","title":"Parece que tiene las cookies del navegador desactivados"},"emails":{"error_notification":"Suscripción no encontrada.","success_subscribed_notification":"Te has suscrito correctamente.","success_unsubscribed_notification":"Te has dado de baja correctamente."},"errors":{"messages":{"invalid_vat":"no es un número de IVA intracomunitario %{country_adjective} válido","vies_down":"No se ha podido validar su número de IVA: El servicio VIES no está disponible, por favor inténtelo más tarde."}},"file_size_validator":{"errors":{"invalid_carrierwave":"Se esperaba un objeto CarrierWave::Uploader::Base","invalid_key":":%{key} debe ser un número entero no negativo","range_format":":in y :within deben ser un rango","range_unspecified":"Rango no especificado. Especifica la opción :within, :maximum, :minimum o :is."}},"file_type":{"default_error":"No se ha proporcionado la ruta al archivo."},"footer":{"bottom-row":{"privacy":"Política de privacidad","terms-link":"Términos y condiciones"},"links":{"buyLinks":[{"text":"Comprar modelos 3D","link":"/es/3d-modelos"},{"text":"Para empresas","link":"/discover/business-account"},{"text":"Conjuntos de datos 3D gratis","link":"/discover/datasets"},{"text":"Contratar a un diseñador 3D","link":"/3d-modeling-jobs"},{"text":"Modelos 3D gratis","link":"/es/gratis-3d-modelos"},{"text":"Modelos 3D para imprimir gratis","link":"/es/gratis-3d-de-impresion-modelos"}],"communityLinks":[{"text":"Comunidad","link":""},{"text":"Mejores diseñadores","link":"/designers"},{"text":"Desafíos","link":"/challenges"},{"text":"Foro","link":"/forum"}],"companyLinks":[{"text":"Empresa","link":"/discover/about-us"},{"text":"Blog","link":"/blog"},{"text":"Empleos","link":"/pages/careers"},{"text":"Centro de ayuda","link":"https://help.cgtrader.com"}],"sellLinks":[{"text":"Vender modelos 3D","link":"/discover/sell-3d-models"},{"text":"Haz crecer tus ventas","link":"/discover/sell-more"},{"text":"Información del mercado 3D","link":"/discover/3d-market-trends"},{"text":"Principales búsquedas de modelos 3D","link":"/discover/3d-models-top-searches"},{"text":"Principales búsquedas de impresión 3D","link":"/discover/3d-print-models-top-searches"}]},"subscribe":{"button-active":"Suscribir","button-disabled":"Suscrito","header":"Suscríbete a nuestras noticias:","placeholder":"Introduce tu correo electrónico","terms-link":"Términos de Uso","text":"Mediante la suscripción de confirmar que ha leído y acepta nuestra"}},"general":{"description":"CGTrader es un mercado en línea donde los artistas y diseñadores 3D puede comprar, vender o compartir modelos 3D. Fue fundada y está siendo desarrollado por un equipo de ingenieros y diseñadores 3D. Nuestro objetivo es democratizar mercado de valores 3D y cambiar la forma en que vendemos modelos 3D en línea. Por autores dejar ganar el máximo regalías, nos esforzamos para aumentar su motivación y elevar la calidad de los modelos 3D.","navigation":{"buy_3d_models":"Modelos 3D Comprar","sell_3d_models":"Vender Modelos 3D"},"search_placeholder":"Buscar entre más de 2 millones de modelos","site-search__field":"Buscar entre %{model_count}
16518 modelos 3D","site_title":"3D tienda Modelo","site_title_postfix":" | CGTrader"},"generator":{"landing_pages":{"three_d_model":{"defaults":{"closing_cta":{"heading":"Deja que la IA haga realidad tus sueños","subtitle":"Convierte un prompt o una imagen de referencia en un modelo 3D listo para producción, luego refínalo, retexturízalo y expórtalo en el formato que necesites."},"faq":{"heading":"Preguntas frecuentes","items":{"commercial":{"answer":"El uso comercial de tu modelo 3D de %{object} está disponible con un plan de pago de CGTrader AI. Consulta los Términos y Condiciones de CGTrader para conocer las condiciones de licencia aplicables a los modelos generados por IA.","question":"¿Puedo usar el modelo de %{object} generado con fines comerciales?"},"formats":{"answer":"Los modelos 3D de %{object} están disponibles en formato GLB, FBX, OBJ, STL o USDZ.","question":"¿En qué formato de archivo puedo obtener mi modelo de %{object}?"},"no_skills":{"answer":"No se requieren conocimientos de modelado 3D. Todo el modelado se realiza a través del generador de modelos de %{object}. El sistema gestiona la geometría, las proporciones y la estructura cuando introduces el prompt o subes una imagen. Incluso los principiantes pueden crear un modelo 3D completo de %{object} en minutos.","question":"¿Se necesitan conocimientos de modelado 3D para usar un generador de modelos de %{object}?"},"what_is":{"answer":"El generador de modelos 3D de %{object} de CGTrader, una herramienta basada en IA, crea un modelo tridimensional de %{object} a partir de instrucciones escritas o imágenes de referencia. Analiza tu idea y crea un modelo descargable para diseño, prototipos y visualización. Nuestras suscripciones de pago ofrecen límites mensuales más altos.","question":"¿Qué es un generador de modelos 3D de %{object}?"},"why_cgtrader":{"answer":"Los usuarios pueden confiar en CGTrader para disfrutar de una experiencia fluida, tiempos de procesamiento rápidos y resultados 3D de alta calidad. Esta herramienta es adecuada tanto para profesionales como para principiantes que buscan una solución fiable para crear modelos 3D de %{object}.","question":"¿Por qué usar el generador de %{object} con IA de CGTrader?"}}},"h1":"Generador de modelos 3D de %{object} con IA","hero_alt":"Generador de modelos 3D de %{object} con IA","hero_height":617,"hero_width":1080,"how_to":{"heading":"Genera %{object} a partir de imágenes o prompts de texto","paragraphs":["Es muy sencillo diseñar un modelo tridimensional de %{object}. En la barra lateral izquierda, en la parte superior izquierda, haz clic en el botón para subir tu imagen de %{object} y crear un modelo 3D. También puedes introducir un prompt en el campo «Describe lo que quieres crear» en la parte inferior. CGTrader AI mejora tu prompt automáticamente, por lo que una descripción breve es suficiente para comenzar.","La barra lateral derecha te ofrece un buen control de tu modelo 3D de %{object}. Elige ajustes como el tipo de malla, material, guía de prompts y formato de archivo. También puedes aplicar texturas para personalizar tu modelo de %{object}. Tras introducir tus instrucciones de texto o imagen, haz clic en «Generar». La herramienta procesará tu entrada en pocos minutos, según la carga del sistema, el material, el tipo de malla, las formas, los valores de seed y la calidad."],"steps":["Escribe tu descripción en texto y/o adjunta una imagen de referencia para mejorar la precisión final de tu modelo 3D de %{object}.","Selecciona uno de nuestros presets, o elige un motor de IA, añade un número de polígonos personalizado y selecciona el tipo de material para tu modelo 3D de %{object}.","Pulsa Generar para crear un modelo 3D de %{object} a partir de tu prompt y/o imagen de referencia.","Descarga tu modelo 3D de %{object} creado en distintos formatos (GLB, FBX, OBJ, STL o USDZ)."]},"intro":"¿Busca una forma sencilla y rápida de crear un modelo 3D de %{object} de alta calidad en minutos? Nuestro avanzado generador de modelos 3D de %{object}, CGTrader AI, puede convertir comandos escritos o imágenes de referencia en modelos completamente funcionales. Funciona con los mejores motores de generación 3D, incluidos Meshy, Hunyuan y Tripo3D. Permite crear modelos 3D de forma rápida tanto para principiantes como para expertos en diseño, para visualización, experimentación y prototipos. Es compatible con texto a 3D e imagen a 3D, y con impresoras 3D. Puede cargar una imagen en formato JPG o PNG, o escribir una descripción en texto. CGTrader AI se encarga del resto.","meta_description":"Generador de modelos 3D de %{object} con IA de CGTrader ☑️ Crea modelos 3D de %{object} detallados a partir de texto o imágenes. Empieza gratis ahora.","primary_cta":"Empieza a generar gratis","title":"Generador de modelos 3D de %{object} con IA (gratis) | CGTrader"}}},"rules":{"errors":{"front_view_required":"Se requiere una imagen de la vista frontal."}}},"home":{"index":{"3D-projects":{"info-section__cta":"Aprende más","info-section__description":"Donde las personas con emocionantes Proyectos en 3D se reúnen talentos del diseño 3D","info-section__heading_html":"\u003cStrong\u003e Proyectos en 3D \u003c/strong\u003e Hub Freelance"},"categories-conatainer":{"browse_all":"Ver todos los modelos","browse_all_tooltip":"Explorar todos los modelos 3D","discounts":"Descuentos"},"cgdream":{"cta":"¡Empiece gratis!","heading_html":"\u003cstrong\u003eExplora CGDream,\u003c/strong\u003e\u003c/br\u003e donde la IA se encuentra con los modelos 3D.","text_html":"\u003cstrong\u003e CGDream \u003c/strong\u003e es una aplicación de vanguardia que aprovecha el poder de los modelos generativos de IA y 3D para producir impresionantes imágenes 2D. \u003cstrong\u003e Tu viaje creativo comienza ahora. \u003c/strong\u003e"},"community":{"info-section__cta":"Unete a la communidad","info-section__heading_html":"Únete CGTrader \u003cstrong\u003e Comunidad \u003c/strong\u003e","info-section__icon-list_1":{"de
16518scription":"Mostrar sus mejores renders, obtener retroalimentación y reconocimiento","heading":"Galerías"},"info-section__icon-list_2":{"description":"Aprender nuevas habilidades y enseñar a los demás","heading":"Tutoriales"},"info-section__icon-list_3":{"description":"Encontrar tutoriales y discutir otros asuntos relacionados con 3D","heading":"Foro"}},"covid":{"timer-strip__item":"Recursos gratuitos para imprimir en 3D para COVID-19 Protección","timer-strip__item--button":"Modelos 3D Navegar"},"enterprise":{"info-section__cta":"Aprende más","info-section__heading_html":"Servicios 3D para el \u003cstrong\u003e Empresa \u003c/strong\u003e","info-section__icon-list_1":"Podemos hacer la implementación de AR para usted","info-section__icon-list_2":"Más conversiones de ventas, menos las devoluciones de productos","info-section__icon-list_3":"Calidad garantizada con control de calidad estricto"},"header":"Modelos 3D para VR / AR, impresión 3D y gráficos por computadora","marketplace":{"info-section__description":"Elegir %{models_count} 3D modelos para aplicaciones tales como juegos, AR / VR, publicidad, animación, películas y la impresión en 3D","info-section__heading_html":"Modelo 3D \u003cstrong\u003e del mercado \u003c/strong\u003e","info-section__image-list_1":{"description":"Comprar Modelos 3D profesional para su proyecto","heading":"Modelos 3D"},"info-section__image-list_2":{"description":"-Poli bajos Modelos PBR 3D listo para videojuegos, VR y AR aplicaciones en tiempo real","heading":"Bajo poli modelos 3D PBR"},"info-section__image-list_3":{"description":"Encuentra 3D Print diseños para su impresora 3D","heading":"Los modelos de impresión 3D"},"view_all":"Ver todos"},"search_dropdown":{"ai_cards":{"image_to_3d":{"description":"Convierte una imagen en un modelo 3D","title":"Imagen a 3D"},"text_to_3d":{"description":"Describe tu idea para crear un modelo 3D","title":"Texto a 3D"}},"ai_section_title":"Crear con IA","aria_label":"Sugerencias de búsqueda","columns":{"cg":{"search_label":"Buscar en Modelos 3D","title":"Modelos 3D"},"print":{"search_label":"Buscar en Impresión 3D","title":"Modelos para impresión 3D"}}},"sell":{"info-section__heading_html":"\u003cstrong\u003e Vender \u003c/strong\u003e los modelos 3D y encontrar proyectos independientes","info-section__icon-list_1":{"description":"Vender sus modelos en 3D y ganar hasta 85% de regalías","link":"Vender modelos 3D"},"info-section__icon-list_2":{"description":"Trabaja con clientes de la empresa","link":"Recibe Calificado"},"info-section__icon-list_3":{"description":"Externalizar sus habilidades y obtener hasta un 95% de regalías","link":"Navegar Modelado 3D Empleos"},"info-section__icon-list_4":{"description":"Construir su cartera y ser visible","link":"Ver diseñadores 3D"}},"seo":{"description":"Explora más de 2 millones de modelos 3D en línea para proyectos de impresión, juegos, VR/AR y diseño. Descarga, personaliza o contrata expertos 3D - todo en CGTrader.","title":"CGTrader - Modelos 3D y Mercado de Impresión 3D"}}},"homepage":{"budget_counter":{"range_0_10":"Menos de $10","range_10_20":"$10 - $20","range_20_15000":"Más de $20"},"quotes":{"quote_0":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Menores comisiones tomadas de los medios vendidos mejores precios para los compradores. Comprobar y comparar los precios en CGTrader y las otras tiendas."},"quote_1":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"La ventaja más notable de CGTrader es el sistema de activación de tarifa plana que nos da altos rendimientos. Usted no está tomando un porcentaje de la comisión de las ventas. Después de la venta, recibimos el 100% del precio. Sólo si queremos continuar la venta de ese producto, es posible volver a activar de nuevo mediante el pago de $ 5. solución elegante."},"quote_10":{"author":{"name":"threed Ken","username":"3DKen"},"text":"El sistema de activación de tarifa plana es ingenioso. Me preguntaba por qué nadie había pensado en esto antes. Después de la venta, pagar una pequeña cuota para activar un producto y eso es todo. El vendedor está en pleno control."},"quote_11":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"Hay dos cosas que me gusta de CGTrader. En primer lugar, no hay ocultos que significa que cualquier cosa aquí. No es necesario tener cuidado con CGTrader. En segundo lugar, esta tienda es por los artistas y para los artistas. Ellos se entienden mejor. Algo le molesta aquí? ... hacerles saber y que se asegurará de que usted se sienta cómodo aquí."},"quote_12":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"El sistema de activación de tarifa plana es el mejor! (Al menos no he encontrado nada mejor que esto). Es muy realista y justo para los vendedores. No les importa la cantidad de beneficios que usted hace, simplemente pagar la renta fija. Pero si usted no hace ninguna venta, usted no tiene que pagar el alquiler, ya sea. El comercio justo CG está aquí!"},"quote_13":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"CGTrader atrae a los clientes incluso de países en desarrollo, ya que los precios son más baratos que en otras tiendas. Al mismo tiempo, los autores pueden ganar más, porque no hay tal cosa como altas tasas de comisión. Es una forma correcta de valorar el trabajo de los artistas 3D."},"quote_14":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"La interfaz intuitiva de los editores y herramientas innovadoras como la recomendación de la etiqueta y formas flexibles descripción hace que el proceso de publicación fácil y rápido."},"quote_15":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"enfoque de la venta 3D de CGTrader es diferente y no tienen nada en común con otras tiendas que vendía. Pagar un precio rebajado de $ 5 si la venta es una gran idea y me permite ofrecer precios más bajos aquí. Cliente y el artista se beneficiaron con esta forma de trabajar."},"quote_16":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"El sistema de carga es fácil y clara. Es bueno saber que detrás del proyecto son personas con años de experiencia en materia de gráficos de ordena
16518dor. Cada vez que tenía un problema, el equipo CGTrader fija rápidamente."},"quote_17":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"CGTrader me permite vincular mi perfil con mi página web personal. Los clientes pueden ponerse en contacto conmigo, si no están seguros acerca de una característica de un artículo que desea comprar o simplemente desea una modificación de la misma. Es como tener una tienda 3D personal en un mercado grande!"},"quote_2":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Hemos disfrutado de una gran y sensible apoyo del equipo CGTrader. El personal muy amablemente se ofreció a hacer la publicación de todos los modelos para nosotros, lo cual es una gran ayuda para los vendedores profesionales."},"quote_3":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"En esta plataforma podemos ofrecer precios mucho mejor a nuestros clientes. Por ejemplo, todos nuestros productos tienen un precio 20% menor que en cualquier otro lugar y aún así obtener más beneficios en comparación con otras tiendas!"},"quote_4":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"Este es el mejor sistema hasta la fecha. Todas las otras tiendas en 3D en línea toman un% de nuestros beneficios. Esta fue la característica que captó nuestro interés en CGTrader desde el principio. Clientes, la compra de CGTrader, saben que la mayor parte de su dinero para ir al autor, que al final va a hacer más y mejores modelos si mejoran sus ganancias."},"quote_5":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"CGTrader es la tienda en línea 3D que hizo que los de más rápido crecimiento de ventas y ganancias para nosotros. El sistema de activación de tarifa plana es el mejor que se puede encontrar, ya que premia a los creadores de contenido en lugar de los intermediarios."},"quote_6":{"author":{"name":"Ričardas Marozas","username":"autobomba"},"text":"Cuando recibí $ 160 a mi cuenta de Paypal, no sabía lo que está pasando, ya que nunca antes había sucedido, pero luego pensé que podría ser una venta CGTrader y yo estaba en lo cierto. Se vende el mismo paquete en Turbosquid el mes pasado por $ 189, pero después de 40% tasa de regalías y el impuesto del 30%, todo lo que tengo $ 52.92 - monedas de un centavo en comparación con $ 160 desde CGTrader. Además, el cliente pagado $ 29 de menos - ahora que es un buen negocio."},"quote_7":{"author":{"name":"threed Ken","username":"3DKen"},"text":"Al principio estaba escéptico sobre tal un acuerdo bueno para los vendedores que no es la norma para las grandes tiendas en línea 3D. Cuando supe de la activación del sistema de tarifa plana, me impresionó de tal manera creativa de la venta de activos digitales. Venta, a continuación, active mediante el pago de una cuota de $ 5. Todo tiene sentido para mí. He subido unos cuantos para probar y verdaderamente, cuando llegué a mi primera venta, que era 100% de la realeza!"},"quote_8":{"author":{"name":"threed Ken","username":"3DKen"},"text":"Estaba tan acostumbrada a muchos otros sitios que cobran entre 30% - 60% de comisión hasta que descubrí CGTrader. Como vendedor, definitivamente como el alto porcentaje de los rendimientos de regalías."},"quote_9":{"author":{"name":"threed Ken","username":"3DKen"},"text":"La experiencia vendedor es fantástico. presentación del producto es una brisa. Y sí, el sistema me permitió guardar un borrador de mi presentación de productos de cada etapa del camino."}}},"hyperwallet":{"failed_payment_agreement":"Ocurrió algo inesperado","success_notification":"El acuerdo de pago se ha actualizado correctamente.","success_payment_agreement":"El acuerdo de pago se ha enviado correctamente."},"item-search-filters":{"3d-models-url":"3d-modelos","3d-print-models-url":"3d-de-impresion-modelos","dedicated-search-page-url":{"animated":"/animados-","free":"/gratis-","low_poly":"/de-bajo-poli-","pbr":"/pbr-","rigged":"/aparejado-"}},"item_use_case":{"not_valid":"Archivo no válido","pundit":{"not_authorized":"No está autorizado para realizar esta acción."},"title":"Caso de uso para %{item_name}"},"items":{"already_purchased":"Ya has comprado este artículo.","filters":{"attributes":"atributos","free":"Gratis","max_value":"$ 500 +","min_value":"$ 2","reset":"Restablecer filtros"},"footer":{"next":"Próximo","per_page":"Por página:","previous":"Previo"},"geometry_type_forbidden":"¡Tipo de geometría prohibido!","last_card":{"button_text":"Solicitar un modelo 3D personalizada","text":"No se pudo encontrar lo que estás buscando?"}
16518,"migrations":{"errors":{"invalid_owner":"El nuevo propietario no puede hacerse cargo de los artículos.","unpublished_items":"Todos los artículos deben estar publicados."}},"new_price_applied":"Los nuevos precios se han aplicado correctamente a todos los artículos.","new_price_applied_to_selected_items":"Los nuevos precios se han aplicado correctamente a los artículos seleccionados.","new_prices_to_filtered_models":"Los nuevos precios se han aplicado correctamente a todos los artículos filtrados.","order_processed":"Pedido procesado.","order_processing":"Procesando pedido.","something_went_wrong":"Algo salió mal","title_error":"El título no debe contener símbolos que no sean latinos","uniq_user":"ya ha enviado un caso de uso para este artículo","upload_error":"Error de carga.","validate_license_for_zero_price":"no es válido para un artículo gratuito","validate_minimum_price":"Las ofertas de precio no deben estar por debajo del mínimo del 50% del precio original","verify_printable_formats_error":"Por favor, sube al menos un archivo .stl, .obj o .wrl.","you_can_not_delete_models":"No puedes eliminar modelos"},"jobs":{"about":{"hire_designers_page":{"hero":{"description":"3D Projects facilita la contratación de artistas 3D freelance de calidad y te ayuda a resolver todos tus retos de modelado.","heading_html":"Descubre la mejor plataforma de freelancers para tus \u003cstrong\u003etrabajos de modelado 3D\u003c/strong\u003e"},"sections":{"call_to_action":{"description":"Publica tu proyecto gratis y descubre lo fácil que es resolver tu próximo reto de modelado con Proyectos 3D.","title":"¡Contrata al mejor talento 3D hoy mismo!"},"freelancer_selection":{"title":"Haz que tu próximo proyecto 3D sea un éxito","widest_selection":{"description":"No te conformes con menos. Con 40.000 diseñadores 3D freelance, CGTrader es la plataforma más grande para ayudarte a conectar con especialistas cualificados: modeladores, animadores, riggers, expertos en impresión 3D y todo lo demás.","title":"La mayor selección de freelancers 3D"}},"how_it_works":{"steps":{"describe":{"description":"Cuéntanos lo que necesitas. Usa nuestro formulario sencillo, añade imágenes y publica tu oferta freelance en minutos.","title":"Describe tu proyecto"},"pay":{"description":"Prepara el presupuesto de forma segura en CGTrader y hazle saber a tu freelancer que estás listo para empezar. Chatea, comparte archivos y visualiza el progreso del proyecto fácilmente en tu espacio de trabajo dedicado. Libera el pago cuando estés satisfecho con el resultado final.","title":"Paga por el resultado"},"select":{"bullets":["Recibe presupuestos de diseñadores calificados directamente en tu bandeja de entrada","Chatea con diseñadores en tiempo real","Explora sus proyectos anteriores","También puedes buscar, filtrar e invitar a freelancers según sus habilidades 3D, valoraciones y tus preferencias de software o formatos"],"title":"Selecciona al mejor freelancer"}},"title":"Realiza tu trabajo en 3 pasos simples"},"payment_quality":{"description":"CGTrader garantiza que los servicios freelance cumplirán con tus requisitos y se entregarán a tiempo. Nuestro equipo de control de calidad está disponible siempre que lo necesites.","title":"Calidad garantizada"},"secure_payments":{"description":"Nuestro servicio de depósito en garantía asegura que tus fondos estén protegidos y que solo pagues cuando estés satisfecho con el resultado final.","title":"Pagos seguros"},"workspace_convenience":{"description":"Olvídate de la frustración de cambiar entre pestañas y diferentes herramientas. Con el espacio de trabajo dedicado, puedes gestionar fácilmente todo en un solo lugar: múltiples proyectos, facturas, conversaciones, intercambio de archivos y mucho más.","title":"Comodidad de una sola pantalla"}},"shared":{"post_project_cta":"Publicar proyecto"}},"navigation":{"find_jobs_cta":"Encontrar trabajos 3D","hire_freelancers_cta":"Contrata freelancers 3D","how_it_works":"Cómo funciona","why_projects":"Por qué los proyectos 3D"}},"agreed_to_terms_of_use":"Debes aceptar los Términos y Condiciones de 3D Projects para continuar.","budget_error_message":"es demasiado alto","check_designer_uniqueness_error":"El diseñador solo puede ser invitado una vez","check_ownership_error":"El invitador no es propietario del proyecto","check_status_error":"Los diseña
16518dores solo pueden ser invitados a proyectos aprobados.","deadline_error_message":"La fecha límite debe estar en el futuro","discount_code_error":"Lo sentimos, el código de descuento no existe o ha caducado.","email_subject":"Oportunidad de proyecto 3D: %{subject}","error_check_application_uniqueness":"Solo puedes postularte una vez","error_check_applier":"No puedes postularte a tu propio proyecto 3D","error_check_job_availability":"El usuario no está autorizado para postularse","error_check_job_status":"Las solicitudes de proyectos están en espera hasta que se complete nuestro nuevo proceso de moderación, normalmente dentro de 3 días hábiles. ¡Gracias por tu comprensión!","modeling_request_failed":"¡La solicitud de modelado ha fallado!","offer_has_been_sent":"¡Gracias, tu oferta ha sido enviada!","offer_is_closed":"La oferta está cerrada.","payment_successful":"Pago realizado con éxito.","payment_under_review":"El pago está en revisión.","share_with_yourself_error":"No está permitido compartir plantillas de Contrátame contigo mismo","system_messages":{"buyer_cancels_job":"El proyecto fue cancelado y movido a la pestaña %{archive_url}.","buyer_edits_hire_me_template":"Los detalles del proyecto fueron actualizados por el cliente.","buyer_edits_job_details":"Los detalles del proyecto fueron actualizados.","buyer_marks_job_as_finished":"El proyecto fue marcado como finalizado y movido a la pestaña %{archive_url}.","buyer_marks_job_as_finished_with_resell":"El cliente marcó el proyecto como finalizado y te permitió vender los resultados en CGTrader. ¡Asegúrate de \u003cb\u003e\u003ca href=\"/profile/upload/model\"\u003esubir\u003c/a\u003e\u003c/b\u003e el modelo creado!","buyer_negotiates":"El cliente negoció tu oferta de precio.","buyer_pays_for_the_job":"El proyecto fue prepagado y movido a la pestaña %{in_progress_url}.","designer_accepts_changes":"El diseñador revisó los detalles actualizados.","designer_accepts_offer":"El diseñador aceptó tu oferta.","designer_edits_offer":"El diseñador editó su oferta.","designer_negotiates":"El diseñador negoció tu oferta.","designer_withdraws_offer":"El diseñador retiró su oferta.","job_expires":"El proyecto expiró y fue movido a la pestaña %{archive_url}."},"template_error":"¡No se han creado proyectos!","user_not_exists":"El usuario con ese nombre de usuario no existe.","want_that_job":"¡Quiero este trabajo!"},"kodama":{"invalid_request":"¡Solicitud no válida!"},"landing":{"consent_not_saved":"El consentimiento no se guardó","failure_notification":"Todos los campos obligatorios de la solicitud de acceso a la API deben ser válidos.","success_notification":"Gracias por tu solicitud. Nos pondremos en contacto contigo en breve."},"layout":{"chunk_load_notice":"Algo salió mal al cargar esta página. Por favor, actualízala.","cookies_policy":{"accept":"Aceptar","learn_more":"Aprende más","text":"Nuestros utiliza el sitio web cookies para recopilar datos estadísticos de visitantes y la interacción pista con la comunicación de marketing directo / mejorar nuestro sitio web y mejorar su experiencia de navegación. Por favor, vea nuestro Aviso de cookies para obtener más información acerca de las cookies, datos que recogen, que puedan acceder a ellos, y sus derechos."},"translations_notice":{"cta":"Cambiar a English","text":"¡Hola! Estamos traduciendo CGTrader. Las traducciones están en curso y algunas secciones pueden no estar disponibles en español."}},"model":{"already_purchased":"Ya has comprado este producto. Puedes descargarlo desde tu \u003ca href='/profile/cart/purchased'\u003ecarrito de compras\u003c/a\u003e. Si lo deseas, puedes comprarlo nuevamente.","animated":"Animado","artist_profile":"Perfil del artista","author":"Autor","author_get_your_payment":"El autor recibirá el 100% de tu pago. CGTrader permite a los artistas ganar al máximo y los motiva a vender contenido de alta calidad.","author_products":"Más productos de \u003ca href=\"%{author_url}\"\u003e%{username}\u003c/a\u003e","authorised_seller":"Vendedor autorizado de CGTrader","authority":"Autoridad","breadcrumb":{"3d_models":"Modelos 3D","home":"Inicio"},"bulk_download":{"download_all":"Descargar todos los archivos","files_included":{"one":"%{count} archivo incluido","other":"%{count} archivos incluidos"},"too_large":"Este modelo es demasiado grande para descargarlo como un solo archivo (más de %{limit}). Por favor, descarga los archivos individualmente."},"buy":"Comprar","cannot_download":"Solo los usuarios con %{experience_needed} puntos de reputación pueden descargar más de %{models} modelos gratuitos. Aprende más sobre niveles y reputación \u003ca href='%{reputation_url}' target='_blank'\u003eaquí\u003c/a\u003e.","collection":"pack 3D","contact_author":"Contactar al autor","description":"Descripción","download":"Descargar","download_limit_exceeded":"Has excedido tu límite diario de descargas. Por favor, inténtalo de nuevo mañana. Este límite no aplica para usuarios que han realizado una compra en los últimos 12 meses.","download_modal":{"chat_with_us":"Chatea con nosotros","contact_support":"Por favor, contáctanos a través del chat.","download_file":"Descargar %{name}","empty":"No se han encontrado archivos","generic_error":"Algo salió mal, por favor inténtalo de nuevo","link_error":"Lo sentimos, pero parece haber un pequeño problema con tu enlace de descarga.","pbr_ready":"Compatible con PBR","preparing":"Preparando tu descarga...","title":"Descargar ar
16518chivos"},"format_downloads":"Descargas de formato de software 3D","game_ready":"Listo para juegos (lowpoly)","geometry":"Geometría","license":"Licencia","low_poly":"Low poly","make_offer":"Hacer oferta","materials":"Materiales","messages":{"no_files":"No hay archivos subidos","no_rating":"No hay valoraciones disponibles para este modelo aún"},"model_details":"Detalles del modelo 3D","pbr_textures":"Texturas PBR","plugin_used":"Plugin usado","polygons":"Polígonos","price":"Precio","print_ready":"Listo para impresión 3D","product_id":"ID del producto","product_support":"Soporte del producto","product_type":"Tipo de producto","published":"Publicado el","rated":{"error":"Ya has calificado este modelo","success":"Gracias por calificar este modelo"},"rating":"Calificación del modelo 3D","ratings":{"desc":{"negative":"Los usuarios que compraron este modelo 3D creen que es de menor calidad de lo esperado","positive":"Los usuarios que compraron este modelo 3D creen que es de buena calidad"},"negative":"negativo","positive":"positivo"},"recently_viewed_models":"Modelos vistos recientemente","renderer":"Renderizador","report":"Reportar","rigged":"Rígido","scanned":"Escaneado","scripts-plugins":"Scripts y complementos","similar_models":"Modelos similares","statistics":"Estadísticas","subscription":{"limit_reached":"Has alcanzado el límite de descargas para este período de facturación.","limit_reached_detailed":"Has alcanzado tu límite de %{limit} descargas de este mes. Tu límite se restablece el %{reset_date}. Para descargar más modelos ahora, puedes comprarlos individualmente.","limit_reached_detailed_no_date":"Has alcanzado tu límite de %{limit} descargas de este mes. Para descargar más modelos ahora, puedes comprarlos individualmente."},"tags":"Etiquetas","textures":"Texturas","title":"%{item_title} modelo 3D","unwrapped_uvs":"UVs desplegadas","uv_mapping":"Mapeo UV","vat_notice":"Todos los precios son sin IVA","version":"Versión","vertices":"Vértices","vr":"Modelos VR"},"models":{"error":{"cannot_be_blank":" no puede estar vacío."}},"navigation":{"3d-models":{"3d-printing":"Archivos de impresión 3D","discounts":"Descuentos","find_designers":"Encuentra los mejores diseñadores","free":"Modelos en 3D gratuitos","pbr":"PBR","scanned_3d-models":"Modelos escaneados 3D","scripts_apps":"Scripts y Aplicaciones","show-all":"Mostrar todo","textures":"Texturas","title":"Modelos 3D","vr_low-poly":"VR / Low-poli"},"already_added_message":"ya ha sido añadido a la cesta","already_purchased_message":"Que ya han comprado este producto.","aria":{"account":"Cuenta","cart":"Carrito","close_menu":"Cerrar menú","logo":"CGTrader - Modelos 3D / Diseñadores 3D","messages":"Mensajes","notifications":"Notificaciones","open_menu":"Abrir menú","search":"Buscar","search_by_image":"Buscar por imagen"},"budgets":{"below_10":"Menos de $10","collections":"Paquetes","free":"Gratis","more_than_20":"Más de $20","more_than_50":"Más de $50","sale":"En Oferta","subscription":"Suscripción"},"cart_limit_reached":"Has alcanzado el límite de 500 artículos en tu carrito. Para continuar comprando, por favor completa tu pedido actual y comienza uno nuevo para cualquier artículo adicional.","cart_widget":{"continue_shopping":"Continuar comprando","discount":"De descuento con el cupón de la empresa: ","discount_applied":"Descuentos aplicados:","final":"Precio final: ","have":"Tienes ","items":" artículos en su carrito de compras.","left_to_unlock_discounts":"Faltan {{leftToUnlock}} para desbloquear descuentos","potential_discount":"Descuento potencial:","proceed":"Pasar por la caja","recently_viewed_empty_text":"Comienza a explorar y tus artículos vistos recientemente aparecerán aquí.","recently_viewed_empty_title":"Nada aquí todavía","recently_viewed_tab":"Visto recientemente","remainer":"Para activar los puntos de CGTrader, añade: ","sale_discounts":"Descuentos aplicados: ","sale_time_left":"La venta termina en","sale_tooltip":"Los descuentos de nuestras campañas de venta solo se aplican cuando tu carrito llega a ${{threshold}} o más.","sale_tooltip_text":"Este artículo está en venta, pero el descuento solo se aplicará una vez que el subtotal de tu carrito llegue a ${{threshold}} o más.","text":"Su cesta está vacía.","title":"Carrito de compras","total":"Total: ","used":"Créditos utilizados: ","view":" Ver carrito","wishlist_empty_text":"Comienza a explorar y guarda tus favoritos para más tarde — aparecerán aquí.","wishlist_empty_title":"Tu lista de deseos está vacía","wishlist_tab":"Lista de deseos"},"community":{"awards":"Premios de Diseñadores 🏆","blog":"Blog","challenges":"Desafíos","community_ratings":"Valoración de la comunidad","designers":"Diseñadores 3D","digital_art":"Concurso de Arte Digital","forum":"Foro","galleries":"Galerías","invite":"Invita a un amigo","printlist":"ImprimeLista 3D","sell_3d-models":"Vender Modelos 3D","title":"Comunidad","tutorials":"Tutoriales"},"conversations_widget":{"conversations":"conversaciones","messages":{"loading":"Cargando la bandeja de entrada"},"types":{"cgtrader_support":"Soporte CGTrader","file_conversion_request":"solicitud de conversión de archivos","
16518job_application":"Proyecto 3D","model_feedback":"Comentarios sobre el producto","model_support":"modelo de ayuda","offer":"Precio de oferta","personal":"Mensaje personal","promotions":"promociones"}},"create_ai":{"community_gallery":"Galería de la comunidad","free_trial":"Prueba gratuita","generate_3d_model":"Generar modelo 3D","generation":{"dismiss":"Descartar notificación","error_title":"Problema de generación","generating_title":"Tu modelo 3D se está generando","open_model":"Abrir modelo","ready_title":"¡Tu modelo 3D está listo!"}},"create_with_ai":"Crear con IA","credits_guest_widget":{"create":"Crear una cuenta ahora!","discount":" descuento","for":" para tu ","get":" y obtener hasta ","learn_more":"Más información sobre los créditos de Fidelidad CGTrader ","link":"aquí","purchase":"¡primera compra!","sign-up":"Regístrate","text_below_1":"Los créditos pueden ser utilizados en la compra \"","text_below_2":"\"Firmar los modelos marcados y cubrir hasta el 30% de descuento del precio."},"credits_user_widget":{"condition_1":"Los puntos de CGTrader se pueden aplicar a la compra de modelos marcados con el signo \u003ci class=\"custom-font-icon custom-font-icon--small icon-wallet\"\u003e\u003c/i\u003e, proporcionando hasta un 30% de descuento sobre su precio.","condition_2":"Los puntos solo se pueden usar si el valor total de su carrito es de \u003cstrong\u003e$%{minimum}\u003c/strong\u003e o mas.","condition_3":"Durante ventas o promociones activas, no se pueden usar puntos.","condition_4":"Tenga en cuenta que los puntos tienen una fecha de vencimiento: sus puntos caducarán en \u003cstrong\u003e%{days}\u003c/strong\u003e dias.","earn_rate":"Crédito ganan tasa ","first_text_1":"Consideramos que es un regalo para su primera compra - se puede utilizar en la compra de créditos \"","first_text_2":" \"Modelos marcados y obtener hasta un 30% de descuento de su precio. Los créditos sólo se pueden utilizar si el valor total de su compra es igual o superior a","important":"IMPORTANTE:","learn_more":"Aprende más","lifetime_spending":"Gasto de por vida: ","list_item":"Mediante el aumento de sus gastos por vida en CGTrader, su crédito ganan tasa aumenta también.","message":" Los créditos no pueden ser utilizados durante las campañas de venta, pero todavía se puede aumentar su nivel de regalías y recoger más créditos para su próxima compra.","more_credits":"¿Cómo puedo obtener más créditos?","next_level":"Siguiente nivel: ","notice":"AVISO: Los puntos de CGTrader no se pueden usar durante la venta en curso, pero estarán disponibles nuevamente una vez que finalice la venta. Mientras tanto, descubre grandes ofertas en modelos 3D \u003ca href=\"https://www.cgtrader.com/3d-models?sale_off=1\"\u003eaquí\u003c/a\u003e","par_1":"Tienes ","par_2":" CGTrader créditos.","par_2_variation":" en Puntos que puedes usar cuando los sale off no estan activos.","regular_text_1":"Los créditos pueden ser utilizados en la compra \"","regular_text_2":" \"Firmar los modelos marcados y cubrir hasta el 30% de descuento de su precio.","title":"CGTrader descuento de fidelidad"},"enterprise":{"management":"Gestión de activos 3D","title":"Producción de Contenido 3D"},"hire_a_freelancer":"Contratar freelancer","item_add_message":"éxito ha sido añadido a la cesta","item_restricted_message":"Este artículo no se puede comprar debido a las sanciones impuestas al país del diseñador (RU/BY).","navbar":{"actions":{"new":"Nuevos","top_selling":"Más vendidos","trending":"Tendencias"},"budgets":{"below_10":"Menos de $10","free":"Gratis","more_than_20":"Más de $20","more_than_50":"Más de $50","sale":"En Oferta","up_to_10":"Hasta $10"}},"projects":{"3d_customs":"3D a medida","3d_printing":"Impresión 3D","about":"Acerca de los proyectos en 3D","browse":"Ver proyectos","buyer_perspective":"Los diseñadores de coches en 3D","for_designers":"Para diseñadores","my_projects":"Mis proyectos","seller_perspective":"Proyectos en 3D independientes","start":"Iniciar un proyecto","work":"Trabaja con CGTrader"},"sell_3d-models":"Vender Modelos 3D","sellers_hub":"Centro de vendedores","subscription_includes_item_message":"Este artículo ya está incluido en tu suscripción.","tooltips":{"cart":"Carrito de compras","conversations":"Mis conversaciones","discounts":"CGTrader descuento de fidelidad","notifications":"Notificaciones","publisher":"Editor"},"user":{"ban_notification":"Esta cuenta de usuario está suspendida. Por favor, contacta con nuestro soporte para obtener una explicación.","cannot_refer_yourself":"no puedes recomendarte a t
16518i mismo","email_confirmation":"Tu dirección de correo electrónico ha sido verificada.","email_reconfirmation":"ya estaba confirmada, por favor intenta iniciar sesión","ends_at":"Finaliza en: %{date}","handle_error_tfa_code_missmatch":"El código de seguridad que ingresaste es incorrecto. Por favor, inténtalo de nuevo.","handle_error_tfa_code_missmatch_last":"Tu código de seguridad ha caducado. Si deseas continuar, haz clic en reenviar correo y utiliza el nuevo código proporcionado.","handle_error_tfa_default":"Algo salió mal, por favor inténtalo más tarde.","handle_error_tfa_expired":"Tu código de seguridad ha caducado.","hundred_percent_royalties_popover":"Estás participando en una campaña temporal de ganancias del 100%: todos los ingresos de las ventas de nuevos modelos irán a ti hasta el final de la campaña","invalid_email_message":"Por favor, introduce una dirección de correo electrónico válida para continuar.","login":"Iniciar sesión","meta_description":"Mira el perfil del diseñador 3D %{username} en CGTrader, su portafolio y modelos 3D disponibles. Descubre los mejores modelos 3D de los principales artistas de la industria.","meta_title_suffix":"Diseñador 3D","not_valid_birthday":"La fecha de nacimiento no es válida.","offline":"Desconectado","online":"En línea","page_title":"Perfil del diseñador","rate_models_limit":"Hoy no puedes calificar más modelos.","semantic_description":"Modelos 3D, obras y portafolio del diseñador. Mira sus creaciones en CGTrader","semantic_title":"Perfil del diseñador","session_failure":"Se ha proporcionado un nombre de usuario/correo electrónico o contraseña incorrectos","session_password_expired":"Tu contraseña ha caducado por inactividad. Restablécela a través del enlace enviado a tu correo electrónico.","session_two_fa_failure":"La verificación del código ha fallado","session_unconfirmed":"Tu dirección de correo electrónico aún no ha sido confirmada.","token_not_recognized":"El token no fue reconocido","twitter_shareable_text":"Un #3dartist que vale la pena seguir","username_error":"no debe contener espacios ni símbolos que no sean latinos"},"user_menu":{"buyer":{"freelancers":"encontrar freelancers","purchases":"Mis compras","sell":"Vender Modelos 3D","wishlist":"Modelo 3D Lista"},"dashboard":"administrador del tablero de instrumentos","logout":"Cerrar sesión","public_profile":"Perfil público","seller":{"analytics":"Analítica","find_new_projects":"Encontrar nuevos proyectos 3D","freelance":"Proyectos en 3D independientes","freelance_cgtrader":"Independiente con CGTrader","models":"Mis modelos","publish_project":"Publicar un proyecto","publishing":"Publicación","sales":"Mis ventas","subscription":"Suscripción","wishlist":"Modelo 3D Lista"},"settings":"ajustes"}},"number":{"currency":{"format":{"delimiter":",","format":"%u%n","precision":2,"separator":".","unit":"€"}}},"owly":{"failed_set_pending":"Error al establecer como pendiente.","failed_to_approve":"Error al aprobar.","failed_to_suspend":"Error al suspender.","failed_to_update_license":"Error al actualizar la licencia.","failed_to_update_status":"Error al actualizar el estado de aprobación."},"print_models":{"browse_all_makes":"Ver todos los Ejemplos 3D","browse_all_models":"Ver todos los modelos","description":"Descubre modelos populares listos para impresión 3D, recursos imprimibles y archivos STL para juegos, arte, proyectos del hogar y más.","makes_description":"Explora los últimos ejemplos 3D subidos por la comunidad - ejemplos reales impresos que muestran cómo se ven los modelos 3D en el mundo físico.","makes_title":"Nuevos Ejemplos 3D","new_description":"Descubre las últimas incorporaciones a nuestra colección — diseños frescos listos para descargar e imprimir en tu próximo proyecto.","new_title":"Nuevos modelos para impresión 3D","title":"Modelos 3D de impresión en tendencia"},"profile":{"account_settings":{"account":{"email":"Correo Electrónico","submit":"Guardar","title":"Perfil","update":{"updated":"Perfil actualizado"},"username":"Nombre de Usuario"},"email_settings":{"descriptions":{"ai_studio_notifications":"Notificaciones sobre tus generaciones de CGTrader AI, incluido cuando un modelo que aún no has abierto está listo","communication":"Información sobre tu c
16518omunicación con otros usuarios de CGTrader, incluidos mensajes personales, nuevas ofertas de precios y trabajos personalizados, comentarios y solicitudes de soporte de productos","news":"Información sobre productos de CGTrader, anuncios de nuevas funciones y noticias de la comunidad","onboarding_and_edu":"Información sobre tu proceso de incorporación y consejos para aprovechar al máximo CGTrader","push_notifications":"Notificaciones sobre actividad y actualizaciones importantes de la cuenta","sales_and_offers":"Información sobre ofertas especiales, ventas de modelos 3D y créditos de bonificación de CGTrader","seller_sold_item":"Información sobre tus modelos 3D vendidos y pagos recibidos"},"privacy_policy_html":"Por favor lee nuestra \u003ca href='https://www.cgtrader.com/pages/privacy-policy' 'target='_blank'\u003ePolítica de Privacidad\u003c/a\u003e para más información. ","titles":{"ai_studio_notifications":"Notificaciones de CGTrader AI","communication":"Comunicación (Mensajes personales, comentarios de modelos 3D, actividad de la comunidad, ofertas)","news":"Noticias 3D / Actualizaciones de CGTrader","onboarding_and_edu":"Incorporación y Educación","push_notifications":"Notificaciones Push","sales_and_offers":"Ventas y ofertas especiales","seller_sold_item":"Información de Ventas y Pagos"},"tracking_attributes":{"ai_studio_notifications":"Notificaciones de CGTrader AI","communication":"Comunicación","news":"Noticias 3D / Actualizaciones de CGTrader","onboarding_and_edu":"Incorporación y Educación","sales_and_offers":"Ventas y ofertas especiales","seller_sold_item":"Información de Ventas y Pagos"},"update":{"updated":"Tu correo electrónico ha sido actualizado exitosamente."}},"gdpr_success_notification":{"opt_in":"¡Gracias! Te has vuelto a suscribir exitosamente a nuestros correos electrónicos.","opt_out":"Te has dado de baja exitosamente de nuestros boletines. Puedes cambiar esta preferencia en cualquier momento visitando la configuración de tu perfil.\n"},"location_tooltip_text":"\u003cdiv\u003eEsta casilla permite mostrar la ubicación en la página de perfil.\u003c/div\u003e \u003cdiv\u003eTenga en cuenta que, en cualquier caso, por razones de cumplimiento, su ubicación se mostrará a sus compradores (si es vendedor) y a los vendedores de los que compró.\u003c/div\u003e","moneybookers":{"account":"Cuenta de Skrill"},"password":{"current_password":"Contraseña actual","new_password":"Nueva contraseña","submit":"Cambiar Contraseña","title":"Contraseña","updated":"Tu contraseña ha sido actualizada exitosamente. Ahora puedes usar tu nueva contraseña para iniciar sesión.","verify_password":"Verificar contraseña"},"paypal":{"account":"Cuenta de PayPal","submit":"Guardar","title":"Configuración de PayPal","update":{"updated":"Tu configuración de PayPal ha sido actualizada"}},"personal_info":{"areas_of_expertise":"Áreas de experiencia","country":"País","description":"Unas pocas frases sobre ti","full_name":"Nombre completo","languages":"Idiomas","preffered_tools":"Herramientas preferidas","submit":"Guardar","title":"Información personal y profesional","update":{"updated":"Tu información se actualizó exitosamente"},"website":"Sitio web"},"personalization":{"no_muted_title":"No hay diseñadores silenciados","title":"Diseñadores Silenciados","tooltip":"Los modelos 3D de los diseñadores listados están excluidos de tus resultados de búsqueda.","unmute_designer":{"confirm":"¿Estás seguro?"}},"professional_info":{"allow_private_job_requests":"Los compradores podrán enviarte solicitudes de trabajos 3D personalizados.","buyer_engagement_program_html":"Atrae más clientes permitiéndoles comprar tus modelos con CGTrader Points. \u003ca href='https://www.cgtrader.com/pages/cgtrader-loyalty-discounts-explained' target='_blank'\u003eMás información\u003c/a\u003e\n","pbr_program":"La tasa de ganancia más baja se aplica solo cuando se descarga el archivo convertido.","pbr_program_agreement":"Has aceptado participar en el programa PBR durante 24 meses desde %{date}. Consulta los Términos y Condiciones para más detalles. Por favor contacta a 
16518nuestro soporte si tienes alguna pregunta.","product_analytics_participation_html":"Participo en \u003ca href='https://www.cgtrader.com/profile/analytics'target='_blank'\u003eAnalytics\u003c/a\u003e y estoy de acuerdo con sus Términos y Condiciones\n","product_analytics_participation_tooltip_html":"Has aceptado participar en Analytics y estás de acuerdo con los Términos y Condiciones de Analytics (\u003ca href='https://www.cgtrader.com/pages/cgtrader-analytics-terms-of-service'target='_blank'\u003eenlace\u003c/a\u003e) desde %{accepted_at}. Si deseas terminar tu participación, por favor contacta al soporte de CGTrader\n"},"security":{"allow_mfa":"Habilitar verificación en 2 pasos para proteger tu cuenta","use_authenticator_app":"Usar una aplicación de autenticación para la verificación en 2 pasos","use_authenticator_app_tooltip":"Escanea el código QR con tu aplicación de autenticación (Google Authenticator / Authy) para generar códigos de verificación.","use_email":"Usar correo electrónico para la verificación en 2 pasos"},"sidebar":{"general_settigns":"Configuración General","member_since":"Miembro Desde","paypal":"PayPal","title":"Información de la Cuenta"},"title":"Mi Cuenta"},"conversations":{"conversations":"conversaciones","messages":{"loading":"Cargando la bandeja de entrada"},"types":{"cgtrader_support":"Soporte CGTrader","file_conversion_request":"solicitud de conversión de archivos","job_application":"Proyecto 3D","model_feedback":"Comentarios sobre el producto","model_support":"modelo de ayuda","offer":"Precio de oferta","personal":"Mensaje personal","promotions":"promociones"}},"meta":{"description":"Explora modelos 3D de alta calidad de %{username} en CGTrader. Navega activos únicos para juegos, VR/AR y visualización.","title":"Diseñador de modelos 3D: %{username}"},"my_products":{"suspension_appeal":{"reasons":{"not_available":"N/A"}}},"settings_updated":"Tu configuración se ha actualizado correctamente."},"publishing":{"banned_error":"Su cuenta ha sido suspendida por publicar contenido spam. Si cree que se trata de un error, póngase en contacto con [email protected].","files_not_finalized":"Uno o más archivos no terminaron de subirse y no se pueden publicar. Elimínelos y vuelva a subirlos antes de publicar.","spam_suspect":"Su cuenta no está autorizada para publicar este modelo. Si cree que se trata de un error, póngase en contacto con [email protected]."},"rating":{"anonymous_buyer":"Comprador Anónimo","errors":{"not_verified_buyer":"Solo los compradores verificados pueden dejar una reseña"}},"routes":{"3d-models":"3d-modelos","3d-print-models":"3d-de-impresion-modelos","animated-3d-models":"animados-3d-modelos","controllers":{"three_d_studio":{"3d-models":"3d-models","images":"images"}},"discover":"descubrir","free-3d-models":"gratis-3d-modelos","free-3d-print-models":"gratis-3d-de-impresión-modelos","low-poly-3d-models":"de-bajo-poli-3d-modelos","pbr-3d-models":"pbr-3d-modelos","pbr-textures":"pbr-textures","rigged-3d-models":"aparejado-3d-modelos","search":"search"},"sale_off":{"no_overlapping_campaigns":"Las fechas de la campaña se superponen con otra campaña de oferta existente.","parse_discount_rules_text":"La línea #{index} no contiene el parámetro requerido 'discount:'"},"search":{"not_found":"Suscripción de búsqueda no encontrada","subscription_created":"Suscripción de búsqueda creada","successfully_unsubscribed":"Te has dado de baja correctamente de la suscripción de búsqueda"},"subscription_opt_ins":{"lock_period_error":"Some items cannot be opted out before the six-month lock period ends.","missing_items_error":"Items not found: %{items}."},"subscriptions":{"flash":{"active_subscription":"Ya tienes una suscripción activa.","premium_upgrade_unavailable":"La mejora a Premium no está disponible temporalmente. Inténtalo de nuevo más tarde.","update_payment_method":"Tu suscripción tiene un problema de pago. Actualiza tu método de pago en lugar de comprar una nueva suscripción."},"payment_form":{"current_price":"%{price}","features":{"first":"%{downloads} recursos / mes — descarga modelos o genera con IA","second":"Acce
16518so instantáneo a más de %{models_count} modelos 3D esenciales","third":"Libre de regalías, sin IA"},"from_price_label":"desde","original_price":24.99,"per_model_label":"por modelo","plan_name":"Suscripción CGTrader Essentials — plan anual, facturado mensualmente","premium":{"features":{"first":"%{downloads} recursos / mes — descarga modelos o genera con IA","fourth":"Libre de regalías, sin IA","second":"Acceso instantáneo a más de %{models_count} modelos accesibles en total","third":"Modelos de nivel profesional no incluidos en Essentials"},"plan_name":"Suscripción CGTrader Premium — plan anual, facturado mensualmente"},"price_unit":"/mes","vat_amount":"%{vat_amount}"}},"tag":{"description":{"bottle":"Los científicos dicen que la botella de vino al día no es malo para usted, pero cuando se trata de bebidas más fuertes como whisky o bourbon, que realmente deben vigilar la ingesta de alcohol. Obviamente, una copa de champán en el refrigerador de la manera bar parece que una botella de plástico de agua, pero pensar en la posibilidad de vivir una vida más larga y la cerveza se parece como un sacrificio razonable. Esta categoría ofrece una amplia selección de modelos en 3D de botella, que van desde los objetos relacionados con el alcohol como bastidores de vino y sacacorchos a todo tipo de botella 3D (modelos de champú al aceite de oliva).","brain":"El cerebro es uno de los órganos más grandes y más complejos del cuerpo humano. Debido a su compleja estructura y función, la anatomía del cerebro también es muy complejo. Sin embargo, los científicos profundizar en la arquitectura del cerebro humano, el descubrimiento de secretos alucinantes. Una amplia gama de modelos en 3D del cerebro realistas y detallados están listos para aplicaciones gráficas de ordenador, como la publicidad, la visualización en 3D, juegos en 3D, web y cualquier otro campo relacionado con el diseño 3D está disponible para su descarga.","building":"No está claro que los arquitectos modernos se inspiran, pero algunos de los edificios famosos del mundo están tan de vanguardia que se parezcan a las visiones del futuro. Y no nos limitamos a decir la parte exterior ... En buenas manos una construcción de edificios estándar se convierte en una creación arquitectónica impresionante, con un diseño interior innovador, independientemente de si se trata de una oficina o un edificio urbano. Nuestra selección de la construcción de modelos 3D está listo para su descarga y se puede aplicar en diversos campos - animación 3D, diseño gráfico, publicidad, juegos en 3D o en la web.","camera":"La fotografía y la filmación es en la actualidad uno de los más campos de la carrera popular entre los jóvenes, pero no todo el mund
16518o con una cámara profesional es un artista o un fotógrafo. Con la calidad de las cámaras de teléfonos mejorando año tras año, vemos a la gente de tomar imágenes en cada paso y los que tienen la imagen recién desarrollada edición de habilidades verdaderamente célebre. La vida tiene un aspecto un poco diferente cuando estás en el otro lado de la lente, pero no vamos a dejarse llevar ... Una amplia gama de modelos de cámara 3D detallado está disponible para su descarga a ser utilizado por los diseñadores gráficos, animaciones y los desarrolladores de juegos y los profesionales que trabajan en otros campos relacionados con el 3D.","car":"En cuanto a estos modelos de coches muy detallado y altamente realistas en 3D, casi se puede oír el rugido del motor! Una amplia gama de modelos de vehículos en 3D puede hacer su vida mucho más fácil, si usted está luchando con los gráficos por ordenador, animación o cualquier otro campo 3D-relacionado.","cell":"Los científicos han estimado que el cuerpo humano contiene de 5 mil millones a 200 millones de billones de células. Las plantas y los animales tienen células, también. Nosotros no queremos aburrir con esta materia de biología, pero la amplia gama de teléfonos 3D modelos que tenemos abarca todo, desde la sangre, el virus y el ADN de la médula ósea y las paredes celulares. No es que ... estresante?","chair":"Tomamos sentados en sillas por sentado y no siempre se dan cuenta de cuánto se puede añadir encanto a nuestro interior hogar u oficina. Silla es en realidad una de las piezas centrales de muebles en cualquier hogar. Aquí puede encontrar modelos 3D silla de diseño llamativos que se pueden aplicar en diversos campos de alta calidad y - de la publicidad y la visualización en 3D para juegos en 3D.","city":"La vida urbana es un juego de excéntrica: calles llena de taxis de carreras, edificios modernos, los peatones en la ropa de oficina perfectamente adaptados para dormir, puentes y parques ... de Shanghai, a Nueva York (! Manhattan), a Londres o París - cada ciudad tiene su propia magia. Usted puede elegir de esta amplia selección de modelos 3D detallada de la ciudad que se puede utilizar en un número de campos - Publicidad, gráficos por ordenador, animación, visualización en 3D o juegos. Explorar la vida urbana.","dragon":"Dragón es probablemente la criatura más populares que viven en el mundo de fantasía. Ha aparecido en tantos mitos de culturas diferentes, animaciones, películas y libros que se ha convert
16518ido en una leyenda. Está especialmente amado por los desarrolladores de juegos, ya que añade que el encanto mágico a oscuras cuevas y mazmorras. Una amplia gama de realista, aparejado modelos 3D dragón están listos para aplicaciones gráficas de ordenador, como la publicidad, la visualización en 3D, juegos en 3D, web y cualquier otro campo relacionado con el diseño 3D está disponible para su descarga.","earth":"El nivel de detalle en la vista de la tierra recientemente añadido en los mapas de Google ha conmocionado a un número de personas. Usted puede explorar todo el planeta - océanos, montañas y desiertos, descubrir ciudades y caminos ocultos sin salir de su casa. Eso es algunas cosas increíbles Madre Tierra no ha experimentado antes. Una selección de modelos 3D realistas tierra están listos para aplicaciones gráficas de ordenador, como la publicidad, la visualización en 3D, 3D juegos, web y cualquier otro campo relacionado con el diseño 3D está disponible para su descarga.","engine":"Algunas personas son capaces de nombrar el motor simplemente escuchando el sonido que hace, y que es realmente sorprendente. Independientemente de si se trata de un motor a reacción o un motor de automóvil, son extremadamente complicadas máquinas que utilizamos tan a menudo, pero no pasamos demasiado tiempo pensando en ello. Un motor es un motor (hasta que deja de funcionar). Esta categoría tiene una gama de diferentes tipos de modelos de motor 3D, y también incluye partes de motor como de la hélice o cilindro, por lo que estamos seguros de que encontrará lo que necesita.","female":"No es ninguna broma que existen diferencias significativas entre el cerebro masculino y femenino, lo que significa que es normal que un hombre y una mujer se comportan promedio, sentir y responder de una manera completamente diferente. Pero no es todo acerca de la anatomía. No hay historia de amor, película o juego nunca lo logró sin un fuerte carácter de la muchacha, porque el poder de encanto dama es innegable. Una amplia selección de 3D realistas modelos de mujer que puede ofrecer todo lo que necesita - a partir de modelos 3D desnudas las vías respiratorias femenina.","hand":"La razón por la que usamos nuestras manos para hacer muchas cosas es su extraordinaria anatomía. El pulgar solo es controlado por nueve músculos separados. Es difícil de creer, pero las primeras manos eran más exótico que cualquier mano hoy en día - algunas especies tenían siete dedos. El cuerpo humano ha evolucionado significativamente. Un brazo o una mano como lo vemos hoy en día parece en nada a sus primeras versiones. Una amplia gama de realista, aparejado modelos de mano 3D están listos para aplicaciones gráficas de ordenador, como la publicidad, la visualización en 3D, 3D juegos, web y cualquier otro campo relacionado con el diseño 3D está disponible para su descarga.","head":"En anatomía humana, la cabeza es la parte superior del cuerpo humano. Modelado de una cabeza humana en 3D puede resultar un poco complicado, debido a que (a pesar de que hay muchas formas y tamaños diferentes) apreciamos cabezas simétricas, de aspecto realista - no hay divertidos ojos, los oídos, la nariz, los labios o la línea de la mandíbula, por favor. La cara es una zona sensible y que sólo sería grosero para divertirse con eso. Nuestra selección abarca masculino realista, mujer, niño y personajes de la historia incluso como Harry Potter y Batman o celebridades como Tom Cruise cabezas y Scarlett Johansson están listos para su descarga.","helmet":"Recuerde siempre que la salud y la seguridad es lo primero, por lo que protege su cabeza es una buena idea. caballeros medievales, Daft Punk y hasta los cascos de desgaste Ironman, superhéroes tan regulares como ciclistas o constructores no deben preocuparse de buscar fuera de moda. Esta colección de cascos 3D modelos que está disponible para su descarga será útil si está trabajando en los campos del diseño gráfico, animación 3D, 3D juegos o visualización.","house":"Es una vieja verdad de que la casa de un hombre es su castillo, pero cuando se trata de construir la casa perfecta o la compra de un apartamento ideal, que tienden a perder la cabeza sobre ella. Por suerte, esta amplia gama de modelos de la casa 3D que abarca todo, desde los detalles arquitectónicos, interiores y exteriores, a los muebles, le ayudará a crear modernas y diseño 3D para aliviar el dolor.","human":"Al iniciar la exploración de la anatomía humana, se da cuenta de que la gente es tan intrincada y máquinas complejas que quita el aliento. Cada combinación de parte del cuerpo, los músculos, articulaciones, órganos o de médula se conecta en un solo sistema, nada funciona de manera aislada! Estos modelos humanos 3D realistas súper le permitirá examinar las estructuras anatómicas de cada ángulo.","lamp":"Si se mira alrededor de su casa y contar los tipos de lámparas de su propiedad, usted se sorprenderá: la lectura, mesa, pared, techo, escritorio, trípode lámparas ... Es suficiente para hacer girar su 
16518cabeza! Por no hablar de los estilos que vienen en - de las lámparas modernas extravagantes a retro o futurista con estilo. En esta categoría tenemos todo tipo de lámpara 3D modelos, por lo que navegar y descargar para su uso en diseño gráfico, diseño 3D o cualquier otro campo 3D-relacionado.","light":"¿Se ha preguntado por qué una bombilla de luz es el símbolo de una idea brillante? Sin duda, una idea brillante puede iluminar a la arquitectura de la mente, pero eso no es todo. Una luz de calle, una lámpara de araña o cualquier otro tipo de lámpara no tendría ninguna oportunidad de tomar ese lugar. Si la próxima vez cuando se mira hacia el techo y la mirada en algún tipo de luz arquitectónica de la idea del genio no le golpeó, podría ser porque usted está mirando a un sistema de iluminación mal ... Nuestra selección de luz de alta calidad modelos 3D serán resultar útil si trabaja en los campos de la arquitectura, gráficos por ordenador, animación 3D o diseño.","machine":"Imagínese si todas las máquinas de repente dejó de funcionar: no hay café, sin lavar la ropa cómoda, no hay cajeros automáticos en una esquina de la calle, no hay tiempo gimnasio, ni demasiado caro máquinas expendedoras ... No deprimente sonido, ¿verdad? Estamos tan acostumbrados a estos electrodomésticos y aparatos que sería literalmente detener el giro mundo! Por suerte, nuestra selección de máquina detallada modelos 3D portadas de todo, desde ametralladoras a los generadores de electricidad que serán de utilidad para los diseñadores que trabajan con gráficos por ordenador, publicidad, animación 3D, juegos en 3D o la Web.","man":"Una gran colección de modelos 3D hombre se encuentra disponible para su descarga. hombre sentado, hombre sencillo, hombre volador o el hombre promedio, tenemos todo - excepto que el hombre perfecto que todo el mund
16518o está después. Es un personaje sospechoso y no nos gusta hablar de esto, pero podría ser un mito ... Pero bueno, los seres humanos nacen a ser impresionante, no es perfecto.","robot":"R2D2 es, probablemente, el robot más famoso en el mundo, pero ciertamente no es el único. De industrial a juguete para robots humanos, vemos una opción cada vez más amplia de estas máquinas mecánicas, que es a la vez maravilloso y un poco preocupante. No es demasiado difícil de entender a las personas que insisten en que las computadoras, la tecnología, los robots están teniendo un impacto muy significativo en los seres humanos. Una amplia gama de realista, aparejado modelos de robots en 3D están listos para aplicaciones gráficas de ordenador, como la publicidad, la visualización en 3D, 3D juegos, web y cualquier otro campo relacionado con el diseño 3D está disponible para su descarga.","ship":"Los barcos son embarcaciones de gran alcance y vienen en todos los tamaños, colores y formas. Pueden ser clasificados como buques de carga, buques de guerra o buques de pasaje, pero todos ellos operan en aguas de alta mar. Si usted ha experimentado la incomodidad de la enfermedad de movimiento, puede que no sea una de sus medios de transporte preferido, pero muchas personas consideran como excursiones en barco bastante elegante y agradable. Nuestra selección de modelos de barcos en 3D está listo para su descarga y cumplirá con los estándares de incluso el capitán más exigentes.","skeleton":"El esqueleto humano o sistema esquelético (anatomía) es el marco interno del cuerpo que se compone de 270 huesos en el nacimiento. Se proporciona la estructura de la carrocería, la protección y el movimiento y es uno de los sistemas más complejos esqueléticos. Una amplia selección de anatómicamente correcta, realista y escalado esqueleto modelos 3D que son perfectos para su uso en la animación 3D, diseño gráfico, publicidad, juegos en 3D y la web está disponible para su descarga.","skull":"El más antiguo cráneo humano más completa, genéticamente intacta fue descubierto rodeado de una gran variedad de animales extintos más de 40 metros bajo el nivel del mar. Es uno de los esqueletos más antiguos encontrados en el Nuevo Mundo y también incluye todos los principales huesos del cuerpo y un cráneo intacto y conjunto de dientes. cráneos humanos se han convertido recientemente en una cosa en la moda y el diseño, pero cuando realmente se da un poco de pensamiento, que tipo de hacer que todos se parecen a los monstruos de anatomía ... Esta categoría ofrece una amplia gama de modelos de cráneo 3D para ser utilizado en el diseño gráfico , animación 3D, juegos en 3D o la publicidad.","sofa":"De lujo o simple, moderno o antiguo, cuero o diseño minimalista? ¿Qué le parece su sofá? La gama de modelos en 3D sofá que tenemos para satisfacer a la peor perfeccionista. Cuando uno se detiene a pensar en ello, sofá es el centro de cada sala de estar, por lo que debe ser creativos al elegir el diseño!","table":"Las nuevas tendencias de muebles son tan diversas que la elección de una mesa moda moderna se está convirtiendo en demasiado estresante. Hay que solía ser un momento en que había una mesa simple para sus fiestas modestas y eso fue suficiente, pero hoy en día - la tabla se supone que es una declaración o, al menos, un acento interior impresionante. Esta selección le da una amplia gama de modelos de mesa en 3D para atender a todos los caprichos.","tower":"Cada ciudad tiene una torre especial que todo el mund
16518o está muy orgulloso. Piense Torre Eiffel o la torre de Pisa, o puente de la torre de Londres, por no hablar de todos los modernos rascacielos, edificios futuristas y el paisaje urbano que los turistas admiran. Sin embargo, cuando se trata de las piezas industriales de la ciudad, el agua o torres eléctricas, todos tienden a girar sus ojos lejos ... Pero debemos aprender a apreciar cada creación arquitectónica, no importa antiguo, industrial o moderna. Una gama de modelos de torre detallada en 3D están disponibles para su descarga y listo para aplicaciones gráficas de ordenador, como la publicidad, la visualización en 3D, 3D juegos, web y cualquier otro campo relacionado con el diseño 3D.","tree":"Si usted está en una misión para plantar algunos árboles o plantas exóticas, has venido al lugar correcto. Esta categoría ofrece una amplia gama de uso fácil de, modelos muy realistas de árboles en 3D a artistas y arquitectos CG, que se esfuerzan por añadir algo de verde a sus proyectos. Si usted aprecia buena calidad, estamos seguros de que esta selección de modelos de plantas de árboles en 3D se adapte a sus estándares!","truck":"La conducción de un camión es un trabajo duro, especialmente si usted necesita para encontrar su camino a través de un atasco de tráfico de la ciudad. Independientemente de si se trata de un camión de bomberos, camión de basura, camión de comida o un ser genérico, se necesita una gran habilidad para maniobrar estos vehículos gigantes. Estamos seguros de que esta selección de realistas, camiones modelos 3D de alta calidad son perfectos para su uso en la animación 3D, diseño gráfico, publicidad o cualquier otro campo relacionado 3D.","tv":"Solía ​​haber un momento en que la chimenea era la pieza central de cada hogar respetable, ahora aparato de TV con los jugadores y los altavoces es el rey de la sala de estar (e incluso el dormitorio). En los últimos años la televisión ha puesto al día con la última tecnología. Como resultado, tenemos pantallas de televisión en 3D inteligente, planas y de plasma, millones de nuevas funciones y diseño de pantalla táctil. No es de extrañar la televisión está empujando el cine fuera del mercado. Una amplia gama de modelos realistas, detallados de TV 3D están listos para aplicaciones gráficas de ordenador, como la publicidad, la visualización en 3D, juegos en 3D, web y cualquier otro campo relacionado con el diseño 3D está disponible para su descarga."}},"tag_promotion":{"days_left_error":"debe estar entre 1 y 30","no_other_pending_promotions_for_same_item_tag":"ya tiene una promoción pendiente"},"tooltips":{"buyer_loyalty_program_current_level_html":"Actualmente se han gastado %{total_price_paid} en CGTrader y su crédito ganar tasa es %{buyer_loyalty_rate}. \u003cbr/\u003e Esto significa que obtendrá %{buyer_loyalty_rate} de créditos de cada compra.\n","buyer_loyalty_program_html":"Este es el porcentaje de la cantidad que se obtiene de sus compras como nuevos créditos. \u003cbr/\u003e Se aumentará hasta un 30% en sus gastos de por vida en aumento CGTrader. \u003cbr/\u003e ganar Actualmente su crédito tasa es %{buyer_loyalty_rate}.\n","buyer_loyalty_program_next_level_html":"Siguiente nivel %{next_lvl_expenses} aumentará su crédito ganan velocidad de hasta % {next_lvl_expenses_rate}. \u003cbr/\u003e usted necesita comprar por $%{left_until_next_lvl}  más para llegar a un nivel superior.\n"},"trending_headers":{"models":"Consulta otros modelos 3D en tendencia","print":"Consulta otros modelos 3D para impresión en tendencia"},"tutorial":{"destroy":"El tutorial se ha eliminado correctamente.","submit_for_review_error":"Por favor, completa todos los campos obligatorios.","submitted":"Gracias por enviar tu tutorial. Será revisado y publicado en breve."},"valvat":{"country_adjectives":{"at":"austriaco","be":"belga","bg":"búlgaro","cy":"chipriota","cz":"checo","de":"alemán","dk":"danés","ee":"estonio","es":"español","eu":"europeo","fi":"finlandés","fr":"francés","gb":"británico","gr":"griego","hu":"húngaro","ie":"irlandés","it":"italiano","lt":"lituano","lu":"luxemburgués","lv":"letón","mt":"maltés","nl":"holandés","pl":"polaco","pt":"portugués","ro":"rumano","se":"sueco","si":"esloveno","sk":"eslovaco"}}},"fr":{"activerecord":{"attributes":{"content_report":{"categories":{"adult":"Adulte ou contenu violent","ai_generated":"Contenu généré par IA","copyright":"violation de copyright"}}},"errors":{"messages":{"embedded_script":"Ce fichier n'est pas pris en charge et ne peut pas être importé. Veuillez essayer un autre fichier."}}},"api":{"profile":{"actions":{"contact":"Contact","follow":"Suivre","follow_tooltip":"Commencez à suivre ce designer pour recevoir des mises à jour sur son contenu et filtrer facilement vos favoris dans les résultats de recherche.","hide":"Masquer","hire_me":"Engagez-moi","invite_client":"Lancer un projet","mute_tooltip":"Masquez les modèles de ce designer dans les résultats de recherche.","take_over":"Prendre le contrôle","unfollow":"Ne plus suivre","unhide":"Afficher","view_all_models":"Voir tous les modèles du designer"},"private_project_review":"Projet privé"}},"browser":{"3d_models":"modèles 3D","3d_print":"Impression 3D ","active_filters":{"polygons":"%{range} polygones"},"adult_content":"Afficher l'âge +","animated":"Animé","breadcrumb":{"browse_by_type":"Parcourir par type","collection":"packs 3D","free":"libre","free_models":"Modèles 3D gratuits","free_print_models":"Modèles d'impression 3D gratuits","low_poly":"low-poly","low_poly_models":"VR / AR / low-poly 3D Modèles","low_poly_print_models":"Modèles 3D d'impression Low-Poly","model_3d":"modèle 3D","model_3dp":"modèle d'impression 3D","models":"Modèles 3D","models_3d":"modèles 3D","models_3dp":"modèles d'impression 3D","models_variant_0":"Modèles 3D","models_variant_1":"Ressources 3D","models_variant_2":"Modèles VR","models_variant_3":"Modèles CG","models_variant_4":"Boutique de modèles 3D","models_variant_5":"Objets 3D","print_models":"Fichiers d'impression 3D","print_models_variant_0":"Modèles pour impression 3D","print_models_variant_1":"Modèles d'imprimante 3D","print_models_variant_10":"Modèles 3D pour impression","print_models_variant_11":"Modèles 3D à imprimer","print_models_variant_12":"Fichiers 3D pour impression","print_models_variant_13":"Modèles pour imprimante 3D","print_models_variant_14":"Modèles pour impression 3D","print_models_variant_15":"Modèles d'impression 3D","print_models_variant_16":"Fichiers 3D imprimables","print_models_variant_2":"Modèles 3D à imprimer","print_models_variant_3":"Modèles d'impression 3D","print_models_variant_4":"Fichiers d'impression 3D","print_models_variant_5":"Fichiers pour impression 3D","print_models_variant_6":"Fichiers pour imprimante 3D","print_models_variant_7":"Modèles 3D imprimables","print_models_variant_8":"Modèles 3D à imprimer","print_models_variant_9":"Modèles 3D imprimés"},"buyer_engagement_program":"Réductions","categories":"Catégories","category":{"meta":{"3d_models":"%{count} %{category} modèles 3D disponibles en téléchargement dans un format de fic
16518hier, y compris FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Télécharger %{count} %{category} Modèles d'impression 3D prêts à FDM, SLA, SLS et autres imprimantes 3D, STL formats disponibles, OBJ, 3DM et plus.%{page}","animated_3d_models":"%{count} %{category} d'animation des modèles 3D disponibles en téléchargement dans un format de fichier, y compris FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Télécharger %{count} %{category} 3D gratuits modèles, disponibles en MAX, OBJ, FBX, 3DS, formats de fichiers C4D, prêts pour VR / AR, animation, jeux et autres projets 3D.%{page}","free_3d_print_models":"Télécharger %{count} %{catégorie} Modèles d'impression 3D prêts à FDM, SLA, SLS et autres imprimantes 3D, STL formats disponibles, OBJ, 3DM et plus.%{page}","lowpoly_3d_models":"Télécharger %{count} VR / AR / low-poly %{category} modèles 3D prêts pour la réalité virtuelle, des jeux et d'autres applications à temps réel, disponible en FBX, OBJ, MAX, 3DS, les formats de fichiers C4D. %{page}","rigged_3d_models":"%{count} %{category} gréé modèles 3D disponibles en téléchargement dans un format de fichier, y compris FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"collection":"pack 3D","discounts":"Réductions","ext":{"meta":{"3d_models":"Téléchargez %{rounded_count} modèles 3D au format %{format_upper}. Fichiers %{format_lower} gratuits et premium.","3d_print_models":"Télécharger %{count} %{ext} fichiers - %{software_name} modèles d'impression 3D prêts à VR / AR, la conception 3D, l'animation, des jeux et des applications en temps réel%{page}."}},"filter":"Filtre","filter_button":"Appliquer les filtres","filters":{"adult_content_only":"Contenu adulte uniquement","cgt_standard_only":"Norme CGT","collections_only":"Packs uniquement","content_filters":"Filtres de contenu","designer":"Designer","exclude_3d_print":"Exclure impression 3D","exclude_adult_content":"Exclure contenu adulte","exclude_ai_generated":"Exclure contenu généré par IA","exclude_editorial_license":"Exclure licence éditoriale","exclude_free":"Exclure gratuits","file_format":"Formats de fichier","following":"Abonnements","free":"Gratuit","has_marmoset":"Avec visionneuse 3D","has_video":"Avec vidéo","include_ai_generated":"Inclure contenu généré par IA","label":"Filtres","poly_count":"Nombre de polygones","print_ready":"Impression 3D","stl_files":"Fichiers STL","subscription":"Abonnement","type":"Type"},"formats":"Formats","free":"Gratuit","free_models":"Afficher uniquement les modèles gratuits ","free_to_premium":{"main":"modèles 3D %{category}","postfix":"dans la liste premium.","prefix":"Avez-vous besoin de quelque chose de spécial ? Vous pouvez trouver une meilleure qualité"},"gallery":{"preview":"aperçu"},"game_ready":"Prêt pour le jeu","high_poly":"Haut-poly","items_page_title":"Modèles 3D","license":{"exclude_branded":"Exclure les marques","exclude_editorial":"Exclure l'éditorial","prompt":"Licence","tooltip_more":"Lire la suite","tooltip_text":" Ne pas montrer les modèles qui ne peuvent être utilisés qu'à des fins non commerciales, éducatives.","tooltip_title":"Exclure l'éditorial:"},"low-poly":"Low-poly","low_poly":"Low-poly","meta":{"3d_models":"Téléchargez ou achetez %{rounded_count} modèles 3D professionnels. Modèles gratuits et premium pour jeux, cinéma, VFX, AR/VR et impression 3D. FBX, OBJ, BLEND, GLB.","3d_print_models":"Téléchargez %{rounded_count} fichiers 3D pour impression 3D. Fichiers STL gratuits et premium pour imprimante 3D: figurines, miniatures, déco et plus.","animated_3d_models":"Téléchargez %{rounded_count} modèles 3D animés avec animations incluses. Personnages et objets game-ready en FBX, BLEND, GLB.","free_3d_models":"Téléchargez %{rounded_count} modèles 3D gratuits, libres de droits. Objets 3D et fichiers 3D gratuits en FBX, OBJ, BLEND et GLB.","free_3d_print_models":"%{rounded_count} fichiers STL gratuits pour imprimante 3D. Téléchargez des fichiers 3D et figurines à imprimer gratuitement.","lowpoly_3d_models":"Téléchargez %{rounded_count} modèles 3D low poly pour jeux, AR/VR et rendu temps réel. Gratuits et premium.","metaverse":"Entrez dans le métaverse avec notre collection de modèles 3D hauts et à faible puant gréés, animés et disponibles n'importe quel format de fic
16518hier.","omniverse":"Construisez votre monde virtuel avec nos modèles 3D prêts pour l'omniverse. Choisissez parmi les modèles animés, truqués et PBR avec nombre de polygones élevés et bas.","page_prefix":"Page %{page_number} avec","pbr_3d_models":"Téléchargez %{rounded_count} modèles 3D avec textures PBR pour Unity, Unreal et moteurs temps réel.","rigged_3d_models":"Téléchargez %{rounded_count} modèles 3D riggés avec squelette, prêts à animer dans Blender, Maya, 3ds Max, Unity et Unreal.","snap-ar":"Avec nos modèles 3D, vous pouvez rejoindre Million of Snapchatters en utilisant Snap AR pour créer des expériences de réalité augmentée amusantes.","spark-ar":"Illuminez votre Facebook et Instagram avec une variété de modèles Spark AR 3D qui sont truqués, animés et prêts à l'emploi.","stream_3d_print_models":"Parcourez une variété de flux catégories de modèles d'impression 3D pour découvrir quelques-unes des plus extraordinaires dessins imprimables en 3D des créateurs professionnels 3D.","textures":"%{count} Textures actifs et matériaux 3D disponibles en téléchargement dans différents formats, y compris .tga .sbsar .jpeg .png .tiff et autres. %{page}","tiktok":"Rendez vos effets vidéo plus impressionnants avec les modèles TIKTOK 3D. Notre bibliothèque peut répondre à tous vos besoins créatifs et est constamment mis à jour.","unity":"Trouvez des modèles 3D pour votre projet Unity dans n'importe quel format de fichier et tout nombre de poly. Tout ce dont vous avez besoin pour créer un environnement virtuel unique.","unreal":"Créez des visualisations impressionnantes avec des modèles 3D de moteur Unreal dans n'importe quel format de fichier. Notre bibliothèque a des modèles faibles et élevés de toutes les catégories."},"optimized":"Faible poly / PBR","page_title":{"animated_3d_models":"Modèles 3D Animés – Gratuits \u0026 Premium","format_page":"Modèles 3D %{format_upper} – Fichiers %{format_lower} Gratuits \u0026 Premium","free_3d_models":"Modèles 3D Gratuits – Télécharger","free_3d_print_models":"Fichiers STL Gratuits – Impression 3D","lowpoly_3d_models":"Modèles 3D Low Poly – Gratuits \u0026 Premium","pbr_3d_models":"Modèles 3D PBR – Gratuits \u0026 Premium","rigged_3d_models":"Modèles 3D Riggés (Rigged) – Gratuits \u0026 Premium","software_page":"Modèles 3D %{software} – Télécharger %{ext_file}","tag":{"3d_merged_premium":"%{tag_cap} 3D – Modèles 3D Gratuits \u0026 Premium","3d_merged_premium_print":"Fichiers STL %{tag_cap} – Impression 3D","3d_models":"%{tag_cap} 3D – Modèles 3D Gratuits \u0026 Premium","3d_print_models":"Fichiers STL %{tag_cap} – Impression 3D"}},"pbr":"PBR","per_page":"Par page","polygons_options":{"custom":"Gamme personnalisée","gt_250k":"250k +","lt_5k":"Jusqu'à 5 k","more_than_250000":"250K+","prompt":"Nombre de poly ","range_0_5000":"Jusqu'à 5K","range_100000_250000":"100 - 250K","range_10000_50000":"10 - 50K","range_100k_250k":"de 100k à 250k","range_10k_50k":"de 10k à 50k","range_50000_100000":"50 - 100K","range_5000_10000":"5 - 10K","range_50k_100k":"de 50k à 100k","range_5k_10k":"de 5k à 10k"},"price":"Prix: $%{from}-%{to}","price_range":"Prix","print_ready":" Prêt pour l'impression en 3D","printable":"Impression 3D","reset_button":"Réinitialiser les filtres","rigged":"Squeletté","rounded_count":{"delimiter":" ","exact":"%{n}","million":"%{n} M+","separator":",","thousand":"%{n}+"},"sale_off":"En promotion","search_placeholder":"Rechercher des mots-clés","show_all_formats":"Afficher tous les formats","simulation_ready":"Prêt pour la simulation","software":{"meta":{"3d_models":"Téléchargez %{rounded_count} modèles 3D pour %{software}. Fichiers %{ext_file} gratuits et premium, prêts à importer."}},"sort_by":"Trier par","sort_by_options":{"best_match":"Pertinence","highest_price":"Prix décroissant","lowest_price":"Prix croissant","newest":"Plus récent","oldest":"Plus ancien","sales":"Meilleures ventes","trending":"Tendances"},"subcategory":{"meta":{"3d_models":"%{count} %{category} modèles 3D disponibles en téléchargement dans un format de fic
16518hier, y compris FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Télécharger %{count} %{category} Modèles d'impression 3D prêts à FDM, SLA, SLS et autres imprimantes 3D, STL formats disponibles, OBJ, 3DM et plus.%{page}","animated_3d_models":"%{count} %{category} d'animation des modèles 3D disponibles en téléchargement dans un format de fichier, y compris FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Télécharger %{count} %{category} 3D gratuits modèles, disponibles en MAX, OBJ, FBX, 3DS, formats de fichiers C4D, prêts pour VR / AR, animation, jeux et autres projets 3D.%{page}","free_3d_print_models":"Télécharger %{count} %{catégorie} Modèles d'impression 3D prêts à FDM, SLA, SLS et autres imprimantes 3D, STL formats disponibles, OBJ, 3DM et plus.%{page}","lowpoly_3d_models":"Télécharger VR / AR / low poly %{tag} modèles 3D prêts pour la réalité virtuelle, jeux et autres applications à temps réel, disponible en FBX, OBJ, MAX, 3DS, les formats de fichiers C4D.%{page}","rigged_3d_models":"%{count} %{category} gréé modèles 3D disponibles en téléchargement dans un format de fichier, y compris FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"subtitle":{"aircraft":"Avions modèles 3D %{count} sont disponibles en téléchargement sur notre marché. CG %{airplanes_link} s'intégrera dans des simulations, des visualisations, des publicités et des vidéos. %{drone_link} corps et parties ravira les amateurs de véhicules petits volants. Et les %{rigged_link} modèles sont prêts à être importés dans les moteurs de jeu et être utilisé pour l'animation ou des projets VR / AR. Jeux volants, simulateurs et vidéos à la fois CG régulière et types à faible poly peuvent être remplis avec des modèles 3D dans la plupart des formats populaires, y compris FBX, OBJ, MAX, 3DS, C4D.","animals":"%{count} de CG animaux 3D - %{cats_link}, %{horses_link}, %{bears_link}, %{rhinos_link}, %{eagles_link}, %{lions_link}, fictif de %{dragons_link} et plus - sont disponibles ici pour le téléchargement. La collection de la faune 3D satisfera les besoins des publicités ou des scènes statiques. Nous avons aussi %{rigged_link} modèles pour les jeux, l'animation, la production vidéo et VR / projets AR. Utilisez les filtres pour trouver truquées, animé, low-poly ou libres modèles 3D disponibles dans la plupart des formats de fichiers reconnues par l'industrie, y compris FBX, OBJ, MAX, 3DS, C4D, et d'autres.","animated":"Téléchargez %{rounded_count} modèles 3D animés avec des mouvements prêts à l'emploi: personnages, animaux et objets pour jeux, cinéma et temps réel.","architectural":"%{count} de modèles architecturaux 3D disponibles en téléchargement contient des éléments et des détails de l'architecture, la construction et la conception technique. Pour acheter ou télécharger un modèle 3D libre, parcourir les catégories ci-dessus. Vous pouvez utiliser des modèles pour l'animation ou des jeux - certains d'entre eux sont truqués et low poly pour servir VR / AR besoins du projet. modèles 3D sont disponibles dans des formats de fichiers tels que FBX, OBJ, MAX, 3DS, C4D.","car":"%{count} voiture modèles 3D sont disponibles pour téléchargement. Les versions à faible poly dont seront attrayants pour les concepteurs de jeux ou d'applications VR / AR développeurs, d'autres - à des cinéastes, animateurs ou annonceurs. Des voitures antiques et classiques à ceux de course et concepts vus dans les films, il y a une grande variété de modèles d'automobiles 3D essentiellement tout format (FBX, OBJ, MAX, 3DS, C4D, etc.) que vous pourriez avoir besoin. Utilisez modèles 3D animés gratuits et de prototype et de raccourcir le temps de développement.","character":"%{count} Caractères modèles 3D sont disponibles en téléchargement, donc parcourant cette catégorie, vous trouverez des modèles de 3D %{human_link}, personnages, figures mythiques, de science-fiction %{robots_link}, des vêtements et d'autres choses liées à des personnages. De %{heart_link} et %{hand_link} à réaliste détaillé de %{male_link} ou modèles de %{female_link}. Un certain count de modèles sont truqués et pourrait être facilement ajouté à votre jeu, projet VR / AR, vidéo ou animation. Les deux poly haut et modèles bas poly peuvent être trouvés dans un certain count de formats, y compris FBX, OBJ, MAX, 3DS, C4D.","custom"
16518:"Ici, vous pouvez acheter ou télécharger gratuitement %{tag} modèles 3D. %{tag_heading_subtitle}","electronics":"%{count} électronique des modèles 3D disponibles pour le téléchargement. Cette catégorie pour les architectes, décorateurs d'intérieur, les concepteurs de niveau, les responsables de la scène et d'autres créateurs. modèles 3D de %{smartphones_link}, %{laptops_link}, %{cameras_link}, %{tvs_link} et plus vous aideront à saisir avec précision l'obsession de la vie moderne avec des appareils et de la technologie. Il existe des modèles 3D disponibles dans des formats de fichiers, y compris FBX, OBJ, MAX, 3DS, C4D. Télécharger gratuitement ou acheter des modèles haut de gamme et de l'utilisation pour l'animation, des jeux et des projets VR / AR.","exterior":"%{count} de l'extérieur architectural des modèles 3D disponibles pour la gamme de téléchargement de façades, bâtiments urbains, objets de paysage, maisons d'habitation à des constructions industrielles, des bâtiments d'infrastructure et pleine échelle modèles de %{city_link}. Pour acheter ou télécharger un modèle 3D libre, parcourir les catégories ci-dessus. Utilisez les filtres pour trouver truqué, animé, low-poly, modèles 3D gratuits ou trier selon le format nécessaire, y compris FBX, OBJ, MAX, 3DS, C4D. Certains actifs sont faibles poly et pourrait vous aider à compléter l'environnement dans votre jeu ou une application VR / AR.","food":"%{count} aliments modèles 3D sont disponibles pour download..Food sert bien pour créer des scènes réalistes de la vie domestique. fruits 3D, les légumes, les boissons et les repas sont ici à des annonces complètes pour les restaurants, les applications AR pour manger plus sain et visualisations architectes et designers d'intérieur travaillent. Que vous soyez à la recherche de 3D et %{pizza_link} modèles de %{ketchup_link} ou un modèle 3D d'un de mariage de %{cake_link}, ils sont là. Un certain count de versions de poly-bas sont disponibles pour ceux dans le jeu, le développement VR / AR. Les modèles 3D peuvent être trouvés dans FBX, OBJ, MAX, 3DS, formats C4D.","formats":"Téléchargez %{rounded_count} modèles 3D au format %{format_upper}, gratuits et premium. Chaque fichier de cette page est disponible en %{format_upper} natif, sans conversion.","free_general":"Téléchargez %{rounded_count} modèles 3D gratuits pour vos projets personnels et commerciaux. Objets 3D et fichiers 3D gratuits, libres de droits, en FBX, OBJ, BLEND et GLB.","free_no_tag":"%{model_count} gratuit 3D modèles %{tag} prêt pour l'impression 3D. Télécharger %{tag} gratuits modèles d'impression 3D, les formats de fichiers disponibles, y compris STL, OBJ, 3DM, 3DS, MAX. modèles 3D sont prêts à être utilisés avec FDM, SLA, SLS et d'autres types d'imprimantes 3D.","free_printable":"Téléchargez %{rounded_count} fichiers STL gratuits pour imprimante 3D: figurines, miniatures, objets utiles et déco à imprimer en 3D gratuitement, testés par la communauté.","free_tag":"Modèles 3D %{tag} disponibles pour téléchargement. Disponible dans de nombreux formats de fichiers, y compris MAX, OBJ, FBX, 3DS, STL, C4D, MELANGE, MA, MB. Trouvez professionnels de %{tag_link} pour tout projet de conception 3D comme la réalité virtuelle (VR), la réalité augmentée (AR), les jeux, la visualisation ou de l'animation 3D.","furniture":"%{count} meubles modèles 3D disponibles en téléchargement comprennent des tables, chaises, canapés, lits et autres meubles dans une variété de textures (bois, métal, etc.) et de styles (%{retro_link}, %{modern_link}, etc. ). Cette catégorie est destinée à toute personne impliquée avec l'architecture et le design intérieur, ainsi que les annonceurs et les animateurs. actifs en temps réel peuvent aider les développeurs de jeux et d'applications VR / AR à des projets complets. Utilisez les filtres pour trouver truqué, animé, faible ou poly Modèles 3D. Disponible dans un format de fichier, y compris FBX, OBJ, MAX, 3DS, C4D.","general":"Téléchargez ou achetez %{rounded_count}
16518 modèles 3D professionnels: objets 3D et assets pour jeux, cinéma, VFX, AR/VR et impression 3D. Gratuits et premium, en FBX, OBJ, BLEND, GLB et plus de 100 formats.","high_poly":"Acheter ou télécharger des modèles 3D d'animation professionnels prêts à être utilisés dans des projets CG, la production de films et de vidéos, animation, visualisations, et d'autres. Les actifs sont disponibles en téléchargement dans de nombreux formats acceptés par l'industrie, y compris MAX, OBJ, FBX, 3DS, UE4 et autres. Si vous êtes à la recherche de haute qualité poly actifs 3D, nous avons une importante bibliothèque d'art numérique pour tous vos besoins.\n","household":"%{count} ménages modèles 3D disponibles en téléchargement peuvent être utilisés pour donner des détails aux chambres. Vous voudrez peut-être mettre un ensemble de dîner sur la table, ont des récipients de poivre et de sel à l'intérieur de la cuisine complète, ou mettre un peu %{lights_link} et %{flowers_link} près du lit pour créer la sensation de luxe. Si vous êtes un architecte d'intérieur, animateur, l'application VR / AR ou créateur jeu, cette catégorie est pour vous. Utilisez les filtres pour trouver truqué, animé, faible ou poly Modèles 3D. Disponible dans un format de fichier, y compris FBX, OBJ, MAX, 3DS, C4D.","industrial":"%{count} industriels modèles 3D disponibles pour téléchargement - matériaux de construction, %{engineering_link} outils, machines et %{containers_link} - fournira les détails et interactifs, des éléments truqués pour créer des environnements animés. Cette catégorie est parfaite pour les simulations / VR / AR ou les développeurs de %{construction_link} niveau du site dans le jeu. Les modèles 3D serviront les professionnels du bâtiment qui travaillent avec des visualisations précis des logiciels variés que les modèles dans des formats tels que FBX, OBJ, MAX, 3DS, C4D sont disponibles.","interior":"%{count} intérieur des modèles 3D sont disponibles pour le téléchargement et comprennent des éléments et des détails utilisés dans l'architecture, la construction et l'ingénierie, design d'intérieur. Pour acheter ou télécharger prime un modèle 3D gratuit, parcourir les catégories énumérées ci-dessus. Vous pouvez utiliser des modèles pour l'animation ou des jeux - certains d'entre eux sont truquées, animé et low poly. Disponible dans une variété de formats de fichiers, y compris FBX, OBJ, MAX, 3DS, C4D.","low_poly":"Téléchargez %{rounded_count} modèles 3D low poly: des assets légers et game-ready pour les jeux, l'AR/VR et les projets mobiles.","military":"%{count} 3D des modèles militaires d'armes, armes,%{swords_link} et de l'artillerie à la fois moderne et historique utilisés par les forces militaires et la légitime défense sont disponibles pour téléchargement. Vous pouvez utiliser des modèles pour les jeux ou le contenu VR / AR - certains d'entre eux sont des polygones optimisés et prêts à être ajouté à un moteur de jeu. modèles gréés et animés peuvent faciliter davantage votre travail. Vous trouverez sûrement des actifs pertinents car il existe de countux modèles 3D disponibles dans un format de fichier que vous pourriez avoir besoin (FBX, OBJ, MAX, 3DS, C4D, etc.).","not_custom":"Téléchargez %{rounded_count} modèles 3D de %{tag_low}: gratuits et premium, réalistes et stylisés, en FBX, OBJ, BLEND et GLB.","optimized":"Les modèles qui ont moins de polygones sont mieux utilisés dans des applications en temps réel qui nécessitent un traitement rapide, comme la réalité virtuelle (VR), la réalité augmentée (AR), la réalité mixte (MR), la réalité croisée (XR) et des jeux, en particulier les jeux mobiles. Faites votre choix parmi notre collection de modèles truqués et animés de les utiliser facilement dans vos applications en temps réel. Obtenez 3D actifs pour les environnements, choisissez des accessoires, des objets ou acheter des collections complètes modèles 3D, des paquets et des paquets avec tout ce que votre jeu pourrait avoir besoin. Gagnez du temps de développement et les coûts, assurez-vous des expérience
16518s de prototypes ou de l'utilisation de modèles 3D comme des espaces réservés dans votre projet. Physiquement basé rendu, ou PBR, désigne les modèles qui sont rendus avec précision et de manière cohérente dans toutes les conditions d'éclairage en raison de la texturation les flux de travail utilisés dans le processus de création.\n","pbr":"Téléchargez %{rounded_count} modèles 3D avec textures PBR (rendu physique réaliste): des matériaux cohérents et réalistes pour Unity, Unreal et les moteurs temps réel.","plant":"%{count} plantes modèles 3D disponibles pour téléchargement contiennent des organismes de la flore qui poussent comme des plantes domestiques et dans la nature, comme les fleurs, %{trees_link}, herbe, barboteuses, verdure, herbes, plantes vivaces, plants, pousses, des arbustes, des glissades, les choux, les vignes, les mauvaises herbes. Pour acheter ou télécharger prime un modèle 3D libre, parcourir les catégories ci-dessus pour trouver le meilleur ajustement entre les modèles disponibles. Utilisez les filtres pour trouver truquées, animé, low-poly (pour une utilisation dans les jeux et VR / applications AR) modèles, ou pour filtrer les formats requis (modèles FBX, OBJ, MAX, 3DS, C4D et plus sont disponibles).","printable":"Téléchargez %{rounded_count} fichiers 3D pour votre imprimante 3D: fichiers STL gratuits et premium, testés pour l'impression FDM et résine. Figurines, miniatures, déco, outils et pièces détachées.","printable_merged_tag":"Téléchargez %{rounded_count} fichiers STL de %{tag_low} pour imprimante 3D: modèles gratuits et premium pour impression FDM et résine.","printable_tag":"Téléchargez %{rounded_count} fichiers STL de %{tag_low} pour imprimante 3D: modèles gratuits et premium pour impression FDM et résine.","rigged":"Téléchargez %{rounded_count} modèles 3D rigged (riggés): personnages, animaux et créatures avec squelette, prêts à animer dans Blender, Maya, 3ds Max, Unity et Unreal.","scanned":"%{count} scannée modèles 3D sont disponibles à l'achat ou téléchargement gratuit. Les modèles 3D sont scannées prêts pour l'animation, des jeux et des projets VR / AR. Vous pouvez trouver des modèles 3D d'objets trouvés dans la nature tels que des roches et des surfaces naturelles, les humains et les autres objets ... la vraie vie ici et il ne peut pas obtenir plus réaliste que ça. Utilisez les filtres pour trouver truqué, animé, faible ou poly Modèles 3D. Disponible dans un format de fichier, y compris FBX, OBJ, MAX, 3DS, C4D.","science":"%{count} la science modèles 3D disponibles en téléchargement dans cette gamme de catégorie de la médecine, la biologie, la physique, les champs de la chimie et d'autres objets du monde réel liés à la science et de la technologie: matériel de laboratoire, des organismes microscopiques et dans le monde subatomique. Ne cherchez pas plus loin,%{cell_link}, et %{skull_link}, %{skeleton_link} modèles 3D peuvent être trouvés ici! Pour acheter ou télécharger un 3D gratuitement actifs, parcourir les catégories ci-dessus. Vous pouvez utiliser des modèles pour les truquées d'animation ou poly faible pour les jeux et les projets VR / AR. Les deux modèles gratuits et premium 3D peuvent être trouvésdans un certain count de formats de fichiers, y compris FBX, OBJ, MAX, 3DS, C4D.","scripts_plugins":"%{count} Scripts 3D / plugins sont disponibles pour téléchargement. Scripts 3D / plugins peuvent être ajoutés à différents logiciels et utilisés pour rendre votre pipeline de travail plus efficacement. Filtrer par formats pour trouver des outils adaptés à vos logiciels préféré.\n","software_page":"Téléchargez %{rounded_count} modèles 3D pour %{software}, gratuits et premium. Les fichiers %{ext_file} natifs s'ouvrent directement ; les autres formats s'importent en quelques clics.","space":"%{count} espace modèles 3D sont disponibles en téléchargement sur CGTrader. L'espace est une catégorie à part entière de la low poly, modèles de jeu prêts de science-fiction %{spaceships_link}. Il y a aussi %{asteroids_link}, les planètes (y compris la Lune et Mars, pour simuler la colonisation) et de l'espace réaliste de %{shuttles_link} pour plus scientifiquement incliné. Les modèles peuvent certainement être utilisés dans les jeux de simulateur spatial VR, les applications AR et des animations pour illustrer le monde extérieur planète de %{earth_link}. Les filtres sont ici pour vous aider à trouver truquées, animés, actifs ou poly-bas modèles dans des formats spécifiques, y compris FBX, OBJ, MAX, 3DS, C4D.","sports":"%{count}
16518 sports modèles 3D d'équipement ensemble nid disponible qui est utilisé pour les activités de loisirs, de divertissement, exercice, loisir, passe-temps agréable, amusant ainsi que des jeux, jouets pour enfants et une gamme complète d'instruments de musique. Pour acheter ou télécharger un modèle 3D gratuitement dans un format de fichier nécessaire, y compris FBX, OBJ, MAX, 3DS, C4D, parcourir les catégories ci-dessus. Vous pouvez utiliser des modèles pour l'animation, des jeux, des projets VR / AR - beaucoup d'entre eux sont truqués, low-poly ou déjà animé.","test":{"airplane":"Les amateurs de l'aviation, écoutez, il n'a jamais été aussi facile d'obtenir le modèle d'avion imprimé 3D parfait pour votre jeu de société ou d'une collection de modèle. Faites une recherche parmi %{model_count} modèles d'avion imprimer 3D et trouver un modèle que vous juste besoin d'avoir sur cette étagère. Découvrez réel des modèles d'avion ou ceux confectionnés. Recherchez des projets les plus ou quelque chose qui compte retour de l'époque des frères Wright. Il est aussi un cadeau parfait pour tout enfant qui rêve d'être pilote quand il grandit. Acheter et télécharger un modèle d'impression 3D, imprimez-le par vous-même ou soumettre le modèle à l'un de nos fournisseurs de services d'impression 3D pour qu'il soit imprimé et expédié en peu de temps!\n","anatomy":"%{model_count} modèles 3D disponibles Anatomie en téléchargement sur notre marché. Explorez l'anatomie humaine avec haut - os poly, les muscles, les modèles d'organes. Parfait pour tout projet éducatif ou scientifique. Utiliser des modèles 3D imprimables pour une expérience de meilleure éducation. Créer de superbes animations et des jeux vidéo avec notre\n vaste bibliothèque de modèles truqués. Utilisez notre système de filtrage avancé pour trouver facilement low poly, animé, PBR,\n modèles truquées ou tout ce qui convient le mieux à vos besoins. Faites votre choix parmi un grand nombre de formats de fichiers, y compris la\n la plupart des plus populaires comme FBX, OBJ, MAX, 3DS, C4D.\n","castle":"Votre propre château imprimé 3D étonnant est un clic! Rechercher divers modèles 3D château prêt pour l'impression 3D. Créer une forteresse fantastique unique pour votre session D \u0026 D ou tout autre jeu de plateau de jeu de rôle médiéval. Ces forteresses médiévales sont un cadeau incroyable pour tous les amateurs de jeu de société ou un excellent ajout à votre collection. Trouver le modèle 3D parfait pour la peinture des amateurs ou un d'un jouet genre pour les enfants. Achetez et téléchargez votre choix de conception du château 3D, imprimer avec votre propre imprimante, ou soumettre le modèle à l'un de nos fournisseurs de services d'impression 3D pour qu'il soit imprimé et livré à vous.\n","dice":"La chance ne va pas manquer avec des dés imprimé 3D! Si vous voulez dés de jeu authentique pour votre jeu de plateau nuit, ne cherchez pas plus. Ici, vous pouvez choisir entre %{model_count} gratuit dés 3D modèles prêts pour l'impression 3D. Lorsque le jeu est terminé, utiliser ces beaux modèles comme accessoire. Télécharger des modèles 3D gratuits dés imprimables, formats de fichiers disponibles, y compris STL, OBJ, 3DM, 3DS, MAX. Nos modèles 3D sont prêts à être utilisés avec FDM, SLA, SLS et d'autres types d'imprimantes 3D. Faites votre jeu un peu plus intéressant et obtenir vos dés imprimés 3D!\n","drone":"Imprimer 3D et construire votre propre drone ou trouver des pièces de remplacement. drones imprimé 3D sont faciles à assembler et à réparer après un atterrissage d'accident. Découvrez un modèle avec la position de montage de la caméra parfaite ou demande de modification minimale. Parfait pour les activités de loisirs amusantes, photographie aérienne, la cartographie ou la vidéographie. Trouver et acheter un modèle de drone 3D de conception unique qui correspond à tous vos besoins. Imprimez-le par vous-même ou soumettre le modèle à l'un de nos fournisseurs de services d'impression 3D pour qu'il soit imprimé et expédié en peu de temps! Obtenez votre propre drone imprimé 3D et parcourir le ciel à large bleu!\n","pokemon":"Pikachu, je vous choisissez! Faites votre choix parmi des tas de monstres de poche prêt pour l'impression 3D. Grand pour élargir votre collection pokemon imprimé 3D. Rechercher et tomber par hasard sur les meilleurs, les dessins pokemon plus détaillées pour votre prochain jeu de table avec des amis. Achetez et téléchargez votre pokemon 3D, imprimer avec votre propre imprimante 3D, ou soumettre le modèle à l'un de nos fournisseurs de services d'impression 3D pour qu'il soit imprimé et livré à vous. Après tout, c'est l'une des choses les plus cool à imprimer 3D!\n","ring":"Bijoux imprimé 3D est le nouveau noir dans l'industrie, comme les anneaux imprimés 3D! Rechercher une énorme quantité de %{model_count} Modèles anneau imprimable 3D. Parcourez des conceptions uniques et trouver quelque chose faite pour vous. Vous ne trouvez pas quoi que ce soit pour vous-même au magasin? Ou peut-être que vous voulez un accessoire exactement comme celui de votre personnage de film préféré est? Faites votre choix parmi un grand nombre de formats de fichiers, y compris STL, OBJ, 3DM, 3DS, MAX. modèles 3D sont prêts à être utilisés avec FDM, SLA, SLS et d'autres types d'imprimantes 3D, donc naviguer à travers notre sélection et essayer sur votre nouvel anneau favori.\n","shoes":"Les grandes marques sont à bord avec des chaussures imprimé 3D et maintenant vous pouvez partager l'expérience aussi bien. Parcourir et trouver gratuitement des designs de chaussures imprimables - de baskets à talons à la mode, il y a beaucoup de modèles prêts à imprimer pour vous attendre de les essayer. Faire le parfait résultat en choisissant des matériaux, en ajustant la douceur et la taille. Faites votre choix parmi 146 modèles gratuits de chaussures 3D et de les télécharger dans de
16518s formats de fichiers disponibles, y compris STL, OBJ, 3DM, 3DS, MAX. Les dessins sont prêts à être utilisés avec FDM, SLA, SLS et d'autres types d'imprimantes 3D. Enfilez vos chaussures imprimées en 3D aujourd'hui!\n","textures":"Faites une recherche parmi une grande quantité de %{model_count} CG textures disponibles pour téléchargement, y compris les matériaux magnifiquement réalisés. Parcourez la catégorie et trouver le tissu sans couture, bois, métal, plastique, textures de briques pour aider à terminer votre projet plus rapidement. Utilisez notre recherche avancée pour trouver des textures truquées et d'animation pour jeu ou la création vidéo. Nos matériaux sont disponibles en téléchargement dans différents formats, y compris TGA, SBSAR, JPG, PNG, TIFF et d'autres, alors ne perdez pas de temps et trouver prêt à utiliser des textures CG pour presque tous les imaginables du projet.\n"},"various":"%{count} différents modèles 3D qui ne trouvent pas facilement leur place dans d'autres catégories sont disponibles pour téléchargement. %{rocks_link}, %{clouds_link}, %{logos_link}, %{cigarette_link} et d'autres modèles non classés 3D que vous pourriez avoir besoin pour compléter votre jeu, animation ou projet VR / AR sont ici. Utilisez les filtres pour trouver truquées, animé, low-poly ou libres modèles 3D, ou trier par formats disponibles tels que FBX, OBJ, MAX, 3DS, C4D.","vehicle":"%{count} véhicules modèles 3D disponibles en téléchargement comprennent les camions, %{ambulances_link}, bus, %{bikes_link} et moto - cette catégorie est pour les gens qui veulent que leurs rues de la ville à être remplie de véhicules qu'elles soient réalistes ou de science-fiction. Ceux qui font des jeux de simulation ou VR / applications AR, créations dans la production vidéo ou même la conception des affiches seront plus susceptibles de trouver des formats de fichiers les plus populaires tels que FBX, OBJ, MAX, 3DS, utilisable C4D. D'autres véhicules de transport terrestre comme le train ou %{tractor_link} modèles 3D peuvent également être trouvés dans cette vaste collection. Utilisez les filtres pour trouver truqué, animé, faible ou poly libre modèles 3D plus rapide.","watercraft":"%{count} motomarines modèles 3D disponibles pour téléchargement incluent %{boats_link}, %{yachts_link}, %{ships_link} et d'autres véhicules allant de l'eau de toutes sortes. Les poly-bas modèles 3D truquées se comportent bien dans les jeux vidéo, les projets VR / AR ainsi que des animations, tandis que les modèles CG sont ici pour vos scènes et d'autres applications où la fidélité visuelle est plus important que polycount. Les modèles sont disponibles dans tous les formats de fichiers, y compris, mais sans s'y limiter FBX, OBJ, MAX, 3DS, C4D."},"tag":{"meta":{"3d_merged_premium":"Téléchargez %{rounded_count} modèles 3D de %{tag_low}, gratuits et premium. En FBX, OBJ, BLEND, GLB et plus, pour jeux, cinéma, AR/VR et impression 3D.","3d_merged_premium_print":"Téléchargez %{rounded_count} fichiers STL %{tag_low} pour impression 3D, gratuits et premium, testés pour imprimantes FDM et résine.","3d_models":"Téléchargez %{rounded_count} modèles 3D de %{tag_low}, gratuits et premium. En FBX, OBJ, BLEND, GLB et plus, pour jeux, cinéma, AR/VR et impression 3D.","3d_print_models":"Téléchargez %{rounded_count} fichiers STL %{tag_low} pour impression 3D, gratuits et premium, testés pour imprimantes FDM et résine.","animated_3d_models":"%{count} %{tag} animation des modèles 3D disponibles en téléchargement dans un format de fichier, y compris FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Télécharger %{count} %{tag} 3D gratuits modèles, disponibles en MAX, OBJ, FBX, 3DS, formats de fichiers C4D, prêts pour VR / AR, animation, jeux et autres projets 3D.%{page}","free_3d_print_models":"Télécharger %{count} %{tag} Modèles d'impression 3D prêts à FDM, SLA, SLS et autres imprimantes 3D, STL formats disponibles, OBJ, 3DM et plus.%{page}","lowpoly_3d_models":"Télécharger %{count} VR / AR / low-poly %{tag} modèles 3D prêts pour la réalité virtuelle, jeux et autres applications à temps réel, disponible en FBX, OBJ, MAX, 3DS, les formats de fichiers C4D.%{page}","rigged_3d_models":"%{count} %{tag} truqué modèles 3D disponibles en téléchargement dans un format de fic
16518hier, y compris FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"title":{"download":"| télécharger","files":"fichiers 3D","formats":"Modèles 3D %{format_upper}","free":"Modèles 3D Gratuits","free_printing":"Fichiers STL Gratuits","free_tag":"Gratuit %{selected_tag} ​​modèles 3D","general":"Modèles 3D","high_poly":"%{selected_filter} Modèles 3D","landing":{"animated_3d_models":"Modèles 3D Animés","lowpoly_3d_models":"Modèles 3D Low Poly","pbr_3d_models":"Modèles 3D PBR","rigged_3d_models":"Modèles 3D Riggés"},"low_poly":"%{selected_filter} 3D modèles","newest_cg":"Nouveaux et derniers modèles 3D","newest_print":"Nouveaux et derniers modèles d'impression 3D","page_prefix":"Page %{page_number} de ","pbr":"PBR 3D modèles","printing":"Fichiers 3D \u0026 STL pour Imprimante 3D","sales_cg":"Meilleures ventes de modèles 3D","sales_print":"Meilleures ventes de modèles d'impression 3D","software_page":"Modèles 3D pour %{software}","tag_heading":{"cg":"Gratuit  %{tag} Modèles d'impression 3D","filter":"%{selected_filter} %{tag} Modèles 3D","general":"%{tag_cap} 3D – Modèles 3D","merged_premium_print_tag":"Fichiers STL %{tag_cap}","merged_premium_tag":"%{tag_cap} 3D – Modèles 3D","printable":"Fichiers STL %{tag_cap}"},"trending_cg":"Modèles 3D tendance","trending_print":"Modèles d'impression 3D tendance"}},"cart":{"request_invoice_failed":"Oups. Une erreur s’est produite.","request_invoice_successful":"La demande de facture a été envoyée avec succès.","shareable_cart_created":"Le panier a été partagé avec succès.","shareable_cart_failed":"Oups. Une erreur s’est produite.","subscription_covered_items_removed":"Retiré de votre panier : %{titles}. Déjà inclus dans votre abonnement.","subscription_covered_order_refused":"Votre abonnement inclut désormais %{titles}, nous ne vous avons donc rien facturé. Téléchargez-le depuis votre abonnement."},"categories":{"cg":{"header_title_html":"Modèles 3D","text":"Trouvez le contenu 3D exact qui correspond à vos besoins, y compris AR/VR, jeux, publicité, divertissement et autres"},"head-title":{"cg":"Modèles 3D – Téléchargement Gratuit \u0026 Premium","cg-models":{"sort_by":{"newewst":"Les plus récents Modèles 3D","top-selling":"Modèles 3D les plus vendus","trending":"Modèles 3D tendance"}},"print":"Fichiers 3D pour Imprimante 3D – Fichiers STL","print-models":{"sort_by":{"newewst":"Les plus récents Fichiers d'impression 3D","top-selling":"Fichiers d'impression 3D les plus vendus","trending":"Fichiers d'impression 3D tendance"}},"product-type":{"plural":{"capital":{"cg":"Modèles 3D","cg-merge-premium":"Modèles 3D – Téléchargements gratuits et premium","print":"Fichiers et Modèles d'Impression 3D","print-merge-premium":"%{tag} Fichiers d'impression 3D - modèles STL gratuits et premium"},"lower":{"cg":"modèles 3D","print":"fichiers et modèles d'impression 3D"}},"singular":{"capital":{"cg":"Modèle 3D","print":"Modèle d'impression 3D"},"lower":{"cg":"modèle 3D","print":"modèle d'impression 3D,"}}}},"index":{"browse_all":"Parcourir tous les modèles","info-section__description_1_html":"Achetez ou téléchargez gratuitement des modèles 3D professionnels prêts à être utilisés dans des projets CG, la production de films et vidéos, l’animation, les visualisations, les jeux, la VR/AR et autres. Les ressources sont disponibles dans de nombreux \u003ca href='https://www.cgtrader.com/blog/3d-file-formats' target='_blank' rel='noopener noreferrer'\u003eformats acceptés par l’industrie\u003c/a\u003e, y compris MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB et autres. Si vous recherchez des assets high poly ou temps réel, nous avons une bibliothèque d’art numérique leader pour tous vos besoins.","info-section__description_1_printable_html":"Fichiers, impressions et modèles 3D professionnels prêts à être imprimés à domicile avec des imprimantes 3D FDM de bureau ou SLS industrielles, disponibles dans de nombreux formats industriels, y compris OBJ, \u003ca href=\"https://www.cgtrader.com/3d-print-models/stl-files\"\u003eSTL\u003c/a\u003e, WRL. Si vous êtes un passionné d'impression 3D, un maker, un éducateur ou une entreprise réalisant des prototypes par impression 3D, nos décorations pour la maison, accessoires de gadgets tendance, pièces de mode accrocheuses, bijoux uniques, pièces mécaniques et une variété de miniatures vous inspireront certainement.","info-section__heading_1_html":"Explorer les \u003cstrong\u003ecatégories\u003c/strong\u003e de modèles 3D","info-section__heading_1_printable_html":"Explorer \u003cstrong\u003e les catégories \u003c/strong\u003ede modèle d'impression 3D ","info-section__heading_2_html":"\u003cstrong\u003eMarché\u003c/strong\u003e des modèles 3D","info-section__heading_2_printable_html":"Quoi d’\u003cstrong\u003eautre\u003c/strong\u003e ?"},"marketplace":{"3d-printing":{"link_text":"Parcourir les modèles d’impression 3D","text":"Trouvez des modèles d’impression 3D pour votre imprimante 3D.","title":"Modèles d’impression 3D"},"blender_text":"Découvrez des milliers de modèles 3D Blender disponibles au format blend.","browse":"Parcourir","cinema-4d_text":"Découvrez des milliers de modèles Cinema 4D de haute qualité disponibles au format c4d.","discounted":{"link_text":"Parcourir Modèles 3D à prix réduits","text":"Acheter des modèles 3D professionnels à prix réduit.","title":"Modèles 3D à prix réduits"},"low-poly":{"link_text":"Parcourir les modèles 3D low poly","text":"Modèles 3D low poly prêts pour les jeux vidéo, applications VR et AR en temps réel.","title":"Modèles 3D low poly"}
16518,"pbr":{"link_text":"Parcourir les modèles 3D PBR","text":"Modèles 3D basés sur le rendu physique.","title":"Modèle 3D PBR"},"scripts_apps":{"link_text":"Parcourir tout","text":"Scripts et plugins 3D pour rendre votre pipeline de modélisation 3D plus efficace.","title":"Scripts \u0026 Applications"},"sketchup_text":"Parcourez la vaste sélection de modèles 3D Sketchup pour vos conceptions architecturales, intérieures et de jeux vidéo.","textures":{"link_text":"Parcourir les textures","text":"Textures prêtes à l’emploi pour gagner du temps dans la préparation de vos modèles 3D.","title":"Textures"},"zbrush_text":"Trouvez des assets 3D au format ZTL pour vos projets de sculpture numérique, de modélisation 3D, de texturation et de peinture."},"meta-description":{"cg":"Téléchargez ou achetez %{rounded_count} modèles 3D professionnels. Modèles gratuits et premium pour jeux, cinéma, VFX, AR/VR et impression 3D. FBX, OBJ, BLEND, GLB.","print":"Téléchargez %{rounded_count} fichiers 3D pour impression 3D. Fichiers STL gratuits et premium pour imprimante 3D: figurines, miniatures, déco et plus."},"more":{"designers":{"link_text":"Parcourir la liste des designers 3D","text":"Parcourez des milliers de portfolios de designers 3D et trouvez exactement ce dont vous avez besoin pour votre application.","title":"Trouvez les meilleurs designers 3D"},"projects":{"link_text":"En savoir plus sur les projets 3D","text":"Engagez des artistes 3D freelance de qualité et résolvez tous vos défis de modélisation personnalisée.","title":"Projets 3D CGTrader"},"visualization":{"link_text":"En savoir plus","text":"Transformez vos produits en modèles 3D photoréalistes pour le e-commerce et d’autres applications professionnelles.","title":"Visualisation 3D pour e-Commerce"}},"navbar":{"aircraft":"Avion","animal":"Animaux","animals":"Animaux","architectural":"Architectural","art":"Art","car":"Voiture","character":"Personnage","exterior":"Extérieur","fashion":"Mode","food":"Nourriture","furniture":"Meubles","gadgets":"Gadgets","games-toys":"Jeux et jouets","hobby-diy":"Hobby \u0026 Bricolage","house":"Maison","household":"Ménage","industrial":"Industriel","interior":"Intérieur","jewelry":"Bijoux","military":"Militaire","miniatures":"Miniatures","plant":"Plante","science":"Science","space":"Espace","vehicle":"Véhicule","watercraft":"Nautisme"},"print_models":{"hero_title_html":"Fichiers 3D \u0026 STL pour Imprimante 3D","subtitle":"Trouvez le contenu d’impression 3D exact qui correspond à vos besoins","title_html":"Acheter des \u003cstrong\u003emodèles 3D professionnels pour l’impression\u003c/strong\u003e"},"profile":{"collection":"packs 3D","free":"Modèles 3D","high_poly":"High Poly Modèles 3D","optimized":"Poly-Bas / Pbr Modèles 3D","printable":"Modèles D'Impression 3D"},"seo":{"cg":{"aircraft":"Cette catégorie couvre les avions 3D. Les avions CG conviennent aux simulations,\nvisualisations, publicités et vidéos. Les corps et pièces de drones raviront\nles amateurs de petits véhicules volants. Et les modèles riggés sont prêts à\nêtre importés dans les moteurs de jeu.\n","animals":"La collection de faune 3D – chats, chevaux, ours, rhinocéros, aigles, lions\net plus – se trouve ici. Les animaux CG répondent aux besoins des publicités ou\ndes scènes statiques. Nous avons également des modèles d’animaux riggés pour les jeux.\n","architectural":"Escaliers, cheminées, modèles 3D ainsi que des décorations comme cadres,\noreillers ou lampes se trouvent dans la vaste bibliothèque de modèles de détails\narchitecturaux. Intérieurs et extérieurs peuvent être utilisés pour construire des\ncuisines, chambres, salles de château ou donjons numériques, que vous soyez\narchitecte, ingénieur ou développeur indépendant.\n","car":"La catégorie Voitures contient des modèles 3D d’automobiles, dont les versions\nlow poly séduiront les concepteurs de jeux, d’autres les cinéastes ou publicitaires.\nDes voitures classiques aux voitures de course et concept cars, il existe une\ngrande variété de modèles 3D.\n","character":"Dans cette catégorie, vous trouverez des modèles 3D d’humains, de personnes,\nde science-fiction, de vêtements et autres. Du cœur et de la main aux modèles\nréalistes détaillés masculins ou féminins. De nombreux modèles sont riggés et\npeuvent être facilement ajoutés à votre jeu ou animation.\n","electronics":"Electronics est une catégorie pour les architectes, décorateurs d'intérieur, les concepteurs de niveau, les responsables de la scène et d'autres créateurs. modèles 3D de { \"slug\": \"smartphone\", \"title\": \"smartphones\"}, { \"slug\": \"ordinateur portable\", \"title\": \"ordinateurs portables\"}, { \"slug\": \"appareil photo\", \"title\" : \" caméras \"}, { \"slug\": \"tv\", \"title\": \" téléviseurs \"} et plus vous aidera à saisir avec précision l'obsession de la vie moderne avec des appareils et de la technologie.\n","engineering-parts":"Windows, les pipelines, les mécanismes de portes coulissantes et d'autres b
16518its qui sont nécessaires pour des projets de CAO et même des plans de bricolage entrent dans cette catégorie. C'est là que vous pouvez trouver des modèles 3D des fabricants mondiaux tels que ANVER, Dynatec et CFi.\n","exterior":"Parcourez des extérieurs architecturaux allant des façades, bâtiments urbains\naux objets paysagers, maisons résidentielles, constructions industrielles et\nmodèles de ville 3D à grande échelle. Certains assets sont low poly et peuvent\nvous aider à finaliser votre jeu ou application VR/AR.\n","food":"La nourriture aide à créer des scènes réalistes de vie domestique. Fruits,\nlégumes, boissons et repas en 3D sont tous ici. Publicités pour restaurants,\napplications AR pour une alimentation plus saine. Que vous cherchiez un modèle\nde pizza 3D ou de gâteau de mariage, ils sont ici.\n","furniture":"Tables, chaises, canapés, lits 3D dans une variété de textures (bois, métal,\netc.) et de styles (rétro, moderne, etc.) sont disponibles. Cette catégorie\ns’adresse à tous ceux impliqués dans l’architecture, le design d’intérieur et la publicité.\n","furniture-set":"Si vous avez besoin se ressembler chaise antique et un canapé à la recherche, ou un ensemble complet d'ustensiles de cuisine, c'est un endroit pour être aussi cette catégorie ensembles de modèles de Collectionnez meubles 3D de la cuisine et le salon au bureau et salle à manger ensembles de meubles de salle. Cela aide les chambres spécialisées design d'intérieur ou de niveau construction avec des éléments correspondants.\n","household":"Les modèles 3D d’objets ménagers sont utilisés pour donner des détails aux pièces.\nVous pouvez placer un service de table 3D sur la table ou utiliser des salières et\npoivrières pour compléter la cuisine, peut-être mettre des lumières et des fleurs\nprès du lit pour créer une ambiance luxueuse.\n","industrial":"Matériaux de construction, outils d’ingénierie, machines et conteneurs\nfournissant à la fois des détails environnementaux et des éléments interactifs riggés.\nParfait pour les développeurs de jeux de simulation ou ceux qui construisent des\nniveaux de chantier.\n","interior":"Cette catégorie contient des éléments 3D réalistes et des détails d’architecture,\nde construction et de design d’intérieur. Vous pouvez utiliser ces modèles 3D\npour l’animation ou les jeux – certains sont riggés et low poly.\n","military":"Dans cette catégorie, vous pouvez acheter des modèles 3D d’armes, d’armures,\nd’épées et d’artillerie – modernes et historiques – utilisés par les forces\nmilitaires ou pour l’autodéfense. Vous pouvez utiliser les modèles pour des\njeux – certains sont optimisés en polygones et prêts pour les moteurs de jeu.\n","plant":"Cette catégorie contient des modèles 3D d’organismes de flore tels que fleurs,\narbres, herbes, plantes grimpantes, verdure, herbes, vivaces, semis, pousses,\narbustes, boutures, germes, vignes, mauvaises herbes. Pour acheter ou télécharger\ngratuitement un modèle 3D, parcourez les catégories ci-dessous.\n","scanned":"Modélisateurs créer ces modèles en utilisant les technologies 3D comme RealSense. Les souches d'arbres, statues, les mâles et les femelles même sont très populaires ici. Le produit est prêt à être utilisé dans des scènes et des animations, offrant des niveaux inégalés de réalisme.\n","science":"Cette catégorie contient des modèles 3D liés à la médecine, la biologie, la physique, la chimie et de la technologie, tels que les équipements de laboratoire, des organismes microscopiques et dans le monde subatomique. Ne cherchez pas plus loin, { \"slug\": \"cellule\"}, { \"slug\": \"crâne \"} et { \"slug\": \"squelette\"} modèles 3D peuvent être trouvés ici.\n","scripts-plugins":"Les scripts et les plug-ins que vous fournissent des raccourcis logiciels. La modélisation, le rendu, texturation - tous ces éléments peuvent être facilitée en utilisant des plug-ins qui viennent pour toutes sortes de logiciels comme Maya et V-Ray.\n","space":"L’espace est une catégorie remplie de modèles low poly et prêts pour les jeux\nde vaisseaux spatiaux de science-fiction, astéroïdes, planètes (y compris la Terre\net des navettes spatiales réalistes pour les plus scientifiques). Les modèles 3D\nd’espace sont utiles pour les jeux de simulation spatiale VR.\n","sport-hobby":"Cette catégorie encompases tout ce qui est lié à { \"slug\": \" champ \"} événements, sports et loisirs en général. De { \"slug\": \"football \"} et des pistes de ski à { \"slug\": \"guitare\"} et { \"slug\": \"piano\"}, ce sont des modèles 3D utiles pour des publicités, des scènes et des animations. versions low-poly sont là pour les jeux vidéo de sport.\n","textures":"Parcourez une bibliothèque de matériaux 3D magnifiquement réalisés et trouvez\nles textures prÃ
16518ªtes à l’emploi les plus adaptées pour préparer des modèles prêts\npour les jeux ou photoréalistes, améliorer votre bibliothèque de ressources et\nperfectionner vos travaux 3D.\n","various":"Ce sont les modèles 3D qui ne correspondaient pas facilement dans d'autres catégories, nous avons donc créé un qui les accepter tous. { \"title\": \"Rocks\", \"slug\": \"rochers\"}, { \"slug\": \"nuages\"}, { \"title\": \"logos\", \"slug\": \"logo\"}, { \"title\" : \" parapluies \", \"slug\": \"parapluie\"} et d'autres non classés modèles 3D sont ici.\n","vehicle":"Camions, ambulances, bus, trains, tracteurs, vélos et motos — cette catégorie\ns’adresse aux personnes qui veulent que leurs rues soient remplies de véhicules\nréalistes et de science-fiction, ainsi qu’à ceux qui créent des jeux de simulation\nou des affiches.\n","watercraft":"Les bateaux couvrent les embarcations, yachts, navires et autres véhicules\nmaritimes. Les modèles 3D riggés conviennent bien aux jeux vidéo, animations, tandis\nque les modèles CG sont destinés aux scènes ou autres applications où la fidélité\nvisuelle est plus importante que le nombre de polygones.\n"},"forwarded":{"cg":{"aircraft":"Cette catégorie couvre les aéronefs 3D. CG { \"slug\": \"avions\", \"title\": \"avions\"} s'intégrera dans des simulations, des visualisations, des publicités et des vidéos. { \"slug\": \"drone\", \"title\": \"Drone\"} corps et parties vont ravir les amateurs de petits engins volants. Et les { \"slug\": \"avions\", \"title\": \"modèles squelettés\", \"active_filter\": \"rigged\"} sont prêts à être importés dans les moteurs de jeu.\n","animals":"La collection de la faune 3D - { \"slug\": \"cat\", \"title\": \"chats\"}, { \"slug\": \"horse\", \"title\": \"chevaux\"}, { \"slug\": \"ours\" , \"title\": \"ours\"}, { \"slug\": \"rhinocéros\", \"title\": \"rhinocéros\"}, { \"slug\": \"aigles\", \"title\": \"aigles\"}, { \"slug\": \"lions\", \"title\": \"lions\"} et plus - peuvent être trouvés ici. Les animaux CG en 3D  satisferont les besoins de la publicité ou des scènes statiques. Nous avons aussi  des modèles { \"slug\": \"animaux\", \"title\": \"d'animaux squelettés\", \"active_filter\": \"rigged\"} pour les jeux.\n","architectural":"Des { \"slug\": \"escaliers\", \"title\": \" escaliers\"}, { \"slug\": \"cheminées\", \"title\": \"des cheminées\"}, en modèles 3D, ainsi que des décorations comme { \"slug\": \"cadre\", \"title\": \"des cadres\"}, { \"slug\": \"oreiller\", \"title\": \"des oreillers\"} ou { \"slug\": \"lampe\", \"title\": \"des lampes\"} se trouvent dans le détail architectural étendu. La catégorie voitures contient des modèles d'automobile 3D, { \"slug\": \"voiture\", \"title\": \"low poly\", \"active_filter\" : \"low_poly\"} dans la bibliothèque de modèles. Intérieurs et extérieurs peuvent être utilisés pour construire des cuisines numériques et des chambres ainsi que des chambres de château et des donjons que vous soyez un architecte, un ingénieur ou un développeur de jeux indépendant.\n","car":"La catégorie des voitures contient des modèles 3D d'automobiles,dont  les versions \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e low poly \u003c/a\u003e seront attractives pour les concepteurs de jeux, d'autres - pour les cinéastes ou les annonceurs. Des voitures classiques aux voitures de course et de piste à celles de conception, il y a une grande variété de modèles d'automobiles 3D.\n","character":"Dans cette catégorie, vous trouverez des modèles 3D de { \"slug\": \"humain\", \"title\": \"humain\"}, gens, de science-fiction, de vêtements et d'autres. Du { \"slug\": \"coeur\", \"title\": \"coeur\"} et { \"slug\": \"mains\", \"title\": \"des mains\"} à de réalistes et détaillés modèles {\"slug\": \"mâle\", \"title\": \"de mâle\"} ou { \"slug\": \"femelle\", \"title\": \"femelle\"} . Un certain nombre de modèles sont suqelettés et pourrait être facilement ajouté à votre jeu ou  animation.\n","electronics":"Electroniques est une catégorie pour les architectes, décorateurs d'intérieur, concepteurs de niveau, créateur de  scènes et d'autres concepteurss. Les modèles 3D de { \"slug\": \"smartphone\", \"title\": \"smartphones\"}
16518, { \"slug\": \"lordinateurs-portables\", \"title\": \"ordinateurs portables\"}, { \"slug\": \"appareils-photos\", \"title\" : \" appareils photos\"}, { \"slug\": \"téléviseurs, \"title\": \" téléviseurs\"} et plus encore vous aideront à saisir avec précision l'obsession de la vie moderne pour des appareils et de la technologie.\n","engineering-parts":"Les fenêtres, les tuyaux, les mécanismes de portes coulissantes et d'autres éléments qui sont nécessaires pour des projets de CAO et même des plans de bricolage entrent dans cette catégorie. C'est là que vous pouvez trouver des modèles 3D des fabricants mondiaux tels que ANVER, Dynatec et CFi.\n","exterior":"Explorez les extérieurs architecturaux de façades, bâtiments urbains jusqu'à à des objets de paysage, des maisons d'habitation, des constructions industrielles, et des modèles 3D de { \"slug\": \"ville\", \"title\": \"ville\"} en taille réelle. Certains des actifs sont en low poly et pourrait vous aider à compléter votre jeu ou une application VR / AR.\n","food":"La nourriture aide à créer des scènes réalistes de la vie domestique. Les fruits 3D, les légumes, les boissons et les repas sont tous présents ici. Les publicités pour les restaurants, les applications AR pour manger plus sain. Que vous cherchiez un modèle 3D de{ \"slug\": \"pizza\", \"title\": \"pizza\"} ou un { \"slug\": \"gâteau\", \"title\": \"gâteau\"} de mariage , ils sont là.\n","furniture":"Les tables, chaises, canapés, lits 3D dans une variété de textures (bois, métal, etc.) et de styles ({ \"slug\": \"retro\", \"title\": \"retro\"}, { \"slug\": \"moderne\", \"title\": \"moderne\"}, etc.) sont à portée de main. Cette catégorie est destinée à toute personne impliquée dans l'architecture et le design intérieur, ainsi que les annonceurs.\n","furniture-set":"Si vous avez besoin de chaises anciennes et de canapés qui se ressemblent, ou un ensemble complet d'ustensiles de cuisine, c'est l'endroit où être  car cette catégorie rassemble des modèles 3D de set de meubles, de la cuisine et du salon au bureau et à la salle à manger. Cela aide les spécialistes du design d'intérieur ou de niveau a construire des chambres avec des éléments correspondants.\n","household":"Les modèles 3D d'articles ménagers sont utilisés pour donner des détails aux chambres. Vous pouvez mettre un ensemble de dîner 3D sur la table, ou avoir des poivriers et des salières pour complèter l'intérieur de la cuisine, peut-être mettre un peu de{ \"slug\": \"lumière\", \"title\": \"lumière \"} et des  { \"slug\": \"fleurs\", \"title\": \"fleurs\" } près du lit pour créer une sensation luxueuse.\n","industrial":"Les matériaux de construction, outils, machines et { \"slug\": \"conteneurs\", \"title\": \"conteneurs\"} d' { \"slug\": \"ingénierie\", \"title\": \"ingénierie\"}  fournissent les détails environnementaux et interactifs, des éléments squelettés. C'est parfait pour les développeurs de jeux de simulation ou les personnes qui construisent { \"slug\": \"bâtiments\", \"title\": \"bâtiments\"} au niveau du sol.\n","interior":"Cette catégorie contient des éléments 3D réalistes et des détails d'architecture, de construction et de décoration intérieure de l'ingénierie. Vous pouvez utiliser ces modèles 3D pour l'animation ou des jeux - certains d'entre eux sont squelettés et low poly.\n","military":"Dans cette catégorie, vous pouvez acheter des modèles 3D d'armes, { \"title\": \"épées\", \"slug\": \"épées\"} et de l'artillerie à la fois moderne et historique utilisé par les forces militaires et pour la légitime défense. Vous pouvez utiliser des modèles pour les jeux - certains d'entre eux sont en polygones optimisés et prêts pour les moteurs de jeu.\n","plant":"Cette catégorie contient des modèles 3D d'organismes de la flore tels que des fleurs, { \"slug\": \"arbres\", \"title\": \"arbres\"}, gazon, lianes, verdure, herbes, plantes vivaces, plants, pousses, arbustes, germes, vignes, mauvaises herbes. Pour acheter ou télécharger un modèle 3D gratuit, explorez les catégories ci-dessous.\n","scanned":"Les modélisateurs ont créé ces modèles en utilisant les technologies 3D comme RealSense. Les souches d'arbres, les statues, les mâles et les femelles même sont très populaires ici. Le produit est prêt à être utilisé dans de
16518s scènes et des animations, offrant des niveaux inégalés de réalisme.\n","science":"Cette catégorie contient des modèles 3D liés à la médecine, la biologie, la physique, la chimie et à la technologie, tels que les équipements de laboratoire, les organismes microscopiques et le monde subatomique. Ne cherchez pas plus loin,  les modèles 3D de { \"slug\": \"cellule\", \"title\": \"cellule\"}, { \"slug\": \"crâne\", \"title\": \"crâne\"} et { \"slug\": \"squelette\", \"title\": \"squelette\"} peuvent être trouvés ici.\n","scripts-plugins":"Les scripts et les plug-ins vous fournissent des raccourcis logiciels. La modélisation, le rendu, la  texturation - tous cela peut être facilité en utilisant des plug-ins disponibles pour toutes sortes de logiciels comme Maya et V-Ray.\n","space":"L'espace est une catégorie remplie de modèles prêts-à-jouer low poly  de science-fiction, des { \"slug\": \"vaisseaux-spatiaux\", \"title\": \"vaisseaux spatiaux\"}, { \"slug\": \"astéroïdes\", \"title\": \"astéroïdes\"}, les planètes (y compris la {\"slug\": \"terre\", \"title\": \"terre\"} et les {\"slug\": \"navettes\", \"title\": \"navettes\"} de l'espace réalistes, pour les plus portés scientifiquement. Les modèles 3D spatiaux sont utiles pour les jeux de simulateur spatial VR.\n","sport-hobby":"Cette catégorie englobe tout ce qui est lié aux  événements, sports et loisirs en général de { \"slug\": \"terrain\", \"title\": \"terrain\"}. Du { \"slug\": \"football\", \"title\": \"football\"} et des pistes de ski à la{ \"slug\": \"guitare\", \"title\": \"guitare\"} et au { \"slug\": \"piano\", \"title\": \"piano\"}, ce sont des modèles 3D utiles pour des publicités, des scènes et des animations. les versions low-poly sont là pour les jeux vidéo de sport.\n","textures":"Parcourez une bibliothèque de matériaux 3D magnifiquement réalisés et trouver les textures les mieux adaptées et prêtes-à-l'emploi  pour préparer des modèles prêts-à-jouer ou photoréalistes \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e\u003c/a\u003e, ou mettre à jour votre bibliothèque d'actifs, et d'améliorer vos travaux en 3D.\n","various":"Ceux-ci sont les modèles 3D qui ne correspondaient pas facilement à d'autres catégories, nous en  avons donc créé une qui les accepterait tous. Les { \"title\": \"rochers\", \"slug\": \"rochers\"}, les  { \"slug\": \"nuages\", \"title\": \"nuages\"}, les { \"title\": \"logos\", \"slug\": \"logos\"}, les { \"title\" : \"parapluies\", \"slug\": \"parapluies\"} et d'autres modèles 3D non classés  sont ici.\n","vehicle":"Camions, { \"title\": \"ambulances\", \"slug\": \"ambulances\"}, les autobus, trains, tracteurs, { \"title\": \"motos\", \"slug\": \"motos\"} et motos - cette catégorie est pour les personnes qui veulent que les rues de leur ville soient remplies avec des véhicules réalistes et de science-fiction, et pour ceux qui font des jeux de simulation ou compositions d'affiches.\n","watercraft":"Le nautisme couvre les { \"title\": \"bateaux\", \"slug\": \"bateaux\"}, les { \"title\": \"yachts\", \"slug\": \"yachts\"}, les { \"title\": \"navires\", \"slug\": \"navires\"} et d'autres embarcations. Les modèles 3D squelettés se comportent bien dans les jeux vidéo, l'animation tandis que les modèles CG sont là pour vos scènes ou d'autres applications où la fidélité visuelle est plus importante que le polycount.\n"},"printable":{"art":"C'est là que se trouvent les impressions 3D artistiques. Vous recherchez des dessins de sculpture chics recréant des œuvres classiques et des statues de personnalités historiques, ou des figurines inspirantes, peut-être même des logos 3D imprimables? C'est cette catégorie.\n","fashion":"Les belles et élégantes choses que vous pouvez porter se trouvent dans cette catégorie. Voyez si vous pouvez trouver une utilisation pour des { \"slug\": \"chaussures\", \"title\": \"chaussures\"} fantaisies de l'âge spatial, des lunettes dernier cri et des {\"slug\": \"accessoires\", \"title\": \"accessoires\"} de costumes ici.\n","gadgets":"Toutes les coques d'{ \"title\": \"iPhone\", \"slug\": \"iphone\"}, d'{ \"title\": \"iPad\", \"slug\": \"ipad\"} (et d'autres appareils smart)  peuvent être trouvés dans la catégorie gadget. De simples ajouts 3D à des pièces complexes et \"astuces\" pour vos articles, c'est la catégorie pour les créateurs et les amateurs de technologie.\n","games-toys":"Jeux et jouets est la catégorie qui comprend les aliens ou monstre miniatures 3D imprimables, les tout petits { \"title\": \"chars\", \"slug\": \"chars\"}, les géniales pièces d' { \"slug\": \"échec\", \"title\": \"échec\"}, les casques de caractère de film, les accessoires de collection. C'est la catégorie où aller pour toutes les choses un peu geek.\n","hobby-diy":"Si vous êtes n
16518otre prochain grand inventeur et que vous voulez construire votre projet de bricolage ou obtenir des pièces de rechange pour votre { \"slug\": \"drone\", \"title\": \"drone\"}, c'est l'endroit idéal. De plus, elle dispose de nombreux éléménts pour les personnes qui veulent faire leurs propres figurines ou des répliques d'armes à feu en plastique.\n","house":"Toutes sortes de bric-à-brac peuvent être trouvés ici. Des { \"title\": \"Lampes\", \"slug\": \"lampes\"}, des { \"title\": \"bols\", \"slug\": \"bols\"} artistiques, et toutes sortes de { \"title\": \"contenants\", \"slug\": \"contenants\"} que vous ne trouviez pas un bon endroit pour votre téléphone, mettre en ordre des cure-dents, un tas de stylos ou des bijoux sur votre bureau.\n","jewelry":"Bijoux est où vous allez trouver des bracelets, des pendentifs ou des bagues avec des diamants. Alors que les imprimantes 3D ne sont pas, encore, capables de faire des diamants, vous pouvez toujours imprimer un anneau métallique comme base pour votre bague de fiançaille IT geek.","miniatures":"Les { \"title\": \"caractères\", \"slug\": \"caractères\"}, les gens célèbres, les { \"title\": \"monuments\", \"slug\": \"monuments\"}, les animaux, { \"title\": \"Homme\", \"slug\": \"mâle\"}, { \"slug\": \"femelle\", \"title\": \"femelle\"}, les { \"title\": \"monstres\", \"slug\": \"monstres\"}, les { \"title\": \"aliens\",\"slug\": \"aliens\"} imprimables en 3D - tout  est là. Que vous imprimiez des véhicules utilitaires à mettre sur votre table ou la création de miniatures pour vos jeux de table, c'est votre catégorie.\n","science":"Qu'est-ce qui peut être considéré comme faisant partie de la science? Eh bien, les modèles de squelette d'{ \"slug\": \"os\", \"title\": \"os\"},des {\"slug\": \"outils\", \"title\": \"outils\"} de médecins et des modèles 3D imprimables relatifs à l'astrophysique, qui peuvent être utilisés dans de nombreux processus d'étude.\n"}},"printable":{"art":"C'est là vont les impressions artistiques 3D. Vous recherchez la conception de sculpture de fantaisie recréer des œuvres classiques et des statues de personnalités historiques, ou figurines inspirant, peut-être même des logos 3D imprimables? Telle est la catégorie.\n","fashion":"Choses belles et élégantes que vous pouvez porter se trouvent dans cette catégorie. Voyez si vous pouvez trouver toute utilisation pour l'âge de l'espace de fantaisie { \"slug\": \"chaussures\"}, lunettes de nouveauté et cosplay {\"slug\": \"accessoires\"} ici.\n","gadgets":"Tous les { \"title\": \"iPhone\", \"slug\": \"iphone\"}, { \"title\": \"iPad\", \"slug\": \"ipad\"} (et d'autres appareils intelligents) les cas peuvent être trouvés dans la catégorie gadget. De simples add-ons 3D à des pièces complexes et \" hacks \" pour vos articles, c'est la catégorie pour les créateurs et les amateurs de technologie.\n","games-toys":"Jeux et jouets est la catégorie qui couvre imprimables miniatures étrangers ou monstre 3D, petit { \"title\": \" chars \", \"slug\": \"tank\"}, génial { \"slug\": \"échecs\"} pièces, casques de caractère de film, passe-temps accessoires et accessoires. C'est à aller catégorie pour toutes les choses geeks.\n","hobby-diy":"Si vous êtes notre prochain grand inventeur et que vous voulez construire votre projet de bricolage ou d'obtenir des pièces de rechange pour votre { \"slug\": \"bourdon\"}, c'est l'endroit idéal. De plus, il dispose d'une des choses beaucoup pour les gens qui veulent faire leurs propres figurines ou des répliques d'armes à feu en plastique.\n","house":"Toutes sortes de bric-à-brac peuvent être trouvés ici. { \"title\": \"Lampes\", \"slug\": \"lampe\"}, artistique { \"title\": \"bols\", \"slug\": \"bol\"}, et toutes sortes de { \"title\": \"détenteurs\", \"slug\": \"support\"} si vous ne pouvez pas trouver un bon endroit pour votre téléphone, maintenir l'ordre de cure-dents, tas de stylos ou de bijoux sur votre bureau.\n","jewelry":"Bijoux est où vous allez obtenir des bracelets et des pendentifs ou des bagues avec des diamants. Alors que les imprimantes 3D ne sont pas assez bon pour faire des diamants - encore - vous pouvez toujours imprimer un anneau métallique comme base pour votre IT geek anneau de proposition.\n","miniatures":"3D imprimable { \"title\": \"caractères\", \"slug\": \"caractère\"}, les gens célèbres, { \"title\": \"points de repère\", \"slug\": \"repère\"}, animaux, { \"title\": \"Homme\", \"slug\": \"mâle\"}, { \"slug\": \"femme\"}, { \"title\": \"monstres\", \"slug\": \"monstre\"}, { \"title\": \"étrangers\",\"slug\" : \"alien\"}
16518 - il est là. Que vous imprimez des véhicules utilitaires pour mettre sur votre table ou la création de miniatures pour vos jeux de table, il est votre catégorie.\n","science":"Ce qui peut être considéré comme la science? Eh bien, les modèles de squelette { \"slug\": \"os\"}, médecins {\"slug\": \"outils\"} et Astrophysique-modèles liés imprimables 3D qui peuvent être utilisés dans de nombreux processus d'étude.\n"},"titles":{"aircraft":"Modèles 3D d’avions","animals":"Modèles 3D d’animaux","architectural":"Modèles 3D architecturaux","car":"Modèles 3D de voitures","character":"Modèles 3D de personnages","exterior":"Modèles 3D d’extérieur","food":"Modèles 3D d’aliments","furniture":"Modèles 3D de meubles","household":"Modèles 3D domestiques","industrial":"Modèles 3D industriels","interior":"Modèles 3D d’intérieur","military":"Modèles 3D militaires","plant":"Modèles 3D de plantes","space":"Modèles 3D de l’espace","textures":"Modèles 3D de textures CG","vehicle":"Modèles 3D de véhicules","watercraft":"Modèles 3D de bateaux"}},"title":{"cg":" Modèles 3D","print":" Fichiers d'impression 3D"}}
16518,"competition":{"digital-art-competition":{"categories":{"page_description":"Participez au Concours Art numérique CGTrader en soumettant vos œuvres à l'illustration de caractère, caractère Design Concept, Illustration Environnement, Environnement Design Concept, objet design, et les catégories d'objets Concept design. Si vous avez plusieurs grandes œuvres qui conviennent à différentes catégories, ne hésitez pas à les soumettre aux catégories correspondantes. Sélectionnez une catégorie pour voir les prix pour cette catégorie."}}},"conversations":{"action_button_confirmation":"Voulez-vous vraiment rouvrir cette demande ?","reply_posted":"La réponse a été publiée.","with_yourself":"ne peut pas être identique à l’expéditeur."},"date":{"month_names":[null,"Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"]},"devise":{"failure":{"too_many_requests":"Vous avez déjà demandé un e-mail pour réinitialiser le mot de passe. Veuillez attendre quelques minutes avant de réessayer."}},"disabled_cookies_modal":{"button":"Annuler","text_1":"Vous ne pourrez pas explorer des millions de grands modèles 3D, les ajouter au panier ou acheter.","text_2":"Si vous voulez faire l'expérience CGTrader pleinement, nous encourageons l'activation des cookies sur ce site.","title":"Il semble que vous avez les cookies de votre navigateur désactivée"},"emails":{"error_notification":"Abonnement introuvable.","success_subscribed_notification":"Vous vous êtes abonné avec succès.","success_unsubscribed_notification":"Vous vous êtes désabonné avec succès."},"errors":{"messages":{"invalid_vat":"n’est pas un numéro de TVA %{country_adjective} valide","vies_down":"Impossible de valider votre numéro de TVA : le service de validation VIES est indisponible. Merci de réessayer ultérieurement."}},"file_size_validator":{"errors":{"invalid_carrierwave":"Un objet CarrierWave::Uploader::Base était attendu","invali
16518d_key":":%{key} doit être un entier non négatif","range_format":":in et :within doivent être une plage (Range)","range_unspecified":"Plage non spécifiée. Indiquez l’option :within, :maximum, :minimum ou :is."}},"file_type":{"default_error":"Aucun chemin de fichier fourni."},"footer":{"bottom-row":{"privacy":"Confidentialité","terms-link":"Conditions générales"},"links":{"buyLinks":[{"text":"Acheter des modèles 3D","link":"/fr/3d-modeles"},{"text":"Pour les entreprises","link":"/discover/business-account"},{"text":"Jeux de données 3D","link":"/discover/datasets"},{"text":"Embaucher un designer 3D","link":"/3d-modeling-jobs"},{"text":"Modèles 3D gratuits","link":"/fr/libre-3d-modeles"},{"text":"Modèles d'impression 3D gratuits","link":"/fr/libre-3d-imprimer-modeles"}],"communityLinks":[{"text":"Communauté","link":""},{"text":"Top designers","link":"/designers"},{"text":"Défis","link":"/challenges"},{"text":"Forum","link":"/forum"}],"companyLinks":[{"text":" Compagnie","link":"/discover/about-us"},{"text":"Blog","link":"/blog"},{"text":"Carrières","link":"/pages/careers"},{"text":"Centre d'aide","link":"https://help.cgtrader.com"}],"sellLinks":[{"text":"Vendre des modèles 3D","link":"/discover/sell-3d-models"},{"text":"Développez vos ventes","link":"/discover/sell-more"},{"text":"Tendances du marché 3D","link":"/discover/3d-market-trends"},{"text":"Recherches de modèles 3D populaires","link":"/discover/3d-models-top-searches"},{"text":"Recherches d'impression 3D populaires","link":"/discover/3d-print-models-top-searches"}]},"subscribe":{"button-active":"Souscrire","button-disabled":"Inscrit","header":"Abonnez-vous à nos nouvelles:","placeholder":"Entrer votre Email","terms-link":"Conditions d'utilisation","text":"En vous abonnant confirmez que vous avez lu et accepté notre"}},"general":{"description":"CGTrader est un marché en ligne où les artistes 3D et les concepteurs peuvent acheter, vendre ou partager des modèles 3D. Elle a été fondée et est mis au point par une équipe de designers et d'ingénieurs en 3D. Notre objectif est de démocratiser le marché boursier 3D et changer la façon dont nous vend
16518ons des modèles 3D en ligne. En laissant les auteurs gagnent des redevances maximales, nous nous efforçons d'accroître leur motivation et améliorer la qualité des modèles 3D.","navigation":{"buy_3d_models":"Acheter Modèles 3D","sell_3d_models":"Vendre Modèles 3D"},"search_placeholder":"Rechercher dans plus de 2 millions de modèles","site-search__field":"Recherchez parmi %{model_count} modèles 3D","site_title":"Modèle 3D Magasin","site_title_postfix":" | CGTrader"},"generator":{"landing_pages":{"three_d_model":{"defaults":{"closing_cta":{"heading":"Laissez l'IA réaliser vos rêves","subtitle":"Transformez un prompt ou une image de référence en un modèle 3D prêt pour la production, puis affinez-le, retexturez-le et exportez-le dans le format dont vous avez besoin."},"faq":{"heading":"Questions fréquemment posées","items":{"commercial":{"answer":"L'utilisation commerciale de votre modèle 3D %{object} est disponible avec un plan payant CGTrader AI. Veuillez consulter les Conditions Générales de CGTrader pour les conditions de licence applicables aux modèles générés par IA.","question":"Puis-je utiliser le modèle %{object} généré à des fins commerciales ?"},"formats":{"answer":"Les modèles 3D de %{object} sont disponibles aux formats GLB, FBX, OBJ, STL ou USDZ.","question":"Dans quel format de fichier puis-je obtenir mon modèle %{object} ?"},"no_skills":{"answer":"Aucune connaissance en modélisation 3D n'est requise. Toute la modélisation est effectuée via le générateur de modèles %{object}. Le système gère la géométrie, les proportions et la structure lorsque vous saisissez votre prompt ou téléchargez une image. Même les débutants peuvent créer un modèle 3D %{object} complet en quelques minutes.","question":"Faut-il des connaissances en modélisation 3D pour utiliser un générateur de modèles %{object} ?"},"what_is":{"answer":"Le générateur de modèles 3D %{object} de CGTrader, un outil basé sur l'IA, crée un modèle tridimensionnel de %{object} à partir d'instructions écrites ou d'images de référence. Il analyse votre idée et crée un modèle téléchargeable pour la conception, le prototypage et la visualisation. Nos abonnements payants offrent des limites mensuelles plus élevées.","question":"Qu'est-ce qu'un générateur de modèles 3D %{object} ?"},"why_cgtrader":{"answer":"Les utilisateurs peuvent faire confiance à CGTrader pour une expérience fluide, des temps de traitement rapides et des sorties 3D de haute qualité. Cet outil convient aussi bien aux professionnels qu'aux débutants qui recherchent une solution fiable pour créer des modèles 3D %{object}.","question":"Pourquoi utiliser le générateur %{object} par IA de CGTrader ?"}}},"h1":"Générateur de modèles 3D %{object} par IA","hero_alt":"Générateur de modèles 3D %{object} par IA","hero_height":617,"hero_width":1080,"how_to":{"heading":"Générer un %{object} à partir d'images ou de prompts texte","paragraphs":["Il est très facile de concevoir un modèle tridimensionnel de %{object}. Dans la barre latérale gauche, en haut à gauche, cliquez sur le bouton pour télécharger votre image de %{object} et créer un modèle 3D. Vous pouvez également saisir un prompt dans le champ « Décrivez ce que vous souhaitez créer » en bas. CGTrader AI affine automatiquement votre prompt, donc une courte description suffit pour commencer.","La barre latérale droite vous donne un bon contrôle sur votre modèle 3D %{object}. Choisissez des paramètres tels que le type de maillage, le matériau, le guidage du prompt et le format de fichier. Vous pouvez également appliquer des textures pour personnaliser votre modèle %{object}. Après avoir saisi vos instructions texte ou image, cliquez sur « Générer ». L'outil traitera votre entrée en quelques minutes, selon la charge système, le matériau, le type de maillage, les formes, les valeurs de seed et la qualité."],"steps":["Rédigez votre description textuelle et/ou joignez une image de référence pour améliorer la précision finale de votre modèle 3D %{object}.","Sélectionnez l'un de nos préréglages, ou choisissez un moteur IA, ajoutez un nombre de polygones personnalisé et sélectionnez le type de matériau pour votre modèle 3D %{object}.","Cliquez sur Générer pour créer un modèle 3D %{object} à partir de votre prompt et/ou de votre image de référence.","Téléchargez votre modèle 3D %{object} créé dans différents formats (GLB, FBX, OBJ, STL ou USDZ)."]}
16518,"intro":"Vous cherchez un moyen simple et rapide de créer un modèle 3D %{object} de haute qualité en quelques minutes ? Notre générateur de modèles 3D %{object} avancé, CGTrader AI, peut transformer des commandes écrites ou des images de référence en modèles entièrement fonctionnels. Il fonctionne sur les meilleurs moteurs de génération 3D, notamment Meshy, Hunyuan et Tripo3D. Il permet la modélisation rapide de modèles 3D aussi bien pour les débutants que pour les experts en design, pour la visualisation, l'expérimentation et le prototypage. Il prend en charge le texte vers 3D et l'image vers 3D, et est compatible avec les imprimantes 3D. Vous pouvez télécharger une image au format JPG ou PNG, ou saisir une description en texte. CGTrader AI s'occupe du reste.","meta_description":"Générateur de modèles 3D %{object} par IA de CGTrader ☑️ Créez des modèles 3D %{object} détaillés à partir de texte ou d'images. Commencez gratuitement.","primary_cta":"Commencer à générer gratuitement","title":"Générateur de modèles 3D %{object} par IA (gratuit) | CGTrader"}}},"rules":{"errors":{"front_view_required":"Une image de la vue de face est requise."}}},"home":{"index":{"3D-projects":{"info-section__cta":"Apprendre encore plus","info-section__description":"Où les gens avec des projets 3D passionnants rencontrent talentueux designers 3D","info-section__heading_html":"\u003cStrong\u003e Projets 3D \u003c/strong\u003e Freelance Hub"},"categories-conatainer":{"browse_all":"Parcourir tous les modèles","browse_all_tooltip":"Parcourir tous les modèles 3D","discounts":"Réductions"},"cgdream":{"cta":"Commencez gratuitement!","heading_html":"\u003cstrong\u003eDécouvrez CGDream,\u003c/strong\u003e\u003c/br\u003e où l’IA rencontre les modèles 3D.","text_html":"\u003cstrong\u003e CGDream \u003c/strong\u003e est une application de pointe qui tire parti de la puissance des modèles Génératifs AI et 3D pour produire de superbes images 2D. \u003cstrong\u003e Votre voyage créatif commence maintenant. \u003c/strong\u003e"},"community":{"info-section__cta":"Rejoignez la communauté","info-section__heading_html":"Joignez-vous à CGTrader \u003cstrong\u003e Communauté \u003c/strong\u003e","info-section__icon-list_1":{"description":"Présentez votre rend le mieux, obtenir des commentaires et de la reconnaissance","heading":"Galeries"},"info-section__icon-list_2":{"description":"Apprenez de nouvelles compétences et enseigner aux autres","heading":"Tutoriels"},"info-section__icon-list_3":{"description":"Trouver des tutoriels et de discuter d'autres questions liées à la 3D","heading":"Forum"}},"covid":{"timer-strip__item":"Ressources gratuites imprimables 3D pour Covid-19 Protection","timer-strip__item--button":"Parcourir les modèles 3D"},"enterprise":{"info-section__cta":"Apprendre encore plus","info-section__heading_html":"Services 3D pour la \u003cstrong\u003e l'entreprise \u003c/strong\u003e","info-section__icon-list_1":"Nous pouvons faire la mise en oeuvre de AR pour vous","info-section__icon-list_2":"Plus de conversions de ventes, moins les retours de produits","info-section__icon-list_3":"Qualité assurée avec AQ strictes"},"header":"Modèles 3D pour VR / AR, impression 3D et infographie","marketplace":{"info-section__description":"Choisissez parmi %{models_count} modèles 3D pour des applications telles que les jeux, AR / VR, la publicité, l'animation, les films et l'impression 3D","info-section__heading_html":"Modèle 3D \u003cstrong\u003e marché \u003c/strong\u003e","info-section__image-list_1":{"description":"Acheter des modèles professionnels 3D pour votre projet","heading":"Modèles 3D"},"info-section__image-list_2":{"description":"Low-poly PBR Modèles 3D prêts pour les jeux vidéo, VR et AR applications en temps réel","heading":"Poly faible PBR Modèles 3D"},"info-section__image-list_3":{"description":"Trouver 3D imprimer des dessins pour votre imprimante 3D","heading":"Modèles d'impression 3D"},"view_all":"Voir tous"},"search_dropdown":{"ai_cards":{"image_to_3d":{"description":"Transformez une image en modèle 3D","title":"Image vers 3D"},"text_to_3d":{"description":"Décrivez votre idée pour créer un modèle 3D","title":"Texte vers 3D"}},"ai_section_title":"Créer avec l'IA","aria_label":"Suggestions de recherche","columns":{"cg":{"search_label":"Rechercher dans les modèles 3D","title":"Modèles 3D"},"print":{"search_label":"Rechercher dans l'impression 3D","title":"Modèles pour impression 3D"}}},"sell":{"info-section__heading_html":"\u003cstrong\u003e Vendre \u003c/strong\u003e 3D vos modèles et projets indépendants Trouver","info-section__icon-list_1":{"de
16518scription":"Vendez vos modèles 3D et gagnez jusqu'à 85% des redevances","link":"Vendre Modèles 3D"},"info-section__icon-list_2":{"description":"Travailler avec les clients de l'entreprise","link":"Obtenez qualifié"},"info-section__icon-list_3":{"description":"Externaliser vos compétences et obtenir jusqu'à 95% des redevances","link":"Parcourir Modélisation 3D Offres d'emploi"},"info-section__icon-list_4":{"description":"Construisez votre portefeuille et être visible","link":"Parcourir les concepteurs 3D"}},"seo":{"description":"Explorez plus de 2 millions de modèles 3D en ligne pour les projets d'impression, de jeu, VR/AR et de design. Téléchargez, personnalisez ou engagez des experts 3D - tout sur CGTrader.","title":"CGTrader - Modèles 3D et Marché d'Impression 3D"}}},"homepage":{"budget_counter":{"range_0_10":"Moins de 10 $","range_10_20":"10 $ - 20 $","range_20_15000":"Plus de 20 $"},"quotes":{"quote_0":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Baisser les commissions prélevées sur les vendeurs moyens meilleurs prix pour les acheteurs. Consultez et comparez les prix à CGTrader et les autres magasins."},"quote_1":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Le plus frappant avantage de CGTrader est le système d'activation des frais fixes qui nous donne un rendement élevé. Vous ne prenez pas un pourcentage de commission de notre chiffre d'affaires. Après la vente, nous recevons 100% du prix. Seulement si nous voulons continuer à vendre ce produit, nous pouvons réactiver à nouveau en payant 5 $. Solution élégante."},"quote_10":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"Le système d'activation de frais fixes est ingénieux. Je me demandais pourquoi personne ne l'avait pensé auparavant. Après la vente, payer une petite taxe pour activer un produit et c'est tout. Le vendeur est en plein contrôle."},"quote_11":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"Il y a deux choses que j'aime le plus CGTrader. Tout d'abord, il n'y a pas de sens caché à quoi que ce soit ici. Vous n'avez pas besoin d'être prudent avec CGTrader. En second lieu, ce magasin est par les artistes et les artistes. Ils comprennent mieux vous. Quelque chose vous tracasse ici? ... leur faire savoir et ils feront en sorte que vous vous sentez à l'aise ici."},"quote_12":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"système d'activation des frais fixes est le meilleur! (Je ne l'ai pas au moins sur quoi que ce soit mieux venir que cela). Il est très réaliste et équitable aux vendeurs. Ils ne se soucient pas beaucoup de profit que vous faites, juste payer le loyer fixe. Mais si vous ne faites des ventes, vous n'avez pas de ne pas payer le loyer soit. Le commerce équitable CG est ici!"},"quote_13":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"CGTrader attire les clients, même des pays en développement, parce que les prix sont moins chers que d'autres magasins. En même temps les auteurs peuvent gagner plus, parce qu'il n'y a pas une telle chose que des taux élevés de commission. Il est une bonne façon de valoriser le travail des artistes 3D."},"quote_14":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"Interface intuitive de l'éditeur et des outils innovants comme recommandation tag et formulaires de description flexibles rend le processus d'édition facile et rapide."},"quote_15":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"L'approche de CGTrader de vente 3D est différent et ont rien en commun avec d'autres magasins que je vendais. Payer une somme modique de 5 $ après la vente est une excellente idée et me permettent d'offrir des prix les plus bas ici. Client et l'artiste sont avantagés avec cette façon de travailler."},"quote_16":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"Le système de téléchargement est facile et claire. Il est bon de savoir que derrière le projet sont des personnes avec des années d'expérience en matière de graphiques informatique. Chaque fois que j'avais un problème, l'équipe CGTrader fixe rapidement."},"quote_17":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"CGTrader me permet de lier mon profil avec ma page web personnelle. Les clients peuvent me contacter, si elles ne sont pas certains au sujet d'une caractéristique d'un élément qu'ils veulent acheter ou tout simplement une modification de celui-ci. Il est comme posséder un magasin 3D personnel dans un grand marché!"},"quote_2":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Nous avons bénéficié d'un grand soutien et réactif de l'équipe CGTrader. Le personnel a gentiment proposé de faire la publication de tous les modèles pour nous, ce qui est d'une grande aide pour les vendeurs professionnels."},"quote_3":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Sur cette plate-forme nous pouvons offrir de meilleurs prix à nos clients. Par exemple, tous nos produits sont vendus 20% plus bas que partout ailleurs et nous obtenons encore plus de profits par rapport à d'autres magasins!"},"quote_4":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"Ce système est le meilleur à ce jour. Tous les autres magasins en ligne 3D prennent% de nos bénéfices. Ce fut la caractéristique qui a attiré notre intérêt pour CGTrader depuis le début. Les clients, l'achat sur CGTrader, sac
16518hez que la plupart de leur argent vont à l'auteur, qui à la fin fera des modèles plus et mieux si leurs bénéfices améliorer."},"quote_5":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"CGTrader est la boutique en ligne 3D qui a fait la plus forte croissance des ventes et des bénéfices pour nous. Le système d'activation des frais de plat est le meilleur que vous pouvez trouver, car il récompense les créateurs de contenu au lieu d'intermédiaires."},"quote_6":{"author":{"name":"Ričardas Marozas","username":"autopompe"},"text":"Quand j'ai reçu 160 $ ​​à mon compte Paypal, ne savait pas ce qui se passe car il n'a jamais eu lieu auparavant, mais je pensais que ce pourrait être une vente CGTrader et j'avais raison. Vendu le même pack sur Turbosquid le mois dernier pour 189 $, mais après 40% de taux de redevance et 30% de taxe, je ne ai obtenu 52,92 $ - quelques centimes par rapport à 160 $ ​​de CGTrader. De plus le client a payé 29 $ de moins - maintenant c'est une entreprise appropriée."},"quote_7":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"Au début, j'étais sceptique sur une bonne affaire pour les vendeurs qui ne sont pas la norme pour les grands magasins en ligne 3D. Quand j'appris sur le système d'activation des frais de plat, j'ai été impressionné par cette façon créative de la vente d'actifs numériques. Vendre, puis activer en payant des frais de 5 $. Tout sens pour moi. J'uploadé un peu pour essayer et vraiment, quand je suis arrivé ma première vente, il était de 100% de Royauté!"},"quote_8":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"Je suis tellement habitué à beaucoup d'autres sites qui charge entre 30% - 60% de commission jusqu'à ce que je découvre CGTrader. En tant que vendeur, je doute que le pourcentage élevé des rendements de redevances."},"quote_9":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"L'expérience du vendeur est fantastique. présentation des produits est un jeu d'enfant. Et oui, le système m'a permis de sauver un projet de ma soumission de produit chaque étape du chemin."}}},"hyperwallet":{"failed_payment_agreement":"Une erreur inattendue s’est produite","success_notification":"L’accord de paiement a été mis à jour avec succès.","success_payment_agreement":"L’accord de paiement a été soumis avec succès."},"item-search-filters":{"3d-models-url":"3d-modeles","3d-print-models-url":"3d-imprimer-modeles","dedicated-search-page-url":{"animated":"/animes-","free":"/libre-","low_poly":"/low-poly-","pbr":"/pbr-","rigged":"/gree-"}},"item_use_case":{"not_valid":"Fichier invalide","pundit":{"not_authorized":"Vous n'êtes pas autorisé à effectuer cette action."},"title":"Cas d'utilisation pour %{item_name}"},"items":{"already_purchased":"Vous avez déjà acheté cet article.","filters":{"attributes":"Les attributs","free":"Gratuit","max_value":"500 $ +","min_value":"2 $","reset":"Réinitialiser les filtres"},"footer":{"next":"Prochain","per_page":"Par page:","previous":"Précédent"},"geometry_type_forbidden":"Type de géométrie interdit !","last_card":{"button_text":"Demander un modèle 3D sur mesure","text":"Impossible de trouver ce que vous cherchez?"},"migrations":{"errors":{"invalid_owner":"Le nouveau propriétaire ne peut pas reprendre les articles.","unpublished_items":"Tous les articles doivent être publiés."}},"new_price_applied":"Les nouveaux prix ont été appliqués avec succès à tous les articles.","new_price_applied_to_selected_items":"Les nouveaux prix ont été appliqués avec succès aux articles sélectionnés.","new_prices_to_filtered_models":"Les nouveaux prix ont été appliqués avec succès à tous les articles filtrés.","order_processed":"Commande traitée.","order_processing":"Traitement de la commande.","something_went_wrong":"Une erreur s’est produite","title_error":"Le titre ne doit pas contenir de symboles non latins","uniq_user":"a déjà soumis un cas d’utilisation pour cet article","upload_error":"Erreur de téléversement.","validate_license_for_zero_price":"n’est pas valide pour un article gratuit","validate_minimum_price":"Les offres de prix ne doivent pas être inférieures à 50 % du prix initial","verify_printable_formats_error":"Veuillez téléverser au moins un fichier .stl, .obj ou .wrl.","you_can_not_delete_models":"Vous ne pouvez pas supprimer des modèles"},"jobs":{"about":{"hire_designers_page":{"hero":{"description":"3D Projects facilite l’embauche d’artistes 3D freelances qualifiés et vous aide à relever tous vos défis de modélisation.","heading_html":"Découvrez la meilleure plateforme de freelances pour vos \u003cstrong\u003emissions de modélisation 3D\u003c/strong\u003e"},"sections":{"call_to_action":{"description":"Publiez votre projet gratuitement et voyez à quel point il est facile de résoudre votre prochain défi de modélisation avec des Projets 3D.","title":"Engagez les meilleurs talents 3D dès aujourd’hui !"},"freelancer_selection":{"title":"Faites de votre prochain projet 3D un succès","widest_selection":{"description":"Ne vous contentez pas de moins. Avec 40 000 designers 3D freelances, CGTrader est la plus grande plateforme pour vous connecter à des spécialistes qualifiés : modeleurs, animateurs, riggers, pros de l’impression 3D et bien d’autres.","title":"La plus large sélection de freelances 3D"}},"how_it_works":{"steps":{"describe":{"description":"Dites-nous ce dont vous avez besoin. Utilisez notre formulaire simple, ajoutez des images et publiez votre offre freelance en quelques minutes.","title":"Décrivez votre projet"},"pay":{"description":"Payez le budget à l’avance en toute sécurité à CGTrader et faites savoir à votre freelance que vous êtes prêt à commencer. Discutez, partagez des pièces jointes et suivez l’avancement du projet facilement dans votre espace de travail. Libérez le paiement lorsque vous êtes satisfait du résultat final.","title":"Payez pour le résultat"},"select":{"bullets":["Recevez des devis de designers qualifiés directement dans votre boîte de réception","Discutez en temps réel avec les designers","Parcourez leurs projets précédents","Vous pouvez également rechercher, filtrer et inv
16518iter des freelances selon leurs compétences 3D, évaluations et préférences logicielles ou de format"],"title":"Sélectionnez le meilleur freelance"}},"title":"Réalisez votre travail en 3 étapes simples"},"payment_quality":{"description":"CGTrader garantit que les services freelances répondront à vos exigences et seront livrés à temps. Notre équipe QA est disponible à tout moment.","title":"Qualité garantie"},"secure_payments":{"description":"Notre service d’entiercement signifie que vos fonds sont protégés et que vous ne payez que lorsque vous êtes satisfait du résultat final.","title":"Paiements sécurisés"},"workspace_convenience":{"description":"Oubliez la frustration de devoir passer d’un onglet à l’autre et entre différents outils. Avec l’espace de travail dédié, vous pouvez facilement tout gérer en un seul endroit : plusieurs projets, factures, conversations, partage de fichiers et plus encore.","title":"Commodité sur un seul écran"}},"shared":{"post_project_cta":"Publier un projet"}},"navigation":{"find_jobs_cta":"Trouver des emplois 3D","hire_freelancers_cta":"Engager des freelances 3D","how_it_works":"Comment ça marche","why_projects":"Pourquoi Projets 3D"}},"agreed_to_terms_of_use":"Vous devez accepter les Termes et Conditions de 3D Projects pour continuer.","budget_error_message":"est trop élevé","check_designer_uniqueness_error":"Le concepteur ne peut être invité qu’une seule fois","check_ownership_error":"L’invitant n’est pas propriétaire du projet","check_status_error":"Les concepteurs ne peuvent être invités qu’à des projets approuvés.","deadline_error_message":"La date limite doit être dans le futur","discount_code_error":"Désolé, le code de réduction n’existe pas ou a expiré.","email_subject":"Opportunité de projet 3D : %{subject}","error_check_application_uniqueness":"Vous ne pouvez postuler qu’une seule fois","error_check_applier":"Vous ne pouvez pas postuler à votre propre projet 3D","error_check_job_availability":"L’utilisateur n’est pas autorisé à postuler","error_check_job_status":"Les candidatures aux projets sont en attente jusqu’à ce que notre nouveau processus de modération soit terminé, généralement sous 3 jours ouvrables. Merci de votre compréhension !","modeling_request_failed":"La demande de modélisation a échoué !","offer_has_been_sent":"Merci, votre offre a été envoyée !","offer_is_closed":"L’offre est clôturée.","payment_successful":"Paiement effectué avec succès.","payment_under_review":"Le paiement est en cours de révision.","share_with_yourself_error":"Le partage de modèles « Hire Me » avec vous-même n’est pas autorisé","system_messages":{"buyer_cancels_job":"Le projet a été annulé et déplacé vers l’onglet %{archive_url}.","buyer_edits_hire_me_template":"Les détails du projet ont été mis à jour par le client.","buyer_edits_job_details":"Les détails du projet ont été mis à jour.","buyer_marks_job_as_finished":"Le projet a été marqué comme terminé et déplacé vers l’onglet %{archive_url}.","buyer_marks_job_as_finished_with_resell":"Le client a marqué le projet comme terminé et vous a autorisé à vendre les résultats sur CGTrader. Assurez-vous de \u003cb\u003e\u003ca href=\"/profile/upload/model\"\u003etéléverser\u003c/a\u003e\u003c/b\u003e le modèle créé !","buyer_negotiates":"Le client a négocié votre offre de prix.","buyer_pays_for_the_job":"Le projet a été prépayé et déplacé vers l’onglet %{in_progress_url}.","designer_accepts_changes":"Le concepteur a examiné les détails mis à jour.","designer_accepts_offer":"Le concepteur a accepté votre offre.","designer_edits_offer":"Le concepteur a modifié son offre.","designer_negotiates":"Le concepteur a négocié votre offre.","designer_withdraws_offer":"Le concepteur a retiré son offre.","job_expires":"Le projet a expiré et a été déplacé vers l’onglet %{archive_url}."},"template_error":"Aucun projet n’a été créé !","user_not_exists":"L’utilisateur avec ce nom d’utilisateur n’existe pas.","want_that_job":"Je veux ce travail !"}
16518,"kodama":{"invalid_request":"Requête invalide !"},"landing":{"consent_not_saved":"Le consentement n’a pas été enregistré","failure_notification":"Tous les champs obligatoires de la demande d’accès à l’API doivent être valides.","success_notification":"Merci pour votre demande. Nous vous contacterons sous peu."},"layout":{"chunk_load_notice":"Une erreur s'est produite lors du chargement de cette page. Veuillez l'actualiser.","cookies_policy":{"accept":"J'accepte","learn_more":"Apprendre encore plus","text":"Notre site Web utilise des cookies pour recueillir des données statistiques des visiteurs et l'interaction de piste avec la communication de marketing direct / améliorer notre site Web et d'améliorer votre expérience de navigation. S'il vous plaît voir notre avis Cookie pour plus d'informations sur les cookies, les données qu'ils recueillent, qui peuvent y avoir accès, et vos droits."},"translations_notice":{"cta":"Basculer vers English","text":"Salut! Nous traduisons CGTrader. Les traductions sont en cours et certaines sections pourraient ne pas être disponibles en français."}},"model":{"already_purchased":"Vous avez déjà acheté ce produit. Vous pouvez le télécharger depuis votre \u003ca href='/profile/cart/purchased'\u003epanier\u003c/a\u003e. Si vous le souhaitez, vous pouvez l'acheter à nouveau.","animated":"Animé","artist_profile":"Profil de l'artiste","author":"Auteur","author_get_your_payment":"L'auteur recevra 100% de votre paiement. CGTrader permet aux artistes de maximiser leurs revenus et les motive à vendre du contenu de haute qualité.","author_products":"Plus de produits par \u003ca href=\"%{author_url}\"\u003e%{username}\u003c/a\u003e","authorised_seller":"Vendeur autorisé CGTrader","authority":"Autorité","breadcrumb":{"3d_models":"Modèles 3D","home":"Accueil"},"bulk_download":{"download_all":"Télécharger tous les fichiers","files_included":{"one":"%{count} fichier inclus","other":"%{count} fichiers inclus"},"too_large":"Ce modèle est trop volumineux pour être téléchargé en une seule archive (plus de %{limit}). Veuillez télécharger les fichiers individuellement."},"buy":"Acheter","cannot_download":"Seuls les utilisateurs ayant %{experience_needed} points de réputation peuvent télécharger plus de %{models} modèles gratuits. En savoir plus sur les niveaux et la réputation en \u003ca href='%{reputation_url}' target='_blank'\u003ecliquant ici\u003c/a\u003e.","collection":"pack 3D","contact_author":"Contacter l'auteur","description":"Description","download":"Télécharger","download_limit_exceeded":"Vous avez dépassé votre limite quotidienne de téléchargements. Veuillez réessayer demain. Cette limite ne s'applique pas aux utilisateurs ayant effectué un achat au cours des 12 derniers mois.","download_modal":{"chat_with_us":"Discuter avec nous","contact_support":"Veuillez nous contacter via le chat.","download_file":"Télécharger %{name}","empty":"Aucun fichier trouvé","generic_error":"Une erreur est survenue, veuillez réessayer","link_error":"Nous sommes désolés, mais votre lien de téléchargement semble présenter un léger problème.","pbr_ready":"Compatible PBR","preparing":"Préparation de votre téléchargement...","title":"Télécharger les fichiers"},"format_downloads":"Téléchargements de formats 3D","game_ready":"Prêt pour le jeu (low-poly)","geometry":"Géométrie","license":"Licence","make_offer":"Faire une offre","materials":"Matériaux","messages":{"no_files":"Aucun fichier téléchargé","no_rating":"Pas encore d'évaluation disponible pour ce modèle"},"model_details":"Détails du modèle 3D","pbr_textures":"Textures PBR","plugin_used":"Plugin utilisé","polygons":"Polygones","price":"Prix","print_ready":"Prêt pour l'impression 3D","product_id":"ID du produit","product_support":"Support produit","product_type":"Type de produit","published":"Publié le","rated":{"error":"Vous avez déjà évalué ce modèle","success":"Merci d'avoir évalué ce modèle"},"rating":"Évaluation du modèle 3D","ratings":{"desc":{"negative":"Les utilisateurs ayant acheté ce modèle 3D le considèrent de qualité inférieure aux attentes","positive":"Les utilisateurs ayant acheté ce modèle 3D le considèrent de bonne qualité"},"negative":"négatif","positive":"positif"},"recently_viewed_models":"Modèles récemment consultés","renderer":"Moteur de rendu","report":"Signaler","rigged":"Rigged","scripts-plugins":"Scripts et Plugins","similar_models":"Modèles similaires","statistics":"Statistiques","subscription":{"limit_reached":"Vous avez atteint la limite de téléchargements pour cette période de facturation.","limit_reached_detailed":"Vous avez atteint votre limite de %{limit} téléchargements pour ce mois. Votre limite est réinitialisée le %{reset_date}. Pour télécharger plus de modèles maintenant, vous pouvez les acheter individuellement.","limit_reached_detailed_no_date":"Vous avez atteint votre limite de %{limit} téléchargements pour ce mois. Pour télécharger plus de modèles maintenant, vous pouvez les acheter individuellement."},"tags":"Tags","textures":"Textures","title":"%{item_title} Modèle 3D","unwrapped_uvs":"UVs dépliées","uv_mapping":"Cartographie UV","vat_notice":"Tou
16518s les prix sont hors TVA","version":"Version","vertices":"Sommets","vr":"Modèles VR"},"models":{"error":{"cannot_be_blank":" ne peut pas être vide."}},"navigation":{"3d-models":{"3d-printing":"Fichiers d'impression 3D","discounts":"Remises","find_designers":"Trouvez les meilleurs concepteurs","free":"Modèles 3D gratuits","pbr":"Modèles 3D PBR","scanned_3d-models":"Modèles 3D scannés","scripts_apps":"Scripts \u0026 applications","show-all":"Afficher tout","textures":"Textures CG","title":"Modèles 3D","vr_low-poly":"Modèles 3D low-poly"},"already_added_message":"a déjà été ajouté au panier","already_purchased_message":"Vous avez déjà acheté cet article.","aria":{"account":"Compte","cart":"Panier","close_menu":"Fermer le menu","logo":"CGTrader - Modèles 3D / Concepteurs 3D","messages":"Messages","notifications":"Notifications","open_menu":"Ouvrir le menu","search":"Rechercher","search_by_image":"Recherche par image"},"budgets":{"below_10":"Moins de 10$","collections":"Packs","free":"Gratuit","more_than_20":"Plus de 20$","more_than_50":"Plus de 50$","sale":"En vente","subscription":"Abonnement"},"cart_limit_reached":"Vous avez atteint la limite de 500 articles dans votre panier. Pour continuer vos achats, veuillez compléter votre commande actuelle et en commencer une nouvelle pour tout article supplémentaire.","cart_widget":{"continue_shopping":"Continuer les achats","discount":"Remise avec coupon de l'entreprise: ","discount_applied":"Remises appliquées :","final":"Prix ​​final: ","have":"Vous avez","items":" articles dans votre panier.","left_to_unlock_discounts":"Encore {{leftToUnlock}} pour débloquer les remises","potential_discount":"Remise potentielle :","proceed":"Procédez au paiement","recently_viewed_empty_text":"Commencez à explorer et vos articles récemment consultés apparaîtront ici.","recently_viewed_empty_title":"Rien ici pour l'instant","recently_viewed_tab":"Récemment consultés","remainer":"Pour activer les points de CGTrader, ajoutez: ","sale_discounts":"Remises appliquées: ","sale_time_left":"La vente se termine dans","sale_tooltip":"Les remises de nos campagnes de soldes ne s'appliquent que lorsque votre panier atteint ${{threshold}} ou plus.","sale_tooltip_text":"Cet article est en promotion, mais la réduction ne sera appliquée que lorsque le sous-total de votre panier atteindra ${{threshold}} ou plus.","text":"Votre panier est vide.","title":"Panier","total":"Total: ","used":"Crédits utilisés: ","view":" Voir le panier","wishlist_empty_text":"Commencez à explorer et enregistrez vos favoris pour plus tard — ils apparaîtront ici.","wishlist_empty_title":"Votre liste de souhaits est vide","wishlist_tab":"Liste de souhaits"},"community":{"awards":"Prix des Designers 🏆","blog":"Blog","challenges":"Défis","community_ratings":"Évaluations de la communauté","designers":"Designers 3D","digital_art":"Concours d'art numérique ","forum":"Forum","galleries":"Galeries","invite":"Invitez un ami","printlist":"Liste d'impression 3D","sell_3d-models":"Vendre des modèles 3D","title":"Communauté","tutorials":"Tutoriels"},"conversations_widget":{"conversations":"conversations","messages":{"loading":"boîte de réception de chargement"},"types":{"cgtrader_support":"Assistance CGTrader ","file_conversion_request":"demande de conversion de fichier","job_application":"projet 3D","model_feedback":"Commentaires sur le produit","model_support":"Modèle de support","offer":"Offre de prix","personal":"Message personnel","promotions":"promotions"}},"create_ai":{"community_gallery":"Galerie de la communauté","free_trial":"Essai gratuit","generate_3d_model":"Générer un modèle 3D","generation":{"dismiss":"Ignorer la notification","error_title":"Problème de génération","generating_title":"Votre modèle 3D est en cours de génération","open_model":"Ouvrir le modèle","ready_title":"Votre modèle 3D est prêt !"}},"create_with_ai":"Créer avec IA","credits_guest_widget":{"create":"Créez un compte maintenant!","discount":" remise","for":" pour votre ","get":" et obtenez jusqu'à ","learn_more":"En savoir plus sur les crédits de fidélité de CGTrader","link":"ici","purchase":"Premier ach
16518at !","sign-up":"S'inscrire","text_below_1":"Les crédits peuvent être utilisés lors de l'achat \"","text_below_2":"\" validez les modèles marqués et obtenez jusqu'à 30% de réduction du prix."},"credits_user_widget":{"condition_1":"Les points CGTrader peuvent être utilisés pour l achat de modèles marqués du signe \u003ci class=\"custom-font-icon custom-font-icon--small icon-wallet\"\u003e\u003c/i\u003e, offrant jusqu à 30% de reduction sur leur prix.","condition_2":"Les points ne peuvent être utilisés que si la valeur totale de votre panier est de \u003cstrong\u003e$%{minimum}\u003c/strong\u003e ou plus.","condition_3":"Pendant les ventes ou promotions actives, les points ne peuvent pas être utilisés.","condition_4":"Gardez à l esprit que les points ont une date d expiration - vos points expireront dans \u003cstrong\u003e%{days}\u003c/strong\u003e jours.","earn_rate":"Taux d'obtenrion de crédit ","first_text_1":"Considérez-le un cadeau pour votre premier achat - vous pouvez utiliser des crédits lors d'achats \"","first_text_2":" \" validez les modèles marqués et obtenez jusqu'à 30% de réduction du prix. Les crédits ne peuvent être utilisés que si la valeur totale de votre panier est égal ou supérieur à","important":"IMPORTANT:","learn_more":"En savoir plus","lifetime_spending":"Les dépenses de toute une vie: ","list_item":"En augmentant vos dépenses de toute une vie sur CGTrader, votre taux d'obtention de crédit augmente également.","message":" Les crédits ne peuvent être utilisés lors des campagnes de vente, mais vous pouvez toujours augmenter votre niveau de royalties et recueillir plus de crédits pour votre prochain achat.","more_credits":"Comment puis-je obtenir plus de crédits?","next_level":"Niveau suivant: ","notice":"REMARQUE: Les points CGTrader ne peuvent pas être utilisés pendant la vente en cours, mais ils seront à nouveau disponibles une fois la vente terminée. En attendant, découvrez d'excellentes offres sur les modèles 3D \u003ca href=\"https://www.cgtrader.com/3d-models?sale_off=1\"\u003eici\u003c/a\u003e","par_1":"Vous avez","par_2":" crédits CGTrader.","par_2_variation":" en Points que vous pouvez dépenser lorsqu'aucune promotion n'est disponible.","regular_text_1":"Les crédits peuvent être utilisés lors d'achats \"","regular_text_2":" \" validez les modèles marqués et obtenez jusqu'à 30% de réduction du prix.","title":" rabais de fidélité de CG Trader"},"enterprise":{"management":"Gestion des ressources 3D","title":"Production de contenu 3D"},"hire_a_freelancer":"Engager freelance","item_add_message":"a été ajouté avec succès au panier","item_restricted_message":"Cet article ne peut pas être acheté en raison des sanctions imposées au pays du concepteur (RU/BY).","navbar":{"actions":{"new":"Nouveau","top_selling":"Meilleures ventes","trending":"Tendances"},"budgets":{"below_10":"Moins de 10$","free":"Gratuit","more_than_20":"Plus de 20$","more_than_50":"Plus de 50$","sale":"En vente","up_to_10":"Jusqu'à 10$"}},"projects":{"3d_customs":"3D personnalisé","3d_printing":"Impression 3D","about":"A propos des projets 3D","browse":"Explorez les projets ","buyer_perspective":"Recruter des concepteurs 3D","for_designers":"Pour les designers","my_projects":"Mes projets","seller_perspective":"Projets 3D en free-lance","start":"Démarrez un projet","work":"Travaillez avec CGTrader"},"sell_3d-models":"Vendre des modèles 3D","sellers_hub":"Espace vendeurs","subscription_includes_item_message":"Cet article est déjà inclus dans votre abonnement.","tooltips":{"cart":"Panier","conversations":"Mes conversations","discounts":" rabais de fidélité de CG Trader","notifications":"notifications","publisher":"Éditeur"},"user":{"ban_notification":"Ce compte utilisateur est suspendu. Veuillez contacter notre support pour obtenir des explications.","cannot_refer_yourself":"vous ne pouvez pas vous parrainer vous-même","email_confirmation":"Votre adresse e-mail a été vérifiée.","email_reconfirmation":"était déjà confirmée, veuillez essayer de vous connecter","ends_at":"Se termine le : %{date}
16518","handle_error_tfa_code_missmatch":"Le code de sécurité que vous avez saisi est incorrect. Veuillez réessayer.","handle_error_tfa_code_missmatch_last":"Votre code de sécurité a expiré. Pour continuer, cliquez sur renvoyer l’e-mail et saisissez le nouveau code fourni.","handle_error_tfa_default":"Une erreur s’est produite, veuillez réessayer plus tard.","handle_error_tfa_expired":"Votre code de sécurité a expiré.","hundred_percent_royalties_popover":"Vous participez à une campagne temporaire de gains à 100 % : tous les revenus des ventes de nouveaux modèles vous seront versés jusqu’à la fin de la campagne","invalid_email_message":"Veuillez entrer une adresse e-mail valide pour continuer.","login":"Se connecter","meta_description":"Découvrez le profil de concepteur 3D %{username} sur CGTrader, son portfolio et ses modèles 3D disponibles. Consultez les meilleurs modèles 3D des artistes de modélisation les plus talentueux de l’industrie.","meta_title_suffix":"Concepteur 3D","not_valid_birthday":"La date de naissance n’est pas valide.","offline":"Hors ligne","online":"En ligne","page_title":"Profil du concepteur","rate_models_limit":"Vous ne pouvez plus noter de modèles aujourd’hui.","semantic_description":"Modèles 3D, œuvres et portfolio du concepteur. Découvrez ses créations sur CGTrader","semantic_title":"Profil du concepteur","session_failure":"Nom d’utilisateur/e-mail ou mot de passe incorrect","session_password_expired":"Votre mot de passe a expiré en raison d’une inactivité. Veuillez le réinitialiser via le lien envoyé par e-mail.","session_two_fa_failure":"La vérification du code a échoué","session_unconfirmed":"Votre adresse e-mail n’a pas encore été confirmée.","token_not_recognized":"Le jeton n’a pas été reconnu","twitter_shareable_text":"Un #3dartist à suivre absolument","username_error":"ne doit pas contenir d’espaces ni de symboles non latins"},"user_menu":{"buyer":{"freelancers":"Trouvez des freelance ","purchases":"Mes achats","sell":"Vendre des modèles 3D","wishlist":" Liste de souhait de modèles 3D"},"dashboard":"Tableau de bord admin","logout":"Se déconnecter","public_profile":"Profil public","seller":{"analytics":"Analytiques","find_new_projects":"Trouver de nouveaux projets 3D","freelance":"Projets 3D Freelance","freelance_cgtrader":"Travailler en freelance avec CGTrader","models":"Mes modèles","publish_project":"Publier un projet","publishing":"Édition","sales":"Mes ventes","subscription":"Abonnement","wishlist":" Liste de souhait de modèles 3D"},"settings":"Paramètres"}},"owly":{"failed_set_pending":"Échec de la mise en attente.","failed_to_approve":"Échec de l’approbation.","failed_to_suspend":"Échec de la suspension.","failed_to_update_license":"Échec de la mise à jour de la licence.","failed_to_update_status":"Échec de la mise à jour du statut d’approbation."},"print_models":{"browse_all_makes":"Parcourir toutes les créations 3D","browse_all_models":"Parcourir tous les modèles","description":"Découvrez des modèles 3D prêts à imprimer, des ressources imprimables et des fichiers STL pour les jeux, l’art, les projets domestiques et plus encore.","makes_description":"Explorez les dernières créations 3D téléchargées par la communauté – des exemples réels montrant à quoi ressemblent les modèles 3D dans le monde physique.","makes_title":"Nouvelles créations 3D","new_description":"Découvrez les dernières impressions 3D ajoutées à notre collection – des designs frais prêts à télécharger et à imprimer pour votre prochain projet.","new_title":"Nouveaux modèles 3D à imprimer","title":"Modèles 3D d’impression tendance"},"profile":{"account_settings":{"account":{"email":"Email","submit":"Enregistrer","title":"Profil","update":{"updated":"Profil mis à jour"},"username":"Nom d'utilisateur"},"email_settings":{"descriptions":{"ai_studio_notifications":"Notifications concernant vos générations CGTrader AI, y compris lorsqu'un modèle que vous n'avez pas encore ouvert est prêt","communication":"Informations sur votre communication avec d'autres utilisateurs de CGTrader, y compris les messages personnels, les nouvelles offres de prix et de travail personnalisé, les retours et les demandes de support produit","news":"Informations sur les produits CGTrader, les annonces de nouvelles fonctionnalités et les actualités de la communauté","onboarding_and_edu":"Informations sur votre processus d'intégration et des conseils pour tirer le meilleur parti de CGTrader","push_notifications":"Notifications concernant les activités et mises à jour importantes du compte","sales_and_offers":"Informations sur les offres spéciales, les ventes de modèles 3D et les crédits bonus CGTrader","seller_sold_item":"Informations sur vos modèles 3D vendus et les paiements reçus"},"privacy_policy_html":"Veuillez lire notre \u003ca href=''https://www.cgtrader.com/pages/privacy-policy'' ''target=''_blank''\u003ePolitique de Confidentialité\u003c/a\u003e pour plus d'informations. ","titles":{"ai_studio_notifications":"Notifications CGTrader AI","communication":"Communication (Messages personnels, retours sur modèles 3D, activité communautaire, offres)","news":"Actualités 3D / Mises à jour CGTrader","onboarding_and_edu":"Intégration \u0026 Éducation","push_notifications":"Notifications Push","sales_and_offers":"Ventes \u0026 offres spéciales","seller_sold_item":"Informations sur les Ventes \u0026 Paiements"},"tracking_attributes":{"ai_studio_notifications":"Notifications CGTrader AI","communication":"Communication","news":"Actualités 3D / Mises à jour CGTrader","onboarding_and_edu":"Intégration \u0026 Éducation","sales_and_offers":"Ventes \u0026 offres spéciales","seller_sold_item":"Informations sur les Ventes \u0026 Paiements"},"update":{"updated":"Votre email a été mis à jour avec succès."}},"gdpr_success_notification":{"opt_in":"Merci ! Vous vous êtes réabonné avec succès à nos emails.","opt_out":"Vous vous êtes désabonné avec succès de nos newsletters. Vous pouvez modifier cette préférence à tout moment en visitant les paramètres de votre profil.\n"},"location_tooltip_text":"\u003cdiv\u003eCette case à cocher permet d'afficher l'emplacement sur la page de profil.\u003c/div\u003e \u003cdiv\u003eNotez que, dans tous les cas, pour des raisons de conformité, votre emplacement sera affiché à vos acheteurs (si vous êtes un vendeur) et aux vendeurs auprès desquels vous avez acheté.\u003c/div\u003e","moneybookers":{"account":"Compte Skrill"},"password":{"current_password":"Mot de passe actuel","new_password":"Nouveau mot de passe","submit":"Changer le Mot de Passe","title":"Mot de Passe","updated":"Votre mot de passe a été mis à jour avec succès. Vous pouvez maintenant utiliser votre nouveau mot de passe pour vous connecter.","verify_password":"Vérifier le mot de passe"},"paypal":{"account":"Compte PayPal","submit":"Enregistrer","title":"Paramètres PayPal","update":{"updated":"Vos paramètres PayPal ont été mis à jour"}},"personal_info":{"areas_of_expertise":"Domaines d'expertise","country":"Pays","description":"Quelques phrases sur vous","full_name":"Nom complet","languages":"Langues","preffered_tools":"Outils Préférés","submit":"Enregistrer","title":"Informations personnelles et professionnelles","update":{"updated":"Vos informations ont été mises à jour avec succès"},"website":"Site Web"},"personalization":{"no_muted_title":"Aucun designer muet","title":"Designers Muets","tooltip":"Les modèles 3D des designers listés sont exclus de vos résultats de recherche.","unmute_designer":{"confirm":"Êtes-vous sûr ?"}},"professional_info":{"allow_private_job_requests":"Les acheteurs pourront vous envoyer des demandes de travail 3D personnalisées.","buyer_engagement_program_html":"Attirez plus de clients en leur permettant d'acheter vos modèles avec les Points CGTrader. \u003ca href=''https://www.cgtrader.com/pages/cgtrader-loyalty-discounts-explained'' target=''_blank''\u003ePlus d'informations\u003c/a\u003e\n","pbr_program":"Le taux de gain inférieur est appliqué uniquement lorsque le fichier 
16518converti est téléchargé","pbr_program_agreement":"Vous avez accepté de participer au programme PBR pour 24 mois à partir du %{date}. Consultez les Termes \u0026 Conditions pour plus de détails. Veuillez contacter notre support si vous avez des questions.","product_analytics_participation_html":"Je participe à \u003ca href=''https://www.cgtrader.com/profile/analytics''target=''_blank''\u003eAnalytics\u003c/a\u003e et j'accepte leurs Termes et Conditions\n","product_analytics_participation_tooltip_html":"Vous avez accepté de participer à Analytics et accepté les Termes et Conditions d'Analytics (\u003ca href=''https://www.cgtrader.com/pages/cgtrader-analytics-terms-of-service''target=''_blank''\u003elien\u003c/a\u003e) le %{accepted_at}. Si vous souhaitez mettre fin à votre participation, veuillez contacter le support CGTrader\n"},"security":{"allow_mfa":"Activer la vérification en 2 étapes pour protéger votre compte","use_authenticator_app":"Utiliser une application d'authentification pour la vérification en 2 étapes","use_authenticator_app_tooltip":"Scannez le code QR avec votre application d'authentification (Google Authenticator / Authy) pour générer des codes de vérification.","use_email":"Utiliser l'email pour la vérification en 2 étapes"},"sidebar":{"general_settigns":"Paramètres Généraux","member_since":"Membre Depuis","paypal":"PayPal","title":"Infos du Compte"},"title":"Mon Compte"},"conversations":{"conversations":"conversations","messages":{"loading":"boîte de réception de chargement"},"types":{"cgtrader_support":"CGTrader soutien","file_conversion_request":"demande de conversion de fichier","job_application":"projet 3D","model_feedback":"Commentaires sur le produit","model_support":"modèle de soutien","offer":"Offre de prix","personal":"Message personnel","promotions":"promotions"}},"meta":{"description":"Découvrez des modèles 3D de haute qualité réalisés par %{username} sur CGTrader. Parcourez des assets uniques pour jeux, VR/AR et visualisation.","title":"Créateur·rice de modèles 3D : %{username}"},"my_products":{"suspension_appeal":{"reasons":{"not_available":"N/A"}}},"settings_updated":"Vos paramètres ont été mis à jour avec succès."},"publishing":{"banned_error":"Votre compte a été banni pour publication de contenu spam. Si vous pensez qu'il s'agit d'une erreur, veuillez contacter [email protected].","files_not_finalized":"Un ou plusieurs fichiers n''ont pas terminé leur téléversement et ne peuvent pas être publiés. Veuillez les supprimer et les téléverser à nouveau avant de publier.","spam_suspect":"Votre compte n'est pas autorisé à publier ce modèle. Si vous pensez qu'il s'agit d'une erreur, veuillez contacter [email protected]."},"rating":{"anonymous_buyer":"Acheteur Anonyme","errors":{"not_verified_buyer":"Seuls les acheteurs vérifiés peuvent laisser un avis"}},"routes":{"3d-models":"3d-modèles","3d-print-models":"3d-imprimer-modèles","animated-3d-models":"animés-3d-modèles","controllers":{"three_d_studio":{"3d-models":"3d-models","images":"images"}},"discover":"decouvrir","free-3d-models":"libre-3d-modèles","free-3d-print-models":"libre-3d-imprimer-modèles","low-poly-3d-models":"low-poly-3d-modèles","pbr-3d-models":"pbr-3d-modèles","pbr-textures":"pbr-textures","rigged-3d-models":"gréé-3d-modèles","search":"search"},"sale_off":{"no_overlapping_campaigns":"Les dates de la campagne se chevauchent avec une autre campagne de soldes existante.","parse_discount_rules_text":"La ligne #{index} ne contient pas le paramètre requis 'discount:'"},"search":{"not_found":"Abonnement de recherche introuvable","subscription_created":"Abonnement de recherche créé","successfully_unsubscribed":"Vous vous êtes désabonné avec succès de l’abonnement de recherche"},"subscription_opt_ins":{"lock_period_error":"Some items cannot be opted out before the six-month lock period ends.","missing_items_error":"Items not found: %{items}."},"subscriptions":{"flash":{"active_subscription":"Vous avez déjà un abonnement actif.","premium_upgrade_unavailable":"Le passage à Premium est temporairement indisponible. Veuillez réessayer plus tard.","update_payment_method":"Votre abonnement rencontre un problème de paiement. Veuillez mettre à jour votre moyen de paiement au lieu de souscrire un nouvel abonnement."},"payment_form":{"current_price":"%{price}","features":{"first":"%{downloads} ressources / mois — téléchargez des modèles ou générez avec l'IA","second":"Accès instantané à plus de %{models_count} modèles 3D essentiels","third":"Libre de droits, sans IA"},"from_price_label":"à partir de","original_price":24.99,"per_model_label":"par modèle","plan_name":"Abonnement CGTrader Essentials — plan annuel, facturé mensuellement","premium":{"features":{"first":"%{downloads} ressources / mois — téléchargez des modèles ou générez avec l'IA","fourth":"Libre de droits, sans IA","second":"Accès instantané à plus de %{models_count} modèles accessibles au total","third":"Modèles de niveau professionnel non inclus dans Essentials"},"plan_name":"Abonnement CGTrader Premium — plan annuel, facturé mensuellement"},"price_unit":"/mois","vat_amount":"%{vat_amount}"}},"tag":{"description":{"bottle":"Les scientifiques disent que la bouteille de vin par jour est pas mal pour vous, mais quand il s'agit de boissons fortes comme le whisky ou bourbon, nous devrions vraiment regarder notre c
16518onsommation d'alcool. De toute évidence, un verre de champagne au bar ressemble refroidisseur façon qu'une bouteille d'eau en plastique, mais pensez à la possibilité de vivre une vie et de la bière plus semblera comme un sacrifice raisonnable. Cette catégorie offre une large sélection de bouteilles modèles 3D, allant des objets liés à l'alcool comme porte-vin et tire-bouchons à tous les types de bouteilles de modèles 3D (de shampooing à l'huile d'olive).","brain":"Le cerveau est l'un des plus grands et les organes les plus complexes du corps humain. En raison de sa structure complexe et la fonction, l'anatomie du cerveau est également très complexe. Cependant, les scientifiques creusent plus profondément dans l'architecture du cerveau humain, découvrant des secrets ahurissants. Une large gamme de cerveau réalistes et détaillées des modèles 3D qui sont prêts pour les applications graphiques informatiques comme la publicité, la visualisation 3D, jeux 3D, web et tout autre domaine lié à la conception 3D est disponible en téléchargement.","building":"On ne sait pas où les architectes modernes prennent leur inspiration, mais quelques-uns des célèbres bâtiments du monde réduisent si bord qu'ils ressemblent à des visions de l'avenir. Et nous ne voulons pas dire que la partie extérieure ... Dans les mains capables d'une construction de bâtiment standard devient une création architecturale à couper le souffle avec un design intérieur innovant, peu importe que ce soit un bureau ou un bâtiment urbain. Notre sélection de construire des modèles 3D est prêt pour le téléchargement et peut être appliquée dans divers domaines - animation 3D, design graphique, publicité, jeux 3D ou web.","camera":"Photographier et filmer est actuellement l'un des domaines les plus populaires de carrière chez les jeunes, mais pas tout le monde avec un appareil photo professionnel est un artiste ou un photographe. Avec la qualité des caméras de téléphone améliore d'année en année, nous voyons des gens qui prennent des photos à chaque étape et ceux qui ont l'image nouvellement développé des compétences d'édition sont vraiment célèbre. La vie a l'air un peu différent quand vous êtes de l'autre côté de la lentille, mais nous allons pas se laisser emporter ... Une large gamme de caméra détaillée des modèles 3D est disponible en téléchargement à utiliser par des graphistes, des animations et des développeurs de jeux et les professionnels qui travaillent dans d'autres domaines liés à la 3D.","car":"En regardant ces modèles de voitures 3D très détaillées et très réalistes, vous pouvez presque entendre le bruit du moteur! Une large gamme de véhicules modèles 3D peut rendre votre vie beaucoup plus facile, si vous êtes aux prises avec l'infographie, l'animation ou tout autre domaine connexe 3D.","cell":"Les scientifiques ont estimé que le corps humain contient de 5 milliards de cellules de 200 millions de billions de dollars. Les plantes et les animaux ont des cellules aussi. Nous ne voulons pas vous ennuyer avec ce genre de choses de la biologie, mais la vaste gamme de modèles cellulaires 3D que nous avons couvre tout du sang, le virus et de l'ADN à la moelle osseuse et les parois cellulaires. Est-ce pas ... énervant?","chair":"Nous prenons assis sur des chaises pour acquis et ne remarquent pas toujours combien le charme qu'ils peuvent ajouter à notre maison ou à l'intérieur bureau. Président est en fait l'une des pièces maîtresses de meubles dans tous les ménages. Vous trouverez ci-chaise design de haute qualité et accrocheur modèles 3D qui peuvent être appliqués dans différents domaines - de la publicité et de visualisation 3D pour des jeux en 3D.","city":"la vie de la ville est un jeu excentrique: rues débordant de taxis de course, des bâtiments modernes, des piétons dans les vêtements de bureau parfaitement adaptés, des ponts de couchage et des parcs ... De Shanghai, à New York (! Manhattan), à Londres ou à Paris - chaque ville a son propre magie. Vous pouvez choisir parmi cette riche sélection de ville détaillée des modèles 3D qui peuvent être utilisés dans un certain 
16518nombre de domaines - la publicité, l'infographie, l'animation, la visualisation 3D ou des jeux. Découvrez la vie urbaine.","dragon":"Dragon est probablement la créature la plus populaire vivant dans le monde fantastique. Il a figuré dans tant de mythes de différentes cultures, des animations, des films et des livres qu'il est devenu légendaire. Il est particulièrement apprécié par les développeurs de jeux, car il ajoute que le charme magique des grottes sombres et donjons. Une large gamme de réaliste, truqué de dragon modèles 3D qui sont prêts pour les applications graphiques informatiques comme la publicité, la visualisation 3D, jeux 3D, web et tout autre domaine lié à la conception 3D est disponible en téléchargement.","earth":"Le niveau de détail dans la vue de la terre récemment ajoutée dans les cartes Google a choqué un certain nombre de personnes. Vous pouvez explorer l'ensemble de la planète - les océans, les montagnes et les déserts, les villes et les routes découvrent cachées sans quitter votre maison. C'est des choses incroyables Mère Terre n'a pas connu auparavant. Une sélection de terre réalistes des modèles 3D qui sont prêts pour les applications graphiques informatiques comme la publicité, la visualisation 3D, jeux 3D, web et tout autre domaine lié à la conception 3D est disponible en téléchargement.","engine":"Certaines personnes sont en mesure de nommer le moteur en écoutant simplement le bruit qu'il fait, et qui est vraiment incroyable. Peu importe que ce soit un moteur à réaction ou un moteur de voiture, ils sont des machines extrêmement compliquées que nous utilisons si souvent, mais ne passent pas trop de temps à penser à ce sujet. Un moteur est un moteur (jusqu'à ce qu'il cesse de fonctionner). Cette catégorie comprend une gamme de différents types de moteur modèles 3D, et il comprend également des pièces de moteur comme l'hélice ou d'un cylindre, donc nous sommes confiants que vous trouverez ce dont vous avez besoin.","female":"Il est pas une blague qu'il existe des différences importantes entre le cerveau des hommes et des femmes, ce qui signifie qu'il est normal qu'un homme moyen se comportent et femme, sentir et d'y répondre de manière complètement différente. Mais ce n'est pas tout au sujet de l'anatomie. Aucune histoire d'amour, film ou jeu jamais tiré hors tension sans un fort caractère de fille, parce que la puissance de charme dame est indéniable. Une riche sélection de modèles féminins 3D réalistes peut vous offrir tout ce dont vous avez besoin - à partir de modèles 3D nu féminin pour les voies respiratoires.","hand":"La raison pour laquelle nous utilisons nos mains pour tant de choses est leur anatomie extraordinaire. Le pouce est contrôlé seul par neuf muscles séparés. Il est difficile à croire, mais les mains étaient au début plus exotiques que toute main aujourd'hui - certaines espèces avaient sept doigts. Le corps humain a considérablement évolué. Un bras ou une main comme nous le voyons aujourd'hui ressemble rien comme ses premières versions. Une large gamme de réaliste, truqué la main des modèles 3D qui sont prêts pour les applications graphiques informatiques comme la publicité, la visualisation 3D, jeux 3D, web et tout autre domaine lié à la conception 3D est disponible en téléchargement.","head":"Dans l'anatomie humaine, la tête est la partie supérieure du corps humain. Modélisation d'une tête humaine 3D peut se révéler un peu compliqué, parce que (même si il y a tellement de formes et de tailles différentes), nous apprécions symétriques têtes réalistes prospectifs - sans yeux drôles, les oreilles, le nez, les lèvres ou la ligne mâchoire, s'il vous plaît. Le visage est une zone sensible et il serait juste simplement grossier d'avoir du plaisir avec ça. Notre sélection comprend des hommes réalistes, des femmes, des enfants et même des personnages de l'histoire comme Harry Potter et Batman ou des célébrités comme Tom Cruise tête et Scarlett Johansson qui sont prêts à télécharger.","helmet":"Rappelez-vous toujours que la santé et la sécurité vient en premier, afin de protéger votre tête est une idée de son. chevaliers médiévaux, Daft Punk et même les casques d'usure Ironman, si les superhéros réguliers comme les cyclistes ou les constructeurs ne devraient pas se soucier de regarder pas cool. Cette c
16518ollection de modèles de casque 3D qui est disponible pour le téléchargement se révélera utile si vous travaillez dans les domaines de la conception graphique, animation 3D, jeux 3D ou la visualisation.","house":"C'est une vieille vérité que la maison d'un homme est son château, mais quand il vient à la construction de cette maison parfaite ou l'achat d'un appartement idéal, nous avons tendance à perdre la tête au-dessus. Heureusement, ce large éventail de modèles internes 3D qui englobe tout, de détails architecturaux, intérieurs et extérieurs, des meubles, vous aidera à créer 3D modernes visualisations pour soulager la douleur.","human":"Lorsque vous commencez à explorer l'anatomie humaine, on se rend compte que les gens sont telles machines complexes et complexes qu'il enlève votre souffle. Chaque partie du corps, les muscles, articulations, combinaison d'organes ou d'os est relié en un seul système, rien ne fonctionne en vase clos! Ces super réalistes modèles humains 3D vous permettra d'examiner les structures anatomiques sous tous les angles.","lamp":"Si vous regardez autour de votre maison et compter les types de lampes que vous possédez, vous serez surpris: lecture, table, mur, plafond, bureau, lampes ... trépied Il suffit de faire tourner la tête! Sans oublier les styles qu'ils viennent - des lampes modernes extravagantes à rétro ou futuriste élégant. Dans cette catégorie, nous avons toutes sortes de lampe modèles 3D, donc parcourir et télécharger à utiliser dans la conception graphique, 3D ou visualizations tout autre domaine connexe 3D.","light":"Avez-vous déjà demandé pourquoi une ampoule est le symbole d'une idée brillante? Certes, une idée brillante peut éclairer l'architecture de l'esprit, mais ce ne l'est pas. Une lumière de la rue, un lustre ou tout autre type de lampe ne serait jamais une chance de prendre cette place. Si la prochaine fois que vous levez les yeux vers le plafond et regarder une sorte de lumière architecturale l'idée de génie ne vous a frappé, il pourrait être parce que vous êtes à regarder un système d'éclairage mal ... Notre sélection de lumière de haute qualité modèles 3D se révéleront très pratique si vous travaillez dans les domaines de l'architecture, l'infographie, l'animation ou la conception 3D.","machine":"Imaginez si toutes les machines soudainement cessé de fonctionner: pas de café, pas de laver le linge pratique, pas de points de trésorerie sur un coin de la rue, pas de temps de gym, pas trop cher distributeurs automatiques ... Il ne semble déprimant, non? Nous sommes tellement habitués à ces appareils ménagers et gadgets qu'il serait littéralement arrêter le tournant du monde! Heureusement, notre sélection de la machine détaillée des modèles 3D couvre tout, de mitrailleuses aux producteurs d'électricité qui se révéleront utiles aux concepteurs travaillant avec l'infographie, la publicité, l'animation 3D, jeux 3D ou web.","man":"Une vaste collection de modèles 3D homme est disponible en téléchargement. homme assis, homme simple, homme volant ou homme moyen, nous avons tout - sauf que l'homme parfait que tout le monde est après. Il est un personnage louche et nous déteste en parler, mais il pourrait être un mythe ... Mais bon, les êtres humains sont nés pour être génial, pas parfait.","robot":"R2D2 est probablement le plus célèbre robot dans le monde, mais il est certainement pas le seul. De l'industrie à jouets aux robots humains, nous voyons de plus en plus un choix plus large de ces machines mécaniques, ce qui est à la fois merveilleux et un peu inquiétant. Il est pas trop difficile de comprendre les gens qui insistent sur le fait que les ordinateurs, la technologie, les robots ont un impact trop important sur les êtres humains. Une large gamme de réaliste, truqué robots modèles 3D qui sont prêts pour les applications graphiques informatiques comme la publicité, la visualisation 3D, jeux 3D, web et tout autre domaine lié à la conception 3D est disponible en téléchargement.","ship":"Les navires sont des navires puissants et sont disponibles dans toutes les tailles, les couleurs et les formes. Ils peuvent être classés comme des navires de charge, les navires de guerre ou des navires à passagers, mais ils fonctionnent tous dans les eaux profondes de la mer. Si vous avez connu l'inconfort de la maladie de mouvement, il ne peut pas être un de vos moyens de transport favori, mais beaucoup de gens considèrent comme des excursions en bateau tout à fait chic et agréable. Notre sélection de bateaux modèles 3D est prêt pour le téléchargement et répondra aux normes de même le plus exigeant capitaine.","skeleton":"Le squelette humain ou d'un système squelettique (anatomie) est le cadre interne du corps qui est composé de 270 os à la naissance. Il fournit la structure du corps, la protection et le mouvement et est l'un des systèmes les plus complexes du squelette. Une sélection riche anatomiquement correcte, le squelette des modèles 3D réalistes et mis à l'échelle qui sont parfaits pour une utilisation dans l'animation 3D, la conception graphique, la publicité, les jeux 3D et le Web est disponible en téléchargement.","skull":"Le plus ancien, le crâne humain génétiquement intact le plus complet a été découvert entouré par une variété d'animaux éteints plus de 40 mètres en dessous du niveau de la mer. Il est l'un des plus anciens squelettes trouvés dans le Nouveau Monde et comprend également tous les principaux os du corps et un crâne intact et ensemble de dents. crânes humains sont devenus récemment une chose à la mode et le design, mais quand vous donnez réellement une certaine pensée, ils genre d'entre nous font tous l'air freaks anatomie ... Cette catégorie offre une large gamme de modèles 3D crâne à utiliser dans la conception graphique , l'animation 3D, jeux 3D ou de la publicité.","sofa":"Luxe simple, moderne ou antique, cuir ou design minimaliste? Comment aimez-vous votre canapé? La gamme de canapé modèles 3D que nous avons satisfera même le pire perfectionniste. Lorsque vous arrêtez d'y penser, un canapé est le centre de chaque salon, vou
16518s devez donc faire preuve de créativité lors du choix de la conception!","table":"Les nouvelles tendances meubles sont si diverses que le choix d'une table moderne, la mode devient trop stressant. Il y avait un moment où vous aviez une table simple dîner pour vos fêtes modestes et cela suffisait, mais aujourd'hui - la table est censé être une déclaration ou tout au moins un accent intérieur accrocheur. Cette sélection vous donne une vaste gamme de tables modèles 3D pour répondre à tous les caprices.","tower":"Chaque ville a une tour spéciale que tout le monde est vraiment fier. Pensez Tour Eiffel ou la tour de Pise, ou pont tour de Londres, et encore moins tous les gratte-ciel modernes, les bâtiments futuristes et le paysage urbain que les touristes admirent. Cependant, en ce qui concerne les pièces industrielles de la ville, l'eau ou des tours électriques, tout le monde a tendance à tourner les yeux loin ... Mais nous devons apprendre à apprécier chaque création architecturale, peu importe l'ancienne, industrielle ou moderne. Une gamme de tour des modèles 3D détaillés sont disponibles pour le téléchargement et prêt pour les applications graphiques informatiques comme la publicité, la visualisation 3D, jeux 3D, web et tout autre domaine lié à la conception 3D.","tree":"Si vous êtes sur une mission de planter des arbres ou des plantes exotiques, vous êtes au bon endroit. Cette catégorie offre une gamme de facile à utiliser, très réalistes modèles d'arbres 3D aux artistes et aux architectes CG, qui luttent pour ajouter un peu de verdure à leurs projets. Si vous appréciez de bonne qualité, nous sommes convaincus que cette sélection de modèles de plantes d'arbres 3D répondra à vos besoins!","truck":"Conduire un camion est un travail difficile, surtout si vous avez besoin de trouver votre chemin à travers un embouteillage de la ville. Peu importe que ce soit un camion de pompier, camion à ordures, camion de nourriture ou d'un générique, il faut une grande habileté à manœuvrer ces véhicules géants. Nous sommes convaincus que cette sélection de réalistes, camion de haute qualité des modèles 3D sont parfaits pour une utilisation dans l'animation 3D, la conception graphique, la publicité ou tout autre domaine connexe 3D.","tv":"Il y avait un temps où cheminée était la pièce maîtresse de chaque maison respectable, maintenant télévision avec les joueurs et les haut-parleurs est le roi du salon (et même la chambre à coucher). À la télévision ces dernières années a rattrapé les technologies les plus récentes. En conséquence, nous avons smart TV 3D, écrans plats et plasma, millions de nouvelles fonctions et la conception de l'écran tactile. Pas de télévision miracle pousse le cinéma du marché. Une large gamme de réalistes, TV modèles 3D détaillés qui sont prêts pour les applications graphiques informatiques comme la publicité, la visualisation 3D, jeux 3D, web et tout autre domaine lié à la conception 3D est disponible en téléchargement."}},"tag_promotion":{"days_left_error":"doit être compris entre 1 et 30","no_other_pending_promotions_for_same_item_tag":"a déjà une promotion en attente"},"tooltips":{"buyer_loyalty_program_current_level_html":"Actuellement, vous avez passé %{total_price_paid} sur CGTrader et votre crédit gagner taux est %{buyer_loyalty_rate}. \u003cbr/\u003e Cela signifie que vous obtiendrez %{buyer_loyalty_rate} des crédits de chaque achat.\n","buyer_loyalty_program_html":"Ceci est le pourcentage de combien vous obtiendrez de vos achats en tant que nouveaux crédits. \u003cbr/\u003e Il augmentera jusqu'à 30% de vos dépenses à vie sur l'augmentation CGTrader. \u003cbr/\u003e Actuellement, votre crédit gagner taux est %{buyer_loyalty_rate}.\n","buyer_loyalty_program_next_level_html":"Niveau suivant %{next_lvl_expenses} de va augmenter votre taux de crédit gagner jusqu'à %{next_lvl_expenses_rate}. \u003cbr/\u003e Vous devez acheter pour $%{left_until_next_lvl} plus pour atteindre le niveau suivant.\n"},"trending_headers":{"models":"Découvrez d’autres modèles 3D tendance","print":"Découvrez d’autres modèles 3D imprimables tendance"},"tutorial":{"destroy":"Le tutoriel a été supprimé avec succès.","submit_for_review_error":"Veuillez remplir tous les champs obligatoires.","submitted":"Merci d’avoir soumis votre tutoriel. Il sera examiné et publié sous peu."}
16518,"valvat":{"country_adjectives":{"at":"autrichien","be":"belge","bg":"bulgare","cy":"chypriote","cz":"tchèque","de":"allemand","dk":"danois","ee":"estonien","es":"espagnol","eu":"intracommunautaire","fi":"finlandais","fr":"français","gb":"britannique","gr":"grec","ie":"irlandais","it":"italien","lt":"lituanien","lu":"luxembourgeois","lv":"letton","mt":"maltais","nl":"hollandais","pl":"polonais","pt":"portugais","ro":"roumain","se":"suédois","si":"slovène","sk":"slovaque"}}},"ja":{"activerecord":{"attributes":{"content_report":{"categories":{"adult":"アダルトや暴力的なコンテンツ","ai_generated":"AI生成コンテンツ","copyright":"著作権侵害"}}},"errors":{"messages":{"embedded_script":"このファイルはサポートされていないため、アップロードできません。別のファイルをお試しください。"}}},"api":{"profile":{"actions":{"contact":"コンタクト","follow":"フォロー","follow_tooltip":"このデザイナーをフォローしてコンテンツの更新を受け取り、検索結果でお気に入りのデザイナーを簡単に絞り込みましょう。","hide":"非表示","hire_me":"依頼する","invite_client":"プロジェクトを開始","mute_tooltip":"検索結果でこのデザイナーのモデルを非表示にします。","take_over":"引き継ぐ","unfollow":"フォロー解除","unhide":"表示する","view_all_models":"デザイナーのモデルをすべて見る"},"private_project_review":"非公開プロジェクト"}},"browser":{"3d_models":"3Dモデル","3d_print":"3Dプリント","active_filters":{"polygons":"%{range}ポリゴン"},"adult_content":"成人","animated":"アニメーション","breadcrumb":{"browse_by_type":"タイプで閲覧","collection":"3Dパック","free":"自由","free_models":"無料の3Dモデル","free_print_models":"無料の3Dプリントモデル","low_poly":"低ポリ","low_poly_models":"VR / AR /低ポリ3Dモデル","low_poly_print_models":"ローポリ3Dプリントモデル","model_3d":"3Dモデル","model_3dp":"3Dプリントモデル","models":"3Dモデル","models_3d":"3Dモデル","models_3dp":"3Dプリントモデル","models_variant_0":"3Dモデル","models_variant_1":"3Dアセット","models_variant_2":"VRモデル","models_variant_3":"CGモデル","models_variant_4":"3Dモデルストア","models_variant_5":"3Dオブジェクト","print_models":"3Dプリントファイル","print_models_variant_0":"3Dプリントモデル","print_models_variant_1":"3Dプリンターモデル","print_models_variant_10":"印刷用の3Dモデル","print_models_variant_11":"印刷するための3Dモデル","print_models_variant_12":"印刷用3Dファイル","print_models_variant_13":"3Dプリンター用モデル","print_models_variant_14":"3Dプリント用モデル","print_models_variant_15":"3Dプリントモデルズ","print_models_variant_16":"印刷可能な3Dファイル","print_models_variant_2":"印刷用3Dモデル","print_models_variant_3":"3D印刷モデル","print_models_variant_4":"3Dプリントファイル","print_models_variant_5":"3D印刷用ファイル","print_models_variant_6":"3Dプリンターファイル","print_models_variant_7":"印刷可能な3Dモデル","print_models_variant_8":"印刷用の3Dモデル","print_models_variant_9":"3Dプリントされたモデル"},"buyer_engagement_program":"割引","categories":"カテゴリー","category":{"meta":{"3d_models":"%{count} %{category} FBX、OBJ、MAX、3DS、C4Dを含む任意のファイル形式でダウンロード可能な3Dモデル、%{page}","3d_print_models":"FDM、SLA、SLSおよび他の3Dプリンタ、利用可能なフォーマットのSTL、OBJ、3DMの準備ができて、より。%{page}ダウンロード %{count} %{category}無料の3Dプリントモデル","animated_3d_models":"%{count} %{category}はFBX、OBJ、MAX、3DS、C4Dを含む任意のファイル形式でダウンロード可能な3Dモデルを、アニメーション。%{page}","free_3d_models":"ダウンロードMAX、OBJ、FBX、3DS、C4Dファイルフォーマット、VR / ARのための準備ができて、アニメーション、ゲームやその他の3Dプロジェクトで利用できる %{count} %{category}無料の3Dモデル、。%{page}","free_3d_print_models":"FDM、SLA、SLSおよび他の3Dプリンタ、利用可能なフォーマットのST
16518L、OBJ、3DMの準備ができて、より。%{page}ダウンロード %{count} %{category}無料の3Dプリントモデル","lowpoly_3d_models":"ダウンロード %{count} VR / AR /低ポリ%{category} 3DモデルをFBX、OBJ、MAX、3DS、C4Dファイル形式で利用可能なバーチャルリアリティ、ゲームや他のリアルタイムアプリケーションのための準備ができました。%{page}","rigged_3d_models":"%{count} %{category}はFBX、OBJ、MAX、3DS、C4Dを含む任意のファイル形式でダウンロード可能な3Dモデルを装備。%{page}"}},"collection":"3Dパック","discounts":"割引","ext":{"meta":{"3d_models":"%{format_upper}形式の3Dモデル%{rounded_count}点。無料\u0026有料の%{format_lower}データをダウンロード。","3d_print_models":"ダウンロード%{count}%{ext}ファイル - %{software_name}の3DプリントモデルVR / AR、3Dデザイン、アニメーション、ゲームやリアルタイムのアプリケーションのための準備ができて%{page}。"}},"filter":"フィルタ","filter_button":"フィルタを適用","filters":{"adult_content_only":"アダルトコンテンツのみ","cgt_standard_only":"CGT基準","collections_only":"パックのみ","content_filters":"コンテンツフィルター","designer":"デザイナー","exclude_3d_print":"3Dプリントを除外","exclude_adult_content":"アダルトコンテンツを除外","exclude_ai_generated":"AI生成コンテンツを除外","exclude_editorial_license":"編集ライセンスを除外","exclude_free":"無料を除外","file_format":"ファイル形式","following":"フォロー中","free":"無料","has_marmoset":"3Dビューアーあり","has_video":"動画あり","include_ai_generated":"AI生成コンテンツを含める","label":"フィルタ","poly_count":"ポリゴン数","print_ready":"3Dプリント","stl_files":"STLファイル","subscription":"サブスクリプション","type":"タイプ"},"formats":"フォーマット","free":"無料","free_models":"無料のモデルのみを表示","free_to_premium":{"main":"%{category} 3Dモデル","postfix":"がプレミアムリストにあります。","prefix":"特別なものが必要ですか?より高品質な"},"gallery":{"preview":"プレビュー"},"game_ready":"ゲーム用","high_poly":"ハイポリ","items_page_title":"3Dモデル","license":{"exclude_branded":"ブランドを除外","exclude_editorial":"エディトリアルを除外","prompt":"ライセンス","tooltip_more":"続きを読む","tooltip_text":"非商業的、教育目的でのみ使用できるモデルを表示しない。","tooltip_title":"エディトリアルの除外:"},"low-poly":"ローポリ","low_poly":"ローポリ","meta":{"3d_models":"プロ品質の3Dモデル%{rounded_count}点を販売・購入・無料ダウンロード。ゲーム・映像・VFX・AR/VR・3Dプリント向け。FBX、OBJ、BLEND、GLB対応。","3d_print_models":"3Dプリンター用データ%{rounded_count}点をダウンロード。無料\u0026有料のSTLデータ・3Dプリントモデル:フィギュア、ミニチュア、実用品など。","animated_3d_models":"アニメーション付き3Dモデル%{rounded_count}点をダウンロード。FBX、BLEND、GLB形式のゲーム対応キャラクター・オブジェクト。","free_3d_models":"無料3Dモデル%{rounded_count}点をダウンロード。商用利用可のフリー3D素材・3Dデータ。FBX、OBJ、BLEND、GLB形式に対応。","free_3d_print_models":"無料の3Dプリンター用データ%{rounded_count}点。フィギュアや実用品のSTLデータを無料ダウンロード。","lowpoly_3d_models":"ローポリ3Dモデル%{rounded_count}点。ゲーム・AR/VR・リアルタイムレンダリング向けの軽量モデルをダウンロード。","metaverse":"ファイル形式を装備し、アニメーション化し、利用可能な高ポリ3Dモデルと低ポリ3Dモデルのコレクションでメタバースを入力します。","omniverse":"Omniverse対応の3Dモデルで仮想世界を構築します。ポリゴン数が高く、低いアニメ、リグ、PBRモデルから選択します。","page_prefix":"page %{page_number} with","pbr_3d_models":"PBRテクスチャ付き3Dモデル%{rounded_count}点。Unity、Unreal、リアルタイムエンジン向け。","rigged_3d_models":"リグ(ボーン)入り3Dモデル%{rounded_count}
16518点。Blender、Maya、3ds Max、Unity、Unreal対応、アニメーション対応。","snap-ar":"3Dモデルを使用すると、Snap ARを使用して何百万ものSnapchattersに参加して、楽しい拡張現実体験を作成できます。","spark-ar":"FacebookとInstagramを明るくし、さまざまなSpark AR 3Dモデルを使用して、リグがあり、アニメーション化され、すぐに使用できます。","stream_3d_print_models":"ストリーム3Dプリントモデルカテゴリのさまざまなを表示して、プロの3Dクリエイターから最も異常な3D印刷可能な設計のいくつかを発見します。","textures":".TGA .sbsar .JPEGの.png .TIFFおよび他を含む様々なフォーマットでダウンロード可能%{count}テクスチャ3Dアセットおよび材料。 %{page}","tiktok":"Tiktok 3Dモデルを使用して、ビデオエフェクトをより印象的にします。私たちのライブラリはあなたの創造的なニーズをすべて満たすことができ、絶えず更新されています。","unity":"任意のファイル形式とポリカウントでUnityプロジェクトの3Dモデルを見つけます。一意の仮想環境を作成するために必要なすべて。","unreal":"任意のファイル形式でUnreal Engineに互換性のある3Dモデルを使用して、印象的な視覚化を作成します。私たちのライブラリには、あらゆるカテゴリの低いモデルと高ポリモデルがあります。"},"optimized":"ローポリ/ PBR","page_title":{"animated_3d_models":"アニメーション付き3Dモデル – 無料\u0026有料","format_page":"%{format_upper} 3Dモデル – %{format_lower}データ 無料\u0026有料","free_3d_models":"無料3Dモデル – フリー素材ダウンロード(商用利用可)","free_3d_print_models":"3Dプリンター用データ 無料 – フリーSTLデータ","lowpoly_3d_models":"ローポリ3Dモデル – 無料\u0026有料","pbr_3d_models":"PBR 3Dモデル – 無料\u0026有料","rigged_3d_models":"リグ入り3Dモデル(Rigged) – 無料\u0026有料","software_page":"%{software}用3Dモデル – %{ext_file}ダウンロード","tag":{"3d_merged_premium":"%{tag_cap} 3Dモデル %{rounded_count}点 – 無料\u0026有料","3d_merged_premium_print":"%{tag_cap} 3Dプリンターデータ – STL 無料\u0026有料","3d_models":"%{tag_cap} 3Dモデル %{rounded_count}点 – 無料\u0026有料","3d_print_models":"%{tag_cap} 3Dプリンターデータ – STL 無料\u0026有料"}},"pbr":"PBR","per_page":"1ページあたり","polygons_options":{"custom":"カスタム範囲","gt_250k":"250K +","lt_5k":"最大5K","more_than_250000":"250K以上","prompt":"ポリ数","range_0_5000":"最大5K","range_100000_250000":"100~250K","range_10000_50000":"10~50K","range_100k_250k":"250Kに100K","range_10k_50k":"50Kへの10K","range_50000_100000":"50~100K","range_5000_10000":"5~10K","range_50k_100k":"100Kに50K","range_5k_10k":"10Kに5K"},"price":"価格:$%{from}-%{to}","price_range":"価格","print_ready":"3Dプリント用","printable":"3Dプリントモデル","reset_button":"フィルタをリセット","rigged":"リグ","rounded_count":{"delimiter":",","exact":"%{n}","separator":".","ten_thousand":"%{n}万","thousand":"%{n}"},"sale_off":"セール中","search_placeholder":"検索キーワード","show_all_formats":"すべてのフォーマットを表示","simulation_ready":"シミュレーション対応","software":{"meta":{"3d_models":"%{software}用の3Dモデル%{rounded_count}点。無料\u0026有料の%{ext_file}ファイルをそのままインポート可能。"}},"sort_by":"並び替え","sort_by_options":{"best_match":"ベストマッチ","highest_price":"価格が高い順","lowest_price":"価格が低い順","newest":"最新","oldest":"古い順","sales":"人気商品","trending":"トレンド"},"subcategory":{"meta":{"3d_models":"%{count} %{category} FBX、OBJ、MAX、3DS、C4Dを含む任意のファイル形式でダウンロード可能な3Dモデル、%{page}","3d_print_models":"FDM、SLA、SLSおよび他の3Dプリンタ、利用可能なフォーマットのST
16518L、OBJ、3DMの準備ができて、より。%{page}ダウンロード %{count} %{category}無料の3Dプリントモデル","animated_3d_models":"%{count} %{category}はFBX、OBJ、MAX、3DS、C4Dを含む任意のファイル形式でダウンロード可能な3Dモデルを、アニメーション。%{page}","free_3d_models":"ダウンロードMAX、OBJ、FBX、3DS、C4Dファイルフォーマット、VR / ARのための準備ができて、アニメーション、ゲームやその他の3Dプロジェクトで利用できる %{count} %{category}無料の3Dモデル、。%{page}","free_3d_print_models":"FDM、SLA、SLSおよび他の3Dプリンタ、利用可能なフォーマットのSTL、OBJ、3DMの準備ができて、より。%{page}ダウンロード %{count} %{category}無料の3Dプリントモデル","lowpoly_3d_models":"VR / AR /ローはポリ%{tag} 3Dモデルはバーチャルリアリティ、ゲームや他のリアルタイムアプリケーション、FBXで利用可能な、OBJ、MAX、3DS、C4Dファイルフォーマットの準備ができてダウンロードしてください。%{page}","rigged_3d_models":"%{count} %{category}はFBX、OBJ、MAX、3DS、C4Dを含む任意のファイル形式でダウンロード可能な3Dモデルを装備。%{page}"}},"subtitle":{"aircraft":"%{count}航空機の3Dモデルは,当社の市場からダウンロードできます。 CGの%{airplanes_link}はシミュレーション,視覚化,広告およびビデオに収まります。 %{drone_link}体及び部品は,小型飛行車両のファンを喜ばせます。そして%{rigged_link}モデルは,ゲームエンジンにインポートする準備ができているとアニメーションやVR / ARプロジェクトのために使用することができます。フライングゲーム,シミュレータや定期的なCGと低ポリタイプの両方のビデオはFBX,OBJ,MAX,3DS,C4Dを含む最も人気のあるフォーマットで3Dモデルを充填することができます。","animals":"CG 3D動物の%{count}  - %{cats_link},%{horses_link},%{bears_link},%{rhinos_link},%{eagles_link},%{lions_link},架空%{dragons_link}より - ここで利用可能ですダウンロードします。 3Dの動物のコレクションは,広告または静的なシーンのニーズを満足させます。我々はまた,ゲーム,アニメーション,映像制作およびVR / ARプロジェクトに対する%{rigged_link}モデルを持っています。フィルタを使用して,アニメーション,低ポリまたはFBX,OBJ,MAX,3DS,C4D,その他を含むほとんどの業界で認められたファイル形式で利用できる無料の3Dモデルを装備見つけること。","animated":"アニメーション付き3Dモデル%{rounded_count}点。歩行サイクルやアクションなど、すぐ使えるモーション入りのキャラクター・動物・オブジェクト。","architectural":"ダウンロード可能な建築3Dモデルの%{count}は要素や建築,建設,エンジニアリング設計の詳細が含まれています。無料の3Dモデルを購入したり,ダウンロードするには,上記のカテゴリを参照します。 VR / ARプロジェクトのニーズに応えるためにそれらのいくつかは,不正れており,低ポリ - あなたはアニメやゲームのためのモデルを使用することができます。 3Dモデルは,FBX,OBJ,MAX,3DS,C4Dなどのファイル形式で利用可能です。","car":"%{count}の車の3Dモデルをダウンロードすることができます。映画製作者,アニメーターや広告主に - の低ポリのバージョンでは,ゲームデザイナーやVR / ARアプリケーションの開発者,他の人に魅力的になります。映画で見たレースや概念のものにクラシックとアンティークカーからは,基本的に任意の形式(FBX,OBJ,MAX,3DS,C4D,など)あなたが必要な場合があります内の3D自動モデルの多種多様があります。プロトタイプと短く,開発時間をアニメーションで無料の3Dモデルを使用してください。","character":"あなたは%の3Dモデル%{human_link},人々,神話上の人物,サイファイ%{robots_link},衣類や文字に関連する他のものを見つけるでしょう,このカテゴリを閲覧して%{count}キャラクターの3Dモデルは,ダウンロードすることができます。%{heart_link}と%{hand_link}から詳細な現実的な%{male_link}又は%{female_link}モデルに。モデルの数が不正れており,簡単にゲーム,VR / ARプロジェクト,ビデオやアニメーションに追加することができます。両方の高いポリかつ低ポリモデルはFBX,OBJ,MAX,3DS,C4Dを含むフォーマットの数に見出すことができます。","custom"
16518:"ここでは,無料%{tag} 3Dモデルを購入したり,ダウンロードすることができます。 %{tag_heading_subtitle}","electronics":"ダウンロード可能な%{count}エレクトロニクス3Dモデル。これは,建築家,インテリアデコレーター,レベルデザイナー,シーンのメーカーや他のクリエイティブのためのカテゴリです。 の3Dモデル%{smartphones_link},%{laptops_link},%{cameras_link},%{tvs_link},より正確デバイスや技術と現代の生活の強迫観念をキャプチャするのに役立ちます。 FBX,OBJ,MAX,3DS,C4Dを含むファイル形式で利用可能な3Dモデルがあります。無料ダウンロードやアニメーション,ゲーム,VR / ARプロジェクトのためのプレミアムモデルと使用を買います。","exterior":"ファサード,都市の建物,風景,オブジェクト,住宅からの産業構造,インフラの建物とフルスケールの%{city_link}モデルへのダウンロードの範囲で使用可能な建築外装3Dモデルの%{count}。無料の3Dモデルを購入したり,ダウンロードするには,上記のカテゴリを参照します。利用は無料,低ポリ,アニメーション,3Dモデルを不正に操作またはソート形式でFBX,OBJ,MAX,3DS,C4Dを含め,必要な見つけることはフィルタリングします。資産の一部は,低ポリであり,あなたがあなたのゲームやVR / ARアプリケーション環境を完成に役立つ可能性があります。","food":"食物は,国内の人生のリアルなシーンを作成するためにも機能するために%{count}食品の3Dモデルが用意されています。 3D果物,野菜,飲料や食事は建築家やインテリアデザイナーはに取り組んでいる完全なレストランの広告,健康的な食事のためのARアプリケーション,および視覚化にすべてここにあります。あなたは,3D %{pizza_link}と%{ketchup_link}モデルや結婚式%{cake_link}の3Dモデルを探しているかどうか,彼らはここにあります。低ポリバージョンの%{count}は,ゲーム,VR / AR開発中のもののために用意されています。 3Dモデルは,FBX,OBJ,MAX,3DS,C4Dフォーマットに見出すことができます。","formats":"%{format_upper}形式の3Dモデル%{rounded_count}点をダウンロード。このページのファイルはすべてネイティブ%{format_upper}で、変換なしで利用できます。","free_general":"無料3Dモデル%{rounded_count}点をダウンロード。フリー3D素材・3Dデータとして個人利用も商用利用も可能なモデルを多数掲載。FBX、OBJ、BLEND、GLB対応。","free_no_tag":"%{model_count}フリーの3D %{tag}モデルは,3D印刷の準備します。ダウンロード%{tag}自由な3Dプリントモデル,STL,OBJ,3DM,3DS,MAXを含む利用可能なファイル形式。 3Dモデルは,FDM,SLA,SLS及び3Dプリンタの他のタイプと一緒に使用される準備ができています。","free_printable":"無料の3Dプリンター用データ%{rounded_count}点をダウンロード。コミュニティ検証済みのフリーSTLデータ — フィギュア、ミニチュア、実用品、インテリア雑貨など。","free_tag":"ダウンロード可能な無料の3D%{tag}モデル。 MAX,OBJ,FBX,3DS,STL,C4D,BLEND,MA,MBを含む多くのファイル形式で利用できます。バーチャルリアリティ(VR),拡張現実(AR),ゲーム,3Dビジュアライゼーションやアニメーションなどの任意の3次元設計プロジェクトのための専門の%{tag_link}を見つけます。","furniture":"%{count}ダウンロード可能家具3Dモデルは,テクスチャの様々なテーブル,椅子,ソファー,ベッド,他の家具を含む等(木材,金属,等)やスタイル(%{retro_link},%{modern_link} )。このカテゴリは,建築とインテリアデザインだけでなく,広告主やアニメーターに関与し,誰のためのものです。リアルタイムの資産は完全なプロジェクトにゲームやVR / ARアプリケーション開発者を支援することができます。使用は不正,アニメーション,低ポリまたは無料の3Dモデルを見つけるためにフィルタリングします。 FBX,OBJ,MAX,3DS,C4Dを含む任意のファイル形式で利用できます。","general":"ゲーム・映像・VFX・AR/VR・3Dプリント向けのプロ品質3Dモデル%{rounded_count}点。無料ダウンロードまたは購入でき、FBX、OBJ、BLEND、GLBほか100以上の形式に対応しています。","high_poly":"購入またはダウンロードアニメーションプロフェッショナル3Dモデルは,CGのプロジェクトで使用するために,フィルムやビデオ制作,アニメーション,視覚化,およびその他の準備ができました。資産はMAX,OBJ,FBX,3DS,UE4およびその他を含む多くの業界で認められたフォーマットでダウンロードできます。あなたは,高品質のポリ3D資産の検索にしている場合は,我々はすべてのニーズのための主要なデジタルアートライブラリーを持っています。\n","household":"ダウンロード可能な%{count}家庭の3Dモデルが部屋に詳細を与えるために使用することができます。あなたは,テーブルの上にディナーセットを入れて,完全なキッチンのインテリアに唐辛子と塩の容器を持っているか,高級感を作成するために,ベッドの近くにいくつかの%{lights_link}と%{flowers_link}を置きたいかもしれません。あなたがインテリアデザイナー,アニメーター,VR / ARアプリケーションやゲームクリエイターであれば,このカテゴリはあなたのためです。使用は不正,アニメーション,低ポリまたは無料の3Dモデルを見つけるためにフィルタリングします。 FBX,OBJ,MAX,3DS,C4Dを含む任意のファイル形式で利用できます。","industrial":"建設資材,%{engineering_link}ツール,機械,%{containers_link}  -   - ダウンロード可能な%{count}工業用3Dモデルの両方の詳細を提供し,インタラクティブは,活気のある環境を作成するための要素を装備しました。これは,ゲーム中%{construction_link}サイトレベルを構築し,シミュレーション/ VR / ARや開発者のための完璧なカテゴリです。 3Dモデルは,FBX,OBJ,MAX,3DS,C4Dなどの形式でのモデルが用意されていて様々なソフトウェアでの作業の正確なビジュアライゼーションを構築する専門家をæ
16518ä¾›ã—ます。","interior":"%{count}内部3Dモデルは,ダウンロードのために利用可能であり,建築,建設,エンジニアリング,インテリアデザインで使用される要素と詳細を含みます。プレミアムを購入したり,無料の3Dモデルをダウンロードするには,上記のカテゴリを参照します。あなたは,アニメーションやゲームのためのモデルを使用することができます - そのうちのいくつかは,アニメーションや低ポリ,装備されています。 FBX,OBJ,MAX,3DS,C4Dを含むファイル形式のさまざまな利用できます。","low_poly":"ローポリ3Dモデル%{rounded_count}点をダウンロード。ゲーム・AR/VR・モバイル向けの軽量でゲームレディなアセット。","military":"%{count}軍事力と自己防衛が使用する武器,武器,%{swords_link}と現代と歴史の両方大砲の軍事3Dモデルをダウンロードすることができます。あなたはゲームやVR / ARコンテンツのためのモデルを使用することができます - そのうちのいくつかのポリゴンが最適化され,ゲームエンジンに追加する準備ができています。不正やアニメのモデルは,さらにあなたの仕事を容易にすることができます。あなたは(FBX,OBJ,MAX,3DS,C4D,など)が必要になる場合があります任意のファイル形式で利用できる多くの3Dモデルがあるとして,あなたはきっと,関連する資産を検索します。","not_custom":"%{tag_cap}の3Dモデル%{rounded_count}点。リアル系からデフォルメまで、無料\u0026有料のモデルをFBX、OBJ、BLEND、GLB形式でダウンロードできます。","optimized":"少ないポリゴンを持つモデルは,最高の高速処理,バーチャルリアリティ(VR)のように,拡張現実(AR),複合現実感(MR),クロス現実(XR)とゲームを必要とするリアルタイムアプリケーション,特にモバイルゲームで使用されています。簡単にリアルタイムアプリケーションでそれらを使用する装備やアニメのモデルの私達のコレクションから選択してください。 ,環境のための3D資産を取得小道具を選ぶ,オブジェクトまたはあなたのゲームが必要になる場合がありますすべてを完全な3Dモデルコレクション,バンドルとのパックを購入。保存の開発時間とコスト,メイクプロトタイプの経験やプロジェクト内のプレースホルダとして3Dモデルを使用しています。物理ベースのレンダリング,またはPBR起因テクスチャに任意の照明条件で正確にかつ一貫してレンダリングされるモデルは,作成プロセスで使用されるワークフローです。\n","pbr":"PBR(物理ベースレンダリング)テクスチャ付き3Dモデル%{rounded_count}点をダウンロード。UnityやUnrealなどであらゆるライティングに一貫したリアルな質感。","plant":"ダウンロード可能な%{count}植物の3Dモデルは,そのような花,%{trees_link},草,つる,緑,ハーブ,多年生植物,苗,芽,低木,スリップなど国内工場として,そして自然の中で育つ植物の生物を,含まれていますもやし,つる,雑草。プレミアムを購入したり,無料の3Dモデルをダウンロードするには,利用可能なモデルの中で最高のフィットを見つけるために,上記のカテゴリを参照します。使用フィルタがモデル(ゲームやVR / ARアプリケーションで使用するために),アニメ化,低ポリ不正見つけること,または必要な形式を除外する(FBX,OBJ,MAX,3DS,C4Dでのモデルとは,より利用可能です)。","printable":"3Dプリンター用データ%{rounded_count}点をダウンロード。FDM・光造形プリンターでテスト済みのSTLデータ・STLファイルを無料\u0026有料で提供。フィギュア、ミニチュア、実用品、パーツなど。","printable_merged_tag":"%{tag_cap}の3Dプリント用データ%{rounded_count}点をダウンロード。無料\u0026有料のSTLデータで、FDM・光造形どちらにも対応。","printable_tag":"%{tag_cap}の3Dプリント用データ%{rounded_count}点をダウンロード。無料\u0026有料のSTLデータで、FDM・光造形どちらにも対応。","rigged":"リグ(ボーン)入り3Dモデル%{rounded_count}
16518点をダウンロード。すぐにアニメーションできるキャラクター、動物、クリーチャー。Blender、Maya、3ds Max、Unity、Unreal対応。","scanned":"%{count}は3Dモデルを購入または無料でダウンロードできますスキャンしました。 3Dスキャンモデルは,アニメーション,ゲーム,VR / ARプロジェクトの準備ができています。あなたは,このような岩や自然の表面,ヒトおよび他の現実の生活がここにオブジェクトとして自然界に見られる物体の3Dモデルを見つけることができます...そして,それはこれよりもより現実的な取得することはできません。使用は不正,アニメーション,低ポリまたは無料の3Dモデルを見つけるためにフィルタリングします。 FBX,OBJ,MAX,3DS,C4Dを含む任意のファイル形式で利用できます。","science":"実験装置,顕微鏡本体と素粒子の世界:医学,生物学,物理学,化学の分野や他の現実の世界からこのカテゴリーの範囲内で,ダウンロード用の3Dモデルを利用できる%{count}科学は,科学技術に関連するオブジェクト。それ以上,%{cell_link},%{skull_link}と%{skeleton_link} 3Dモデルここで見つけることができますを見ていません!無料の3D資産を購入したり,ダウンロードするには,上記のカテゴリを参照します。あなたはゲームやVR / ARプロジェクトのためにアニメーションや低ポリもののために不正なモデルを使用することができます。どちらも無料とプレミアム3DモデルはFBX,OBJ,MAX,3DS,C4Dを含むファイルフォーマットのcountで見つけることができます。","scripts_plugins":"%{count} 3Dスクリプト/プラグインをダウンロードすることができます。 3Dスクリプト/プラグインは、さまざまなソフトウェアに追加され、より効率的にあなたの仕事のパイプラインを作るために使用することができます。お好みのソフトウェアをフィッティングツールを見つけるための形式によるフィルタ。\n","software_page":"%{software}用3Dモデル%{rounded_count}点をダウンロード。ネイティブ%{ext_file}ファイルはそのまま開け、他形式も数クリックでインポートできます。","space":"%{count}スペース3DモデルはCGTraderからダウンロードできます。スペースは,低ポリ,サイファイ%{spaceships_link}のゲーム対応モデルの完全なカテゴリです。 %{asteroids_link},惑星(月および火星など,シミュレーションコロニー形成)と現実的な空間%{shuttles_link}は,より科学的に傾斜のためにも存在します。モデルは間違いなく惑星% %{earth_link}外の世界を証明するためにVRスペースシミュレーターゲーム,ARアプリケーションやアニメーションで使用することができます。フィルタを使用すると,FBX,OBJ,MAX,3DS,C4Dを含む特定のフォーマットで不正,アニメーション,低ポリ資産やモデルを見つけるのを助けるためにここにいます。","sports":"%{count}スポーツ3Dモデル利用できるネスト一緒にレクリエーション活動,娯楽,運動,レジャー,楽しい気晴らしのために使用されている機器,楽しいだけでなく,ゲーム,子供のおもちゃや楽器のフルレンジ。 FBX,OBJ,MAX,3DS,C4Dを含む必要な任意のファイル形式で無料の3Dモデルを,購入したり,ダウンロードするには,上記のカテゴリを参照します。あなたは,アニメーション,ゲーム,VR / ARプロジェクトのためのモデルを使用することができます - それらの多くは,装備低ポリまたは既にアニメーション化されています。","test":{"airplane":"航空愛好家,アップ聞いて,それは完璧な3Dは,あなたのボードゲームやモデルの収集のために飛行機モデルを印刷取得するので,簡単ではありませんでした。 %{model_count} 3D印刷可能な飛行機のモデルを検索し,あなただけでは棚の上に持っている必要がありますモデルを見つけます。出会う本物の飛行機モデルまたは作らアップのもの。最新のデザインや数はライト兄弟の時から戻っていることを何かを検索します。それはまた,彼が育つとき,パイロットであることを夢見るすべての子のための完全なギフトです。購入と3Dプリントモデルをダウンロードし,自分でそれを印刷したり,当社の3Dプリントサービスプロバイダーの一つにモデルを提出するには,それが印刷され,時間がないのあなたに出荷されているように!\n","anatomy":"私たちの市場でダウンロードできます%{model_count} 3D解剖モデル。ポリ骨,筋肉,臓器モデル - 高いと人体解剖学を探検。どんな教育や科学的なプロジェクトのための完璧な。改善の教育経験のために印刷可能な3Dモデルを使用してください。当社との見事なアニメーションやビデオゲームを作成します。\n 不正モデルの膨大なライブラリ。簡単PBR,アニメーションの低いポリ,見つけるために私たちの高度なフィルタリングシステムを使用して,\n モデルやニーズに最適なものを装備。含むファイル形式の多大から選択してください\n FBX,OBJ,MAX,3DS,C4Dのような最も人気のあるもの。\n","castle":"独自の驚異的な3D印刷城離れてクリックです!検索様々な3D城のモデルは,3Dプリントの準備ができて。あなたのD&Dセッションや他の中世のRPGのテーブルゲームのためのユニークなファンタジー要塞を作成します。これらの中世の要塞は,任意のボードゲームの愛好家や独自のコレクションに素晴らしい追加するための素晴らしい贈り物です。愛好家や子供のための一種のおもちゃの1を塗装するための完璧な3Dモデルを検索します。購入とは,選択した3Dの城のデザインをダウンロードし,独自のプリンタでそれを印刷したり,それを印刷し,あなたに出荷していると私たちの3Dプリントサービスプロバイダーの一つにモデルを提出します。\n","dice":"3Dはサイコロを印刷して運が尽きません!あなたのボードゲームの夜のための本格的なゲーム用サイコロをしたい場合は,これ以上探す必要はありません。ここでは,3Dプリントの準備ができて%{model_count}無料の3Dサイコロのモデルの間で選択することができます。ゲームが終了すると,アクセサリとしてこれらの美しいモデルを使用しています。ダウンロード無料の3D印刷可能なサイコロのモデル,STL,OBJ,3DM,3DS,MAXを含む利用可能なファイル形式。当社の3Dモデルは,FDM,SLA,SLSと3Dプリンタの他のタイプで使用される準備ができています。あなたのゲームは,もう少し面白くすると,あなたの3D印刷されたサイコロを入手!\n","drone":"3Dプリントと独自のドローンを構築したり,交換用部品を見つけます。 3Dは,無人偵察機が墜落着陸後組み立てや修理が容易な印刷しました。最小限の変更のための完璧なカメラの取り付け位置や要求にモデルを発見。楽しいレジャー,航空写真,ビデオ撮影やマッピングのためのパーフェクト。すべてのニーズに合ったユニークなデザインの3Dドローンモデルを検索し,購入します。自分でそれを印刷するか,それは時間がないで印刷し,あなたに出荷されたのã
16518¯,当社の3Dプリントサービスプロバイダーの一つにモデルを提出!独自の3D印刷されたドローンを取得し,広い青空を歩き回ります!\n","pokemon":"ピカチュウ,私はあなたを選択してください! 3Dプリントの準備ができてポケットモンスターの負荷から選択してください。グレートは,あなたの3D印刷されたポケモンコレクションを展開します。検索や友人とあなたの次のテーブルゲームのための最高の,最も詳細なポケモンのデザイン時につまずきます。購入とは,あなたの3Dポケモンをダウンロードし,独自の3Dプリンタでそれを印刷したり,それを印刷し,あなたに出荷していると私たちの3Dプリントサービスプロバイダーの一つにモデルを提出します。結局のところ,これは,3Dプリントのクールなものの一つです!\n","ring":"3D印刷されたジュエリーは,として,3Dプリントさ環であり,業界では新しい黒です! %{model_count}フリー3D印刷可能なリングモデルの膨大な検索。ユニークなデザインをブラウズし,あなたのためだけに作られた何かを見つけます。店で自分のために何かを見つけることができませんか?それとも,あなたは正確にあなたの好きな映画のキャラクターが持っているようなアクセサリーをしたいですか? STL,OBJ,3DM,3DS,MAXなどのファイル形式の多大からお選びいただけます。 3Dモデルは,そう私達の選択を閲覧し,あなたの新しいお気に入りの指輪を試着,FDM,SLA,SLSと3Dプリンタの他のタイプで使用される準備ができています。\n","shoes":"ビッグブランドは3Dが靴を印刷し,今あなたが経験を共有するだけでなくすることができ,オンボードです。無料の印刷可能な靴のデザインを参照して見つける - スニーカーからファッショナブルなかかとにあなたの上にそれらをしようとするためだけ待ってすぐにプリントモデルがたくさんあります。柔らかさと大きさを調整し,材料を選択することで,成果完璧にします。 146の無料の3Dシューモデルから選択し,STL,OBJ,3DM,3DS,MAXを含む利用可能なファイル形式でそれらをダウンロードしてください。設計は,FDM,SLA,SLS及び3Dプリンタの他のタイプと一緒に使用される準備ができています。あなたの3D印刷の靴今日を入手!\n","textures":"美しく作られた材料を含むダウンロード可能%{model_count} CGテクスチャの膨大な量を検索。ブラウズカテゴリーを通って,迅速にあなたのプロジェクトまで,ヘルプフィニッシュへのシームレスなファブリック,木材,金属,プラスチック,レンガのテクスチャを見つけます。ゲームやビデオを作成するための装備やアニメーションテクスチャを見つけるために私たちの高度な検索を使用してください。私たちの材料はそれほど時間を無駄にし,ほぼすべてのプロジェクト想像のためにすぐに使用できるCGのテクスチャが見つからない,TGA,SBSAR,JPG,PNG,TIFFなどを含む様々なフォーマットでダウンロードできます。\n"},"various":"簡単に他のカテゴリーでその場所を見つけることができません%{count}さまざまな3Dモデルをダウンロードすることができます。 %{rocks_link},%{clouds_link},%{logos_link},%{cigarette_link},あなたがあなたのゲーム,アニメーションやVR / ARプロジェクトを完了する必要がある場合がありますことを,他の非分類3Dモデルはここにあります。フィルタを使用して,アニメーション,低ポリまたは無料の3Dモデルを装備,または,FBX,OBJ,MAX,3DS,C4Dとして使用可能な形式で整理見つけること。","vehicle":"ダウンロードに利用できる%{count}車両3Dモデルはトラック,%{ambulances_link},バス,%{bikes_link}とモトが含まれる - このカテゴリには,現実的またはSFのかどうかを自分の街は車でいっぱいにしたい人のためのものです。これら作るシミュレーションゲームやVR / ARアプリケーション,ビデオ制作におけるクリエイティブ,あるいはポスターのデザインは,ほとんどの場合,このようなFBX,OBJ,MAX,3DS,C4D使用可能などの最も一般的なファイル形式があります。電車や %{tractor_link} 3Dモデルのような他の陸上輸送車両も,この膨大なコレクションで見つけることができます。使用して,より高速な装備,アニメーション,低ポリまたは無料の3Dモデルを見つけるためにフィルタリングします。","watercraft":"%{count}はダウンロード可能な3Dモデルは%{boats_link},%{yachts_link},%{ships_link}及びあらゆる種類の他の水行く車両を含む船舶。 CGモデルは,あなたのシーンと視覚忠実度がポリcountよりも重要である他のアプリケーションのためにここにいる間,低ポリの3Dモデルは,ビデオゲーム,VR / ARプロジェクトだけでなく,アニメーションでよく行います装備。モデルは,以下を含む任意のファイル形式で利用可能であるが,FBX,OBJ,MAX,3DS,C4Dに限定されません。"},"tag":{"meta":{"3d_merged_premium":"%{tag_cap}の3Dモデル%{rounded_count}点をダウンロード。無料\u0026有料、FBX、OBJ、BLEND、GLB対応。ゲーム・映像・AR/VR・3Dプリント向け。","3d_merged_premium_print":"%{tag_cap}の3Dプリンター用データ%{rounded_count}点。無料\u0026有料のSTLデータ、FDM・光造形プリンター対応。","3d_models":"%{tag_cap}の3Dモデル%{rounded_count}点をダウンロード。無料\u0026有料、FBX、OBJ、BLEND、GLB対応。ゲーム・映像・AR/VR・3Dプリント向け。","3d_print_models":"%{tag_cap}の3Dプリンター用データ%{rounded_count}点。無料\u0026有料のSTLデータ、FDM・光造形プリンター対応。","animated_3d_models":"%{count} %{tag} FBX、OBJ、MAX、3DS、C4Dを含む任意のファイル形式でダウンロード可能な3Dモデルを、アニメーション。%{page}","free_3d_models":"ダウンロードMAX、OBJ、FBX、3DS、C4Dファイルフォーマット、VR / ARのための準備ができて、アニメーション、ゲームやその他の3Dプロジェクトで利用できる %{count} %{tag}無料の3Dモデル、。%{page}","free_3d_print_models":"FDM、SLA、SLSおよび他の3Dプリンタ、利用可能なフォーマットのST
16518L、OBJ、3DMの準備ができて、より。%{page}ダウンロード %{count} %{tag}無料の3Dプリントモデル","lowpoly_3d_models":"ダウンロード %{count} VR / AR /低ポリ%{tag} 3DモデルをFBX、OBJ、MAX、3DS、C4Dファイル形式でのバーチャルリアリティ、ゲームや他のリアルタイムアプリケーション、利用できるための準備。%{page}","rigged_3d_models":"%{count} %{tag} FBX、OBJ、MAX、3DS、C4Dを含む任意のファイル形式でダウンロード可能な3Dモデルを装備。%{page}"}},"title":{"download":"|ダウンロード","files":"3Dファイル","formats":"%{format_upper} 3Dモデル","free":"無料3Dモデル・フリー3D素材","free_printing":"無料3Dプリンター用データ","free_tag":"フリー%{selected_tag} 3Dモデル","general":"プロの3Dモデルを購入","high_poly":"%{selected_filter}3D モデル","landing":{"animated_3d_models":"アニメーション付き3Dモデル","lowpoly_3d_models":"ローポリ3Dモデル","pbr_3d_models":"PBR 3Dモデル","rigged_3d_models":"リグ入り3Dモデル"},"low_poly":"%{selected_filter}3D モデル","newest_cg":"新着および最新の3Dモデル","newest_print":"新着および最新の3Dプリントモデル","page_prefix":"page %{page_number}","pbr":"物理ベースのレンダリング 3D モデル","printing":"3Dプリンター用データ・STLデータ","sales_cg":"ベストセラー3Dモデル","sales_print":"ベストセラー3Dプリントモデル","software_page":"%{software}用3Dモデル","tag_heading":{"cg":"無料の %{tag} 3D プリント モデル","filter":"%{selected_filter}%{tag} 3D モデル","general":"%{tag_cap} 3Dモデル","merged_premium_print_tag":"%{tag_cap} 3Dプリンターデータ","merged_premium_tag":"%{tag_cap} 3Dモデル","printable":"%{tag_cap} 3Dプリンターデータ"},"trending_cg":"トレンド3Dモデル","trending_print":"トレンド3Dプリントモデル"}},"cart":{"request_invoice_failed":"おっと、問題が発生しました。","request_invoice_successful":"請求書のリクエストが正常に送信されました。","shareable_cart_created":"カートが正常に共有されました。","shareable_cart_failed":"おっと、問題が発生しました。","subscription_covered_items_removed":"カートから削除されました:%{titles}。すでにサブスクリプションに含まれています。","subscription_covered_order_refused":"%{titles} はサブスクリプションに含まれるようになったため、料金は請求されていません。サブスクリプションからダウンロードしてください。"},"categories":{"cg":{"header_title_html":"プロの3Dモデルを購入","text":"ニーズに合った正確な3Dコンテンツを見つけましょう。AR/VR、ゲーム、広告、エンターテインメントなどに対応。\n"},"head-title":{"cg":"3Dモデル %{rounded_count}点 – 販売・購入・無料ダウンロード","cg-models":{"sort-by":{"newest":"新着3Dモデル","top-selling":"ベストセラー3Dモデル","trending":"トレンド3Dモデル"}},"print":"3Dプリンター用データ – STLデータ 無料\u0026有料","print-models":{"sort-by":{"newest":"新着3Dプリントモデル","top-selling":"ベストセラー3Dプリントモデル","trending":"トレンド3Dプリント商品"}},"product-type":{"cg":"3Dモデル","cg-merge-premium":"3Dモデル – 無料およびプレミアムダウンロード","lower":{"cg":"3Dモデル","print":"3Dプリントファイルとモデル"},"plural":{"capital":null},"print":"3Dプリントファイルとモデル","print-merge-premium":"%{tag} 3Dプリントファイル - 無料およびプレミアムSTLモデル","singular":{"capital":null}}},"index":{"browse_all":"すべてのモデルを閲覧","info-section__description_1_html":"プロ仕様の3Dモデルを購入または無料ダウンロード。CGプロジェクト、映画・ビデオ制作、アニメーション、ビジュアライゼーション、ゲーム、VR/ARなどã
16518§ã™ãã«ä½¿ç”¨å¯èƒ½ã§ã™ã€‚アセットはMAX、OBJ、FBX、3DS、STL、C4D、BLEND、MA、MBなど、多くの\u003ca href='https://www.cgtrader.com/blog/3d-file-formats' target='_blank' rel='noopener noreferrer'\u003e業界標準フォーマット\u003c/a\u003eで利用できます。高ポリゴンやリアルタイム3Dアセットを探しているなら、私たちのデジタルアートライブラリが最適です。\n","info-section__description_1_printable_html":"家庭用デスクトップFDMや産業用SLS 3Dプリンターで印刷可能な、プロフェッショナルな3Dファイル、プリント、モデルが多数の業界フォーマット(OBJ、\u003ca href=\"https://www.cgtrader.com/3d-print-models/stl-files\"\u003eSTL\u003c/a\u003e、WRLなど)で提供されています。もしあなたが3Dプリントの愛好家、メーカー、教育者、または3Dプリントでプロトタイプを作成する企業であるなら、私たちの家庭用装飾品、トレンドのガジェットアクセサリー、目を引くファッションアイテム、ユニークなジュエリー、機械部品、多種多様なミニチュアが、きっとインスピレーションを与えることでしょう。","info-section__heading_1_html":"3Dモデル\u003cstrong\u003eカテゴリー\u003c/strong\u003eを探す","info-section__heading_1_printable_html":"3Dプリントモデルの\u003cstrong\u003eカテゴリー\u003c/strong\u003eを検索する","info-section__heading_2_html":"3Dモデル\u003cstrong\u003eマーケットプレイス\u003c/strong\u003e","info-section__heading_2_printable_html":"その\u003cstrong\u003e他\u003c/strong\u003eは?"},"marketplace":{"3d-printing":{"link_text":"3Dプリントモデルを閲覧","text":"3Dプリンター用の3Dプリントモデルを探す。","title":"3Dプリントモデル"},"blender_text":"Blend形式で利用可能な数千のBlender 3Dモデルを発見。","browse":"閲覧","cinema-4d_text":"C4D形式で利用できる高品質なCinema 4Dモデルを多数発見。","discounted":{"link_text":"値下げ中の3Dモデルを見てみる","text":"割引価格で本格的3Dモデルを購入。","title":"値下げ中の3Dモデル"},"low-poly":{"link_text":"ローポリ3Dモデルを閲覧","text":"ビデオゲーム、VR・ARリアルタイムアプリ用のローポリ3Dモデル。","title":"ローポリ3Dモデル"},"pbr":{"link_text":"PBR 3Dモデルを閲覧","text":"物理ベースレンダリングの3Dモデル。","title":"PBR 3Dモデル"},"scripts_apps":{"link_text":"すべて閲覧","text":"3Dモデリングの作業効率を上げる3Dスクリプトとプラグイン。","title":"スクリプト&アプリ"},"sketchup_text":"建築、インテリア、ゲームデザイン用のSketchup 3Dモデルを豊富に閲覧。","textures":{"link_text":"テクスチャを閲覧","text":"3Dモデル準備の手間を省くすぐに使えるテクスチャ。","title":"テクスチャ"},"zbrush_text":"デジタルスカルプト、3Dモデリング、テクスチャリング、ペイント用のZTL形式3Dアセットを探す。"},"meta-description":{"cg":"プロ品質の3Dモデル%{rounded_count}点を販売・購入・無料ダウンロード。ゲーム・映像・VFX・AR/VR・3Dプリント向け。FBX、OBJ、BLEND、GLB対応。","print":"3Dプリンター用データ%{rounded_count}点をダウンロード。無料\u0026有料のSTLデータ・3Dプリントモデル:フィギュア、ミニチュア、実用品など。"},"more":{"designers":{"link_text":"3Dデザイナーリストを閲覧","text":"数千の3Dデザイナーポートフォリオを閲覧し、必要なスキルを見つけましょう。","title":"最高の3Dデザイナーを探す"},"projects":{"link_text":"3Dプロジェクトについてもっと知る","text":"優秀なフリーランスの3Dアーティストを雇って、あらゆるカスタムモデリングの課題を解決。","title":"CGTrader 3Dプロジェクト"},"visualization":{"link_text":"詳細を見る","text":"商品をフォトリアルな3Dモデルに変換し、eコマースやビジネス用途で活用。","title":"eコマース用3Dビジュアライゼーション"}},"navbar":{"aircraft":"航空機","animal":"動物","animals":"動物","architectural":"建築","art":"アート","car":"車","character":"キャラクター","exterior":"外観","fashion":"ファッション","food":"食品","furniture":"家具","gadgets":"ガジェット","games-toys":"ゲーム・玩具","hobby-diy":"ホビー・DIY","house":"å®¶","household":"生活用品","industrial":"工業","interior":"インテリア","jewelry":"宝石類","military":"軍事的","miniatures":"ミニチュア","plant":"植物","science":"科学","space":"宇宙","vehicle":"車両","watercraft":"舟艇"},"print_models":{"hero_title_html":"3Dプリンター用データ・STLデータ","subtitle":"ニーズに合った3Dプリントコンテンツを見つけましょう","title_html":"プロ仕様の\u003cstrong\u003e3Dプリントモデル\u003c/strong\u003eを購入"},"profile":{"collection":"3Dパック","free":"無料の3Dモデル","high_poly":"高ポリ3Dモデル","optimized":"低ポリ/ PBR 3Dモデル","printable":"3Dプリントモデル"},"seo":{"cg":{"aircraft":"このカテゴリには3D航空機が含まれます。CG飛行機はシミュレーション、ビジュアライゼーション、広告、映像に適しています。ドローンのボディや部品は小型飛行機ファンを喜ばせます。リギング済みモデルはゲームエンジンにすぐインポート可能です。\n","animals":"3Dの動物コレクション(猫、馬、熊、サイ、ワシ、ライオンなど)がここにあります。CG動物は広告や静的シーンのニーズに対応。ゲーム用のリギング済み動物モデルも用意しています。\n","architectural":"階段、暖炉、3Dモデル、額縁、枕、ランプなどの装飾品は、充実した建築ディテールモデルライブラリで見つかります。インテリアやエクステリアを利用して、デジタルのキッチン、寝室、城の部屋、ダンジョンを構築できます。建築家、エンジニア、インディーゲーム開発者のいずれでも利用可能です。\n","car":"車カテゴリには自動車の3Dモデルが含まれており、ローポリ版はゲームデザイナーに、その他は映画製作者や広告主に魅力的です。クラシックカーからレーシングカー、コンセプトカーまで、多彩な3D自動車モデルがあります。\n","character":"このカテゴリでは、人間、人物、SF、衣類などの3Dモデルが見つかります。心臓や手から、精巧な男性・女性のリアルモデルまで。多くのモデルはリギング済みで、ゲームやアニメに簡単に追加できます。\n","electronics":"エレクトロニクスでは,建築家,インテリアデコレーター,レベルデザイナー,シーンのメーカーや他のクリエイティブのためのカテゴリです。 3Dモデル{ \"slug\": \"smartphone\", \"title\": \"スマートフォン\"},{ \"slug\": \"laptop\", \"title\": \"ラップトップ\"}
16518,{ \"slug\": \"camera\",\"title\": \"カメラ\"},{\"slug\":\"tv\",\"title\":\"テレビ\"},より正確デバイスや技術と現代の生活の強迫観念をキャプチャするのに役立ちます。\n","engineering-parts":"Windowsは,パイプライン,スライドドアのメカニズムとCADのプロジェクト,さらにはDIYの計画のために必要な他のビットは,このカテゴリに分類されます。これは,あなたがANVER,ダイナテックとたFCiのようなグローバルメーカーからの3Dモデルを見つけることができる場所です。\n","exterior":"ファサードや都市建築から、ランドスケープオブジェクト、住宅、産業施設、フルスケールの3D都市モデルまで、建築外観を閲覧できます。一部のアセットはローポリで、ゲームやVR/ARアプリの完成に役立ちます。\n","food":"食品は家庭生活のリアルなシーンを作るのに役立ちます。3Dの果物、野菜、飲み物、食事がすべて揃っています。レストラン広告、健康的な食事のためのARアプリ。3Dピザやウェディングケーキのモデルを探していたなら、ここにあります。\n","furniture":"3Dのテーブル、椅子、ソファ、ベッドなどが木材、金属などさまざまなテクスチャやレトロ、モダンなど多様なスタイルで利用可能です。このカテゴリは建築、インテリアデザイン、広告に携わる人向けです。\n","furniture-set":"あなたは同様にアンティークの椅子やソファ見るか,台所用品のフルセットを探して必要な場合は,これはキッチンとオフィスにリビングルーム,ダイニングホールの家具セットから3Dの家具モデルのこのカテゴリコレクトセットとしてある場所です。これは,一致する要素を持つインテリアやレベルデザイン専門ビルド部屋に役立ちます。\n","household":"家庭用3Dアイテムは部屋にディテールを加えるために使用されます。食卓に3D食器セットを置いたり、塩胡椒入れを追加してキッチンを完成させたり、ベッドの横にライトや花を飾って高級感を演出できます。\n","industrial":"建築資材、エンジニアリングツール、機械、コンテナなど、環境ディテールとインタラクティブなリギング済み要素を提供します。シミュレーションゲーム開発者や工事現場レベルを構築する人に最適です。\n","interior":"このカテゴリには、建築、建設、インテリアデザインのリアルな3D要素や詳細が含まれます。これらの3Dモデルはアニメーションやゲームに使用でき、一部はリギング済みでローポリです。\n","military":"このカテゴリでは、武器、防具、剣、大砲などの3Dモデルを購入できます。現代から歴史的なものまで、軍用や自衛用に利用可能です。ゲーム用にポリゴン最適化されたモデルもあり、すぐにゲームエンジンで使用できます。\n","plant":"このカテゴリには、花、木、草、つる植物、緑、ハーブ、多年草、苗、芽、低木、挿し木、若芽、ブドウ、雑草などの植物の3Dモデルが含まれます。3Dモデルを購入または無料でダウンロードするには、以下のカテゴリを閲覧してください。\n","scanned":"モデラーはRealSenseのような3Dスキャン技術を使用してこれらのモデルを作成します。ツリーでも,男性と女性がここに人気があり,彫像を切り株。製品は,リアリズムの比類のないレベルを提供し,シーンやアニメーションで使用できるようになりました。\n","scripts-plugins":"スクリプトとプラグインは,ソフトウェアのショートカットを提供します。モデリング,レンダリング,テクスチャリングは - これらのすべては,MayaとV-Rayのようなソフトウェアのすべての種類のために来たプラグインを使用して容易に行うことができます。\n","space":"宇宙カテゴリには、SF宇宙船、小惑星、惑星(地球や科学志向の人向けのリアルなスペースシャトルを含む)のローポリ・ゲーム対応モデルが多数含まれます。3D宇宙モデルはVR宇宙シミュレーターゲームに役立ちます。\n","sport-hobby":"一般のイベント,スポーツや趣味:このカテゴリには,{\"slug\":\"field\",\"title\":\"フィールド\"}に関連しているものをまとめる。 {\"slug\":\"football\",\"title\":\"フットボール\"}にスキー斜面や{\"slug\":\"guitar\",\"title\":\"ギター\"}:{\"slug\":\"piano\",\"title\":\"ピアノ\"}から,これらは,広告,シーンやアニメーションのために有用な3Dモデルがあります。低ポリのバージョンでは,スポーツのビデオゲームのためにそこにあります。\n","textures":"美しく作られた3D素材のライブラリを閲覧し、ゲーム対応やフォトリアルなモデルに最適なすぐ使えるテクスチャを見つけ、アセットライブラリを充実させ、3D作品を向上させましょう。\n","various":"これらは,我々はそれらすべてを受け入れるものを作成したので,簡単に,他のカテゴリに収まらなかった3Dモデルです。 { \"title\": \"ロックス\", \"slug\": \"rocks\"}
16518,{ \"slug\": \"clouds\",\"title\":\"雲\"},{ \"title\": \"ロゴ\", \"slug\": \"logo\"},{ \"title\" :\"傘\",\"slug\":\"umbrella\"}およびその他の非分類3Dモデルはここにあります。\n","vehicle":"トラック、救急車、バス、電車、トラクター、自転車、バイクなど—街路をリアルでSF的な車両で満たしたい人や、シミュレーションゲームやポスター制作をする人向けのカテゴリです。\n","watercraft":"水上乗り物カテゴリには、ボート、ヨット、船などの水上交通機関が含まれます。リギング済み3Dモデルはビデオゲームやアニメーションに適し、CGモデルはポリゴン数よりも視覚的忠実度が重要なシーンや他の用途に適しています。\n"},"forwarded":{"cg":{"aircraft":"このカテゴリは3D航空機を対象としています。 CG {\"slug\": \"airplane\",\"title\":\"飛行機\"}は,シミュレーション、視覚化、広告や動画によく使われます。 {\"slug\": \"drone\",\"title\":\"ドローン\"}の本体や部品は小さな空飛ぶ乗り物が好きな人を喜ばせます。そして{\"slug\": \"aircraft\", \"title\": \"リグされたモデル\", \"active_filter\": \"rigged\"}はゲームエンジンにインポートできます。","animals":"3Dの動物コレクション -  { \"slug\": \"cat\", \"title\": \"猫\"}、{ \"slug\": \"horse\", \"title\": \"馬\"}、{ \"slug\": \"bear\" , \"title\": \"クマ\"}、{ \"slug\": \"rhino\", \"title\": \"サイ\"}、{ \"slug\": \"eagle\", \"title\": \"ワシ\"}、{ \"slug\": \"lion\", \"title\": \"ライオン\"}などをここで見つけることができます。3DCGの動物は、広告または静的なシーンのニーズを満たすことができます。 ゲーム用に{\"slug\": \"animal\", \"title\": \"リグされた動物\", \"active_filter\": \"rigged\"}のモデルも揃っています。","architectural":"{\"slug\": \"stairs\", \"title\": \"階段\"}、 {\"slug\": \"fireplace\", \"title\":\"暖炉\"}の3Dモデル。また、 {\"slug\": \"frame\", \"title\":\"フレーム\"}、 {\"slug\": \"pillow\", \"title\":\"枕\"}、 {\"slug\": \"lamp\",\"title\":\"ランプ\"}の装飾など、 幅広い建築モデルを見つけることができます。車のカテゴリでは、自動車の3Dモデルや {\"slug\": \"car\", \"title\": \"ローポリ\", \"active_filter\": \"low_poly\"} モデルがライブラリに含まれています。インテリアや家の外観ã
16518®ãƒ‡ã‚¸ã‚¿ãƒ«ã‚­ãƒƒãƒãƒ³ã‚„ベッドルーム、お城の部屋やその地下室など、あなたが建築家、エンジニア、またはインディーズゲームの開発者であっても、ニーズに合わせて様々な構築をすることができます。","car":"車のカテゴリには、自動車の3Dモデルや、その \u003ca href=\"%{link_to_low_poly_items_url}\"\u003eローポリ\u003c/a\u003eバージョンが含まれており、ゲームデザイナーや映画製作者、広告主にとって魅力的です。クラシックカーからスポーツカー、またはトラックやコンセプトカーまで、多種多様な3Dの自動車モデルが揃っています。","character":"このカテゴリでは {\"slug\": \"human\", \"title\":\"人間\"} 、人物、SF、洋服などの3Dモデルを見つけることができます。 {\"slug\": \"heart\", \"title\":\"心臓\"}や{\"slug\": \"hand\", \"title\":\"手\"}また、リアルな{\"slug\": \"male\", \"title\":\"男性\"}モデル{\"slug\": \"female\", \"title\":\"女性\"}まで。多くのモデルがリグされており、簡単にゲームやアニメーションに追加することができます。\\n","electronics":"電子機器カテゴリには、建築家、インテリアコーディネーター、環境デザイナーや、シーンの製作者などのクリエイティビティーな製作に役立つモデルが含まれています。 { \"slug\": \"smartphone\", \"title\": \"スマートフォン\"}、{ \"slug\": \"laptop\", \"title\": \"パソコン\"}、{ \"slug\": \"camera\", \"title\": \"カメラ\"}、{\"slug\": \"tv\", \"title\":\"テレビ\"}などの3Dモデルは、より正確に現代の生活をデバイスや技術で表すことができます。","engineering-parts":"CADのプロジェクトに必要な窓、パイプライン、スライドドアおよび、その他のビットやDIYプランもこのカテゴリに分類されます。ANVER、DynatecやFCiのようなグローバルメーカーの3Dモデルも見つけることができる場所です。","exterior":"町の外観、都市の建物から、風景のオブジェクトや住宅および、産業施設のフルスケール3D {\"slug\": \"city\", \"title\":\"都市\"}モデルまでの建物の外観を閲覧できます。一部のアセットはローポリであり、ゲームやVR / ARアプリケーションの完成に役立ちます。","food":"食品モデルは、家庭での生活のリアルなシーンを作成するのに役立ちます。3Dの果物、野菜、飲み物、料理はすべてこのカテゴリに取り揃えられています。レストランの広告や、健康的な食事のためのARアプリケーションなどにも使えます。あなたが探しているのが{\"slug\": \"pizza\", \"title\":\"ピザ\"}でもウェディング{\"slug\": \"cake\", \"title\":\"ケーキ\"}でも、欲しかったものが何でも見つかるでしょう。","furniture":"3Dテーブル、椅子、ソファーやベッドなどの様々なテクスチャ(木材、金属等)やスタイル({\"slug\": \"retro\", \"title\":\"レトロ\"}、{\"slug\": \"modern\", \"title\":\"モダン\"}など)はここにあります。このカテゴリは、建築やインテリアデザインだけでなく、広告業界にも役立つ商品を取り揃えています。","furniture-set":"アンティーク調の椅子やソファー、台所用品の一式などが必要なら、このカテゴリーがぴったりです。キッチンやオフィスにリビングルーム、さらにはダイニングホールの3D家具モデルをご用意しています。これら3D家具モデルはインテリアまたは環境デザイナーが思い通りの部屋を作りたいときに役立ちます。","household":"家庭用アイテムの3Dモデルは、部屋の詳細な部分を表現するために使用されます。テーブルの上に3Dのディナーセットを置いたり、キッチンのインテリアとして塩、胡椒の容器を設置したりすることができます。さらに、{\"slug\": \"light\", \"title\":\"間接照明\"}や{\"slug\": \"flowers\", \"title\":\"花\"}をベッドの近くに置くことで高級感を演出することもできます。","industrial":"このカテゴリは建設資材、{\"slug\": \"engineering\", \"title\":\"建築\"}機材、機械、{\"slug\": \"container\", \"title\":\"コンテナ\"}のように工業環境と、それに関係する道具類を提供します。これらはシミュレーションゲームの開発者や{\"slug\": \"construction\", \"title\":\"建設\"}の構築をする人に最適です。","interior":"このカテゴリには、リアルな3Dの要素や建築、建設、そして工学的なインテリアデザインのディテールが含まれています。これらの3Dモデルは一部リギングされ、ローポリゴンのためアニメやゲームにも使用できます。","military":"このカテゴリでは、武器、防具、 {\"slug\": \"sword\", \"title\": \"剣\"} 、大砲などの3Dモデルを購入することができます。これらのモデルは現代に使用されている、または歴史的に軍隊や自衛隊で使用したものを参考にしています。そのうちのいくつかはポリゴンが最適化され、ゲームエンジンに対応しているので、ゲーム上で使用できます。","plant":"{\"slug\": \"tree\", \"title\":\"植物\"}このカテゴリには、花、木、芝、ツル、緑の葉や枝、ハーブ、多年生植物、苗木、新芽、低木、さし枝、双葉、つる植物、雑草などの植物の3Dモデルが含まれます。3Dモデルを購入または無料でダウンロードするには、以下のカテゴリを参照してください。\\n","scanned":"模型の製作者はリアルな3Dスキャン技術を使用してこれらのモデルを作成します。木の切り株、彫刻、そして男性と女性のモデルは特に人気があります。現実世界と変わらないクオリティーのものを、映像やアニメーションで使用できます。","scie
16518nce":"このカテゴリには、医学、生物学、物理学、化学、といった技術に関連する3Dモデルが含まれています。{\"slug\": \"cell\", \"title\":\"細胞\"}や{\"slug\": \"skull\", \"title\":\"頭蓋骨\"}、そして{\"slug\": \"skeleton\", \"title\":\"骨格\"}の3Dモデルがここに揃っているので、色々なサイトでそれぞれを探す必要はもうありません。","scripts-plugins":"スクリプトやプラグインで、ソフトウェアのショートカットを作ることができます。モデリング、レンダリング、テクスチャリングはすべて、MayaやV-Rayのようなあらゆる種類のソフトウェアに付属するプラグインを使用して容易に行うことができます。","space":"宇宙のカテゴリーは、よりリアルな{\"slug\": \"spaceship\", \"title\":\"宇宙船\"}、{\"slug\": \"asteroid\", \"title\":\"小惑星\"}を含む惑星({\"slug\": \"earth\", \"title\": \"地球\"}やリアルな{\"slug\": \"shuttle\", \"title\":\"スペースシャトル\"}など)があり、ゲーム対応のローポリモデルが多数あります。3D空間モデルはVR空間シミュレーターゲームに役立ちます。","sport-hobby":"このカテゴリには、 {\"slug\": \"field\", \"title\":\"野外\"} イベント、スポーツ、趣味全般に関連しているものがまとめられています。 {\"slug\": \"football\", \"title\":\"サッカー\"}、スキー、{\"slug\": \"guitar\", \"title\":\"ギター\"}や{\"slug\": \"piano\", \"title\":\"ピアノ\"}まで、広告または映像やアニメーションに役立つ3Dモデルが揃っています。ローポリのバージョンは、スポーツ系のビデオゲームに使用することができます。","textures":"美しく作られた3D素材のライブラリを参照し、すぐに使用できる最適なテクスチャを見つけ、\u003ca href=\"%{link_to_low_poly_items_url}\"\u003eゲーム対応\u003c/a\u003eまたは写真のようにリアルなモデルを探しましょう。これらのモデルはあなたのアセットライブラリをアップグレードし、あなたの3D作品の質を格段に向上させます。","various":"こちらに含まれているのは、他のカテゴリに収まらなかった3Dモデルです。{\"slug\": \"rocks\", \"title\": \"岩\"}、 {\"slug\": \"clouds\", \"title\": \"雲\"} 、{\"slug\": \"logos\", \"title\": \" ロゴ\"}、{\"slug\": \"umbrellas\", \"title\": \"傘\"}および、その他の分類されていない3Dモデルが見つかります。","vehicle":"このカテゴリにはトラック、 {\"slug\": \"ambulance\", \"title\": \"救急車\"} 、 バス、 電車、 トラクター、 {\"title\": \"自転車\", \"slug\": \"bike\"} やオートバイが含まれます。街の通りをリアルな車やSF系の乗り物でいっぱいにしたい人、またはシミュレーションゲームや、ポスターのフォーマットを作成する人向けです。","watercraft":"船舶のカテゴリには、{\"slug\": \"boat\", \"title\": \"小型船\"}、{\"slug\": \"yacht\", \"title\": \"ヨット\"}、{\"slug\": \"ship\", \"title\": \"フェリー\"}などの水上車両が含まれます。リグされた3Dモデルは、主にビデオゲームやアニメーションで使用でき、CGモデルは映像やその他のアプリでポリゴン数よりも視覚的な忠実度が重要な場合に便利です。"},"printable":{"art":"芸術的な3Dプリントが行きつく場所。古典的な作品や歴史的な人物の彫像、または刺激的な置物、豪華な彫刻デザインなどをお探しですか?それならこのカテゴリがぴったりです。さらに、3D印刷可能なロゴもあります。","fashion":"このカテゴリでは美しくスタイリッシュな服を見つけることができます。空想の中にある宇宙時代の{\"slug\": \"footwear\", \"title\":\"履物\"}だったり、見たことのない眼鏡や、コスプレ用の{\"slug\": \"accessories\", \"title\":\"アクセサリー\"}などを閲覧しましょう。 ","gadgets":"このガジェットカテゴリでは、すべての{ \"title\": \"iPhone\", \"slug\": \"iphone\"}、{ \"title\": \"iPadの\", \"slug\": \"ipad\"}(および、その他のスマートデバイス)を見つけることができます。シンプルな3Dアドオンをより緻密なものにし、アイテムの\"カスタム\"までできます。クリエイティブな人やãƒ
16518†ã‚¯ãƒŽãƒ­ã‚¸ãƒ¼å¥½ãã®ãŸã‚ã®ã‚«ãƒ†ã‚´ãƒªã§ã™ã€‚","games-toys":"印刷可能な3Dエイリアン、ミニチュアのモンスター、小さな {\"slug\": \"tank\", \"title\":  \"戦車\"} や、一風変わった {\"slug\": \"chess\",\"title\":\"チェス\"} 、映画のキャラクターのヘルメット、趣味で集めるような道具やアクセサリーなどを含むゲームやおもちゃのカテゴリーです。","hobby-diy":"アイディアマンやDIYプロジェクトを構築する人、 {\"slug\": \"drone\",\"title\":\"ドローン\"}のスペアパーツが欲しい人にはこちらのカテゴリーがぴったりです。さらに、自分用のフィギュアやプラスチック銃のレプリカを作りたい人のためのモデルなど、様々な種類を提供しています。","house":"{\"slug\": \"Lamps\", \"title\":  \"ランプ\"}、アーティスティックな{\"slug\": \"bowl\", \"title\": \"お皿\"}、及びのあらゆる種類の{\"slug\": \"holder\", \"title\": \"入れ物\"}や小物は、ここで見つけることができます。机の上には電話、その他ペンやつまようじ、宝石類などを置くことができます。","jewelry":"ダイヤモンドがついているブレスレットやペンダント、またはリングなどがあるジュエリーのカテゴリーです。3Dプリンタでダイヤモンドを印刷する技術はまだ開発されていませんが、IT好きの人がプロポーズする為の金属のリングのベースを作るにはピッタリかもしれません。","miniatures":"3Dプリント可能な{ \"slug\": \"character\", \"title\": \"文字\"}、有名人、{ \"slug\": \"landmark\", \"title\": \"標識\"}、動物、{ \"slug\": \"male\", \"title\": \"男性\"}、{ \"slug\": \"female\", \"title\": \"女性\"}、{ \"slug\": \"monster\", \"title\": \"モンスター\"}、{ \"slug\": \"alien\", \"title\": \"外国人\"}などはすべてこのカテゴリにあります。あなたがテーブルの上に置けるような独自のビークルを印刷したり、テーブルゲームのミニチュアを作成する場合もこのカテゴリがぴったりです。","science":"科学に分類されるモデルと言われても想像が付きづらいですか? {\"slug\": \"bones\", \"title\":\"骨格\"} 模型のモデル、医師が使う {\"slug\": \"tools\", \"title\":\"道具\"} 、天体物理学に関連する物など、多くの研究プロセスで使用できる3Dプリント可能なモデルが見つかるカテゴリです。"}},"printable":{"art":"芸術的な3Dプリントが行くところです。でも3D-印刷可能なロゴ多分,歴史的な性格の古典作品や彫像を再作成,または人形を鼓舞派手な彫刻のデザインをお探しですか?それはカテゴリです。\n","fashion":"あなたが着ることができる美しくスタイリッシュなものが,このカテゴリで発見されています。あなたは空想の宇宙時代{\"slug\":\"footwear\",\"title\":\"履物\"}のための任意の使用を見つけることができるかどうかを確認し,新規性ガラスやコスプレ{\"slug\":\"accessories\",\"title\":\"付属品\"}こちらをご覧ください。\n","gadgets":"すべて{ \"title\": \"iPhone\", \"slug\": \"iphone\"},{ \"title\": \"iPadの\", \"slug\": \"ipad\"}(のおよび他のスマートデバイス)の場合は,ガジェットのカテゴリーで見つけることができます。シンプルな3Dからアドオン複雑な部品および\"ハック\"にあなたの項目のために,これはクリエイティブ,技術の愛好家のためのカテゴリです。\n","games-toys":"ゲーム,おもちゃ,印刷可能な3Dエイリアンやモンスターのミニチュアをカバーし,カテゴリ,小さな{\"title\":\"戦車\",\"slug\":\"tank\"}で,ファンキー{\"slug\":\"chess\",\"title\":\"チェス\"}作品,映画のキャラクターヘルメット,趣味道具やアクセサリー。これは,すべての物事のオタクのためのto-Goのカテゴリです。\n","hobby-diy":"あなたが私たちの次の偉大な発明者であり,あなたのDIYプロジェクトを構築したり,{\"slug\":\"drone\",\"title\":\"ドローン\"}のためのいくつかのスペアパーツを取得したい場合は,これはある場所です。さらに,それは自分のフィギュアやプラスチック銃のレプリカを作りたい人のためにたくさんのものを提供しています。\n","house":"小物のすべての種類は,ここで見つけることができます。 {\"title\":\"ランプ\",\"slug\":\"lamp\"},芸術{\"title\":\"ボウル\",\"slug\":\"bowl\"},及びのすべての種類{\"title\":\"ホルダ\", \"slug\":\"holder\"}あなたは,あなたの携帯電話のための右の場所を見つける爪楊枝,あなたの机の上にペンや宝石の山の秩序を維持することはできませんか。\n","jewelry":"あなたはダイヤモンドブレスレットやペンダントやリングを得るために行くところジュエリーです。まだ -   -  3Dプリンタは,ダイヤモンドを作るための良い十分ではありませんが,あなたはまだあなたのITオタクの提案リングのベースとして金属製のリングをプリントアウトすることができます。\n","miniatures":"3D印刷可能{ \"title\": \"文字\", \"slug\": \"character\"},有名な人,{ \"title\": \"ランドマーク\", \"slug\": \"landmark\"},動物,{ \"title\":\"男性\",\"slug\": \"male\"}
16518,{ \"slug\": \"female\",\"title\":\"女性\"},{ \"title\": \"モンスター\", \"slug\": \"monster\"},{ \"title\": \"外国人\",\"slug\": \"alien\"}は - それは,ここですべてです。あなたがテーブルの上に置くためにユーティリティビークルを印刷したり,テーブルゲームのためのミニチュアを作成しているかどうか,それはあなたのカテゴリです。\n","science":"何が科学として分類することができますか?さて,スケルトンのモデル{\"slug\":\"bones\",\"title\":\"骨格\"},医師{\"slug\":\"tools\",\"title\":\"ツール\"}と多くの研究の過程で使用することができる天体物理学に関連する3次元印刷可能なモデル。\n"},"titles":{"aircraft":"航空機3Dモデル","animals":"動物3Dモデル","architectural":"建築3Dモデル","car":"車3Dモデル","character":"キャラクター3Dモデル","exterior":"外装3Dモデル","food":"食品3Dモデル","furniture":"家具3Dモデル","household":"家庭用3Dモデル","industrial":"産業用3Dモデル","interior":"インテリア3Dモデル","military":"ミリタリー3Dモデル","plant":"植物3Dモデル","space":"宇宙3Dモデル","textures":"CGテクスチャ3Dモデル","vehicle":"乗り物3Dモデル","watercraft":"船舶3Dモデル"}},"title":{"cg":" 3Dモデル","print":" 3Dプリントファイル"}}
16518,"competition":{"digital-art-competition":{"categories":{"page_description":"キャラクターイラスト、キャラクターコンセプトデザイン、環境イラスト、環境コンセプトデザイン、オブジェクトのデザイン、およびオブジェクトコンセプトデザインカテゴリにあなたの作品を提出することによってCGTraderデジタルアートコンペに参加しています。あなたが異なるカテゴリに合わせて、いくつかの素晴らしい作品を持っている場合は、該当するカテゴリに提出して自由に感じます。そのカテゴリの賞を参照するにはカテゴリーを選択します。"}}},"conversations":{"action_button_confirmation":"このリクエストを本当に再開しますか?","reply_posted":"返信が投稿されました。","with_yourself":"送信者と同じにすることはできません。"},"date":{"month_names":[null,"1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"]},"devise":{"failure":{"too_many_requests":"パスワードリセットのメールは既にリクエストされています。数分待ってから再度お試しください。"}},"disabled_cookies_modal":{"button":"キャンセル","text_1":"あなたは、偉大な3Dモデルの何百万人を探るカートや購入にそれらを追加できなくなります。","text_2":"あなたは完全にCGTraderを体験したいならば、我々はこのサイトでCookieを有効に奨励します。","title":"あなたのブラウザのクッキーが無効になっていそうです"},"emails":{"error_notification":"購読が見つかりません。","success_subscribed_notification":"購読に成功しました。","success_unsubscribed_notification":"購読を正常に解除しました。"},"file_size_validator":{"errors":{"invalid_carrierwave":"CarrierWave::Uploader::Base オブジェクトが必要です","invalid_key":":%{key} は非負の整数でなければなりません","range_format":":in と :within は範囲 (Range) でなければなりません","range_unspecified":"範囲が指定されていません。:within、:maximum、:minimum、または :is オプションを指定してください。"}},"file_type":{"default_error":"ファイルへのパスが指定されていません。"},"footer":{"bottom-row":{"privacy":"プライバシー","terms-link":"利用規約"},"links":{"buyLinks":[{"text":"3Dモデルを購入","link":"/ja/3d-models"},{"text":"ビジネス向け","link":"/discover/business-account"},{"text":"無料の3Dデータセット","link":"/discover/datasets"},{"text":"3Dデザイナーを雇う","link":"/3d-modeling-jobs"},{"text":"無料の3Dモデル","link":"/ja/free-3d-models"},{"text":"無料の3D印刷モデル","link":"/ja/free-3d-print-models"}],"communityLinks":[{"text":"コミュニティ","link":""},{"text":"トップデザイナー","link":"/designers"},{"text":"チャレンジ","link":"/challenges"},{"text":"フォーラム","link":"/forum"}],"companyLinks":[{"text":"会社","link":"/discover/about-us"},{"text":"ブログ","link":"/blog"},{"text":"キャリア","link":"/pages/careers"},{"text":"ヘルプセンター","link":"https://help.cgtrader.com"}],"sellLinks":[{"text":"3Dモデルを販売","link":"/discover/sell-3d-models"},{"text":"売上を伸ばす","link":"/discover/sell-more"},{"text":"3Dマーケットインサイト","link":"/discover/3d-market-trends"},{"text":"人気の3Dモデル検索","link":"/discover/3d-models-top-searches"},{"text":"人気の3D印刷検索","link":"/discover/3d-print-models-top-searches"}]},"subscribe":{"button-active":"登録","button-disabled":"登録しました","header":"当社のニュースに登録:","placeholder":"メールアドレスを入力","terms-link":"利用規約","text":"登録することにより、あなたは当社の(Terms of Use)を読んで同意したとみなされます"}},"general":{"description":"CGTraderは、3Dアーティストやデザイナーが3Dモデルを購入、売却または共有できるオンライン市場です。これは、設立されたと3Dデザイナーとエンジニアのチームによって開発されています。私たちの目標は、3Dの株式市場を民主化し、我々は、オンライン3Dモデルを販売する方法を変更することでã
16518™ã€‚著者は最大のロイヤリティを獲得させることによって、我々は彼らのモチベーションを高め、3Dモデルの品質を高めるために努力しています。","navigation":{"buy_3d_models":"3Dモデルを購入","sell_3d_models":"3Dモデルを販売"},"search_placeholder":"200万以上のモデルを検索","site-search__field":"%{model_count}件以上の3Dモデルを検索","site_title":"3Dモデル通販","site_title_postfix":" | CGTrader"},"generator":{"landing_pages":{"three_d_model":{"defaults":{"closing_cta":{"heading":"AIがあなたの夢を実現します","subtitle":"プロンプトや参考画像を本番環境に対応した3Dモデルに変換し、リファイン・リテクスチャリン グして必要な形式でエクスポートしてください。"},"faq":{"heading":"よくある質問","items":{"commercial":{"answer":"%{object}の3Dモデルの商業利用は、CGTrader AIの有料プランで利用できます。AI生成モデ ルに適用されるライセンス条件については、CGTraderの利用規約をご確認ください。","question":"生成した%{object}モデルを商業目的で使用できますか?"},"formats":{"answer":"%{object}の3DモデルはGLB、FBX、OBJ、STL、USDZ形式で利用できます。","question":"どのファイル形式で%{object}モデルを取得できますか?"},"no_skills":{"answer":"3Dモデリングの知識は不要です。すべてのモデリングは%{object}モデルジェネレーターを通 じて行われます。プロンプトを入力するか画像をアップロードすると、システムが自動的にジ オメトリ、プロポーション、構造を処理します。初心者でも数分で包括的な%{object}の3D モデルを作成できます。","question":"%{object}モデルジェネレーターの使用に3Dモデリングの知識は必要ですか?"},"what_is":{"answer":"CGTrader 3D %{object}モデルジェネレーターは、AIベースのツールで、テキスト指示や参考 画像から%{object}の3Dモデルを作成します。アイデアを分析し、デザイン・プロトタイピン グ・可視化のためのダウンロード可能なモデルを作成します。有料サブスクリプションでは、 より高い月間上限が提供されます。","question":"3D %{object}モデルジェネレーターとは何ですか?"},"why_cgtrader":{"answer":"CGTraderはスムーズな体験、高速な処理時間、高品質な3D出力を提供します。このツールは %{object}の3Dモデルを作成するための信頼性の高いソリューションを求めるプロフェッショ ナルと初心者の両方に適しています。","question":"CGTraderのAI %{object}ジェネレーターを使用する理由は?"}}},"h1":"AI %{object} 3Dモデルジェネレーター","hero_alt":"AI %{object} 3Dモデルジェネレーター","hero_height":617,"hero_width":1080,"how_to":{"heading":"画像またはテキストプロンプトから%{object}を生成する","paragraphs":["%{object}の3Dモデルの設計は非常に簡単です。左サイドバーの左上にあるボタンをクリック して%{object}の画像をアップロードし、3Dモデルを作成します。また、下部の「作成したい ものを説明してください」フィールドにプロンプトを入力することもできます。CGTrader AI がプロンプトを自動的に洗練させるため、短い説明で十分です。","右側のサイドバーでは、%{object}の3Dモデルを細かく制御できます。メッシュタイプ、マテ リアル、プロンプトガイダンス、ファイル形式などの設定を選択してください。テクスチャリン グで%{object}モデルをカスタマイズすることも可能です。テキスト指示または画像を入力した 後、「生成」をクリックしてください。システムの負荷、マテリアル、メッシュタイプ、形状、 ã
16518‚·ãƒ¼ãƒ‰å€¤ã€å“è³ªã«ã‚ˆã£ã¦ç•°ãªã‚Šã¾ã™ãŒã€æ•°åˆ†ã§å‡¦ç†ã•れます。"],"steps":["テキスト説明を入力し、参考画像を添付することで、%{object}の3Dモデルの最終的な精度を 向上させてください。","プリセットから選択するか、AIエンジンを選択し、カスタムポリゴン数を設定して、%{object} の3Dモデルのマテリアルタイプを選択してください。","「生成」をクリックして、プロンプトと参考画像から%{object}の3Dモデルを作成してください。","作成した%{object}の3DモデルをGLB、FBX、OBJ、STL、USDZなどのさまざまな形式でダウン ロードしてください。"]},"intro":"数分で高品質な%{object}の3Dモデルを作成する簡単で迅速な方法をお探しですか?高度な %{object} 3Dモデルジェネレーター「CGTrader AI」は、テキスト指示や参考画像を完全に 機能するモデルに変換します。Meshy、Hunyuan、Tripo3Dなどの最先端の3D生成エンジンで 動作し、デザイン初心者から専門家まで、可視化・実験・プロトタイピングのための3Dモデル を素早く作成できます。テキストから3Dおよび画像から3Dに対応し、3Dプリンターとも互換性 があります。JPGまたはPNG形式の画像をアップロードするか、テキストで説明を入力してくだ さい。CGTrader AIが残りの手順を代わりに行います。","meta_description":"CGTrader AI %{object} 3Dモデルジェネレーター ☑️ テキストや画像から精細な%{object}の3Dモデルを生成。今すぐ無料で始めましょう。","primary_cta":"無料で生成を開始","title":"AI %{object} 3Dモデルジェネレーター(無料)| CGTrader"}}},"rules":{"errors":{"front_view_required":"正面ビューの画像が必要です。"}}},"home":{"index":{"3D-projects":{"info-section__cta":"もっと詳しく知る","info-section__description":"エキサイティングな3Dプロジェクトを持つ人々が有能な3Dデザイナーと出会う場所","info-section__heading_html":"\u003cstrong\u003e 3Dプロジェクト\u003c/strong\u003eフリーランスハブ"},"categories-conatainer":{"browse_all":"すべてのモデルを閲覧","browse_all_tooltip":"すべての3Dモデルをブラウズ","discounts":"割引"},"cgdream":{"cta":"無料で始めましょう","heading_html":"\u003cstrong\u003eCGDream を探索,\u003c/strong\u003e\u003c/br\u003e ここでAIと3Dモデルが出会う。","text_html":"\u003cstrong\u003e CGDream \u003c/strong\u003eは、生成AIおよび3Dモデルの力を活用して、見事な2D画像を生成する最先端のアプリケーションです。 \u003cstrong\u003e あなたの創造的な旅が今始まります。\u003c/strong\u003e"},"community":{"info-section__cta":"コミュニティに参加","info-section__heading_html":"CGTraderの\u003cstrong\u003eコミュニティ\u003c/strong\u003eに参加","info-section__icon-list_1":{"description":"レンダリングあなたの最高のショーケース、フィードバックとの認識を得ます","heading":"ギャラリー"},"info-section__icon-list_2":{"description":"新しいスキルを学び、他の人に教える","heading":"チュートリアル"},"info-section__icon-list_3":{"description":"チュートリアルを見つけ、他の3D関連の問題を議論する","heading":"フォーラム"}},"covid":{"timer-strip__item":"COVID-19の保護のための無料の3Dプリントが可能なリソース","timer-strip__item--button":"3Dモデルをブラウズ"},"enterprise":{"info-section__cta":"もっと詳しく知る","info-section__heading_html":"\u003cstrong\u003eエンタープライズ\u003c/strong\u003e向けの3Dサービス","info-section__icon-list_1":"私たちはあなたのためのAR実装を行うことができます","info-section__icon-list_2":"より多くの販売コンバージョン、よち少ない返品","info-section__icon-list_3":"厳格なQAで品質を保証されています"},"header":"VR / AR、3Dプリントとコンピューtagラフィックスのための3Dモデル","marketplace":{"info-section__description":"ゲーム、AR / VR、広告、アニメーション、映画、3Dプリントなどのアプリケーションを3Dモデル%{models_count}から選択","info-section__heading_html":"3Dモデル\u003cstrong\u003e市場\u003c/strong\u003e","info-section__image-list_1":{"de
16518scription":"あなたのプロジェクトのためのプロフェッショナルな3Dモデルを購入","heading":"3Dモデル"},"info-section__image-list_2":{"description":"ビデオゲーム、VR&ARリアルタイムアプリケーションのための低ポリPBRの3Dモデル","heading":"低ポリPBR 3Dモデル"},"info-section__image-list_3":{"description":"3Dプリンターのための3Dプリントデザインを見つけられます。","heading":"3Dプリントモデル"},"view_all":"すべて表示"},"search_dropdown":{"ai_cards":{"image_to_3d":{"description":"画像を3Dモデルに変換","title":"画像から3D"},"text_to_3d":{"description":"アイデアを説明して3Dモデルを作成","title":"テキストから3D"}},"ai_section_title":"AIで作成","aria_label":"検索候補","columns":{"cg":{"search_label":"3Dモデルを検索","title":"3Dモデル"},"print":{"search_label":"3Dプリントを検索","title":"3Dプリントモデル"}}},"sell":{"info-section__heading_html":"3Dモデルを\u003cstrong\u003e販売\u003c/strong\u003eし、フリーランスプロジェクトを見つける","info-section__icon-list_1":{"description":"3Dモデルを販売して85%の印税まで獲得します","link":"3Dモデルを販売\u003e"},"info-section__icon-list_2":{"description":"エンタープライズクライアントと仕事を一緒に働く","link":"\u003e認定を取得"},"info-section__icon-list_3":{"description":"スキルを外注して95%の印税まで取得します","link":"3Dモデリングジョブズをブラウズ"},"info-section__icon-list_4":{"description":"自分のポートフォリオを構築し、表示されるようになろう","link":"3Dデザイナーをブラウズ\u003e"}},"seo":{"description":"200万以上のオンライン3Dモデルを探索しましょう。プリント、ゲーム、VR/AR、デザインプロジェクト用。CGTraderでダウンロード、カスタマイズ、または3Dエキスパートを雇用できます。","title":"CGTrader - 3Dモデルと3Dプリントマーケットプレイス"}}},"homepage":{"budget_counter":{"range_0_10":"10ドル未満","range_10_20":"10ドル - 20ドル","range_20_15000":"20ドル以上"},"quotes":{"quote_0":{"author":{"name":"フアン・カルロス・ヒメネス","username":"CGShape"},"text":"低い手数料は売り手手段からバイヤーのためのより良い価格を撮影しました。チェックアウトし、CGTraderの価格やその他のお店を比較します。"},"quote_1":{"author":{"name":"フアン・カルロス・ヒメネス","username":"CGShape"},"text":"CGTraderの最も顕著な利点は、高い収益をもたらす定額制アクティベーションシステムです。 あなたは当社の売上の手数料の割合を取っていません。 セール終了後、100%の代金を頂戴いたします。 その製品の販売を継続する場合にのみ、5ドルを支払うことで再度有効化できます。 エレガントなソリューションです。"},"quote_10":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"定額制アクティベーションシステムは独創的です。 なぜこれまで誰もこれを考えなã
16518‹ã£ãŸã®ã‹ã¨æ€ã£ã¦ã„ました。 販売後、製品をアクティベートするために少額の手数料を支払うだけです。 売り手は完全に制御されています。"},"quote_11":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"CGTraderについて私が最も気に入っている点は2つあります。 まず、ここには何も隠された意味はありません。 CGTraderに注意する必要はありません。 2つ目は、このストアはアーティストにより、アーティストのためにです。 彼らはあなたをよりよく理解しています。 何かがあなたをここで悩ませていますか?...彼らに知らせてください、そして彼らはあなたがここで快適に感じることを確認します。"},"quote_12":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"定額制アクティベーションシステムが最高! (少なくとも私はこれより良いものに遭遇していません)。 それは売り手にとって非常に現実的で公平です。 彼らはあなたがどれだけの利益を上げるかを気にせず、固定家賃を支払うだけです。 ただし、売り上げがない場合でも家賃を払う必要はありません。 フェアCGTraderはこちら!"},"quote_13":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"価格が他の店よりも安いため、CGTraderは発展途上国からでも顧客を引き付けます。 同時に、高い手数料率などがないため、著者はより多くの収入を得ることができます。 これは、3Dアーティストの作品を評価する正しい方法です。"},"quote_14":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"パブリッシャーの直感的なインターフェイスと、tagの推奨や柔軟な説明フォームなどの革新的なツールにより、パブリッシングプロセスが簡単かつ迅速になります。"},"quote_15":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"3Dを販売するCGTraderのアプローチは異なり、私が以前販売していた他のストアと共通点はありません。 セール後に5 $の低料金を支払うのは素晴らしいアイデアであり、ここで最低価格を提供することができます。 顧客とアーティストは、このような働き方の恩恵を受けています。"},"quote_16":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"アップロードシステムは簡単で明確です。 プロジェクトの背後には、コンピューtagラフィックの問題で長年の経験を持つ人々がいることを知っておくのは良いことです。 問題が発生したときはいつでも、CGTraderチームが迅速に修正しました。"},"quote_17":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"CGTraderを使用すると、自分のプロファイルを個人のウェブページに連結できます。 購入したいアイテムの特性がわからない場合や、変更したい場合は、お客様から連絡を受けることができます。 大きな市場で個人の3Dストアを所有しているようなものです。"},"quote_2":{"author":{"name":"フアン・カルロス・ヒメネス","username":"CGShape"},"text":"私たちは、CGTraderチームから素晴らしいと迅速なサポートを楽しんできました。スタッフが親切、プロの売り手のための大きな助けである私たちのためにすべてのモデルの公開を、行うことを申し出ました。"},"quote_3":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"このプラットフォームでは、お客様により良い価格を提供できます。 例えば、当社のすべての製品の価格は他のどこよりも20%安く、他の店と比べてより多くの利益を得ています!"},"quote_4":{"author":{"name":"ヘスス・ペドロサヴィダル","username":"NoneCG"},"text":"これは、これまでの最適なシステムです。他のすべてのオンラインの3D店は、当社の利益から%を取ります。これは、最初からCGTraderで私たちの関心を引いた機能でした。顧客、CGTraderの買いが、彼らの利益が改善される場合、最終的にはより多くの、より良いモデルを作る作者に彼らのお金を、外出先の大部分が、ことを知っています。"},"quote_5":{"author":{"name":"ヘスス・ペドロサヴィダル","username":"NoneCG"},"text":"CGTraderは、私たちにとって最も成長している売上と利益を生み出したオンライン3Dストアです。 定額制のアクティベーションシステムは、仲介業者ではなくコンテンツ作成者に報酬を与えるため、見つけることができる最高のシステムです。"},"quote_6":{"author":{"name":"RičardasMarozas","username":"Pumper"},"text":"私はPaypalアカウントに160ドルを受け取ったとき、これまでに起こったことがないので何が起こっているのかわかりませんでしたが、それはCGTraderセールの可能性があると思い、正しかったです。 同じパックを先月Turbosquidで189ドルで販売しましたが、40%の印税と30%の税金の後、CGTraderの160ドルと比較して、52.92ペニーしか得られませんでした。 加えて、お客様は29ドルの支払いを減らしました。これは適切ã
16518ªãƒ“ジネスです。"},"quote_7":{"author":{"name":"Threedケン","username":"3DKen"},"text":"最初、私は、大手オンライン3Dストアの標準ではない、売り手にとってこのようなかなりの取引に懐疑的でした。 定額制アクティベーションシステムについて知ったとき、私はデジタル資産を販売するそのような独創的な方法に感銘を受けました。 $ 5の手数料を支払うことで販売し、アクティブ化します。 それはすべて私には理にかなっています。 私はいくつかをアップロードして試してみましたが、本当に最初のセールを受け取ったとき、それは100%のロイヤルティでした!"},"quote_8":{"author":{"name":"Threedケン","username":"3DKen"},"text":"私はCGTraderを発見するまで、30%から60%の手数料を請求する他の多くのサイトに慣れていました。 売り手として、私は間違いなく高い割合のロイヤルティの返品が好きです。"},"quote_9":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"売り手の体験は素晴らしいです。 製品の提出は簡単です。 そして、はい、システムにより、製品提出のドラフトをあらゆる段階で保存することができました。"}}},"hyperwallet":{"failed_payment_agreement":"予期しない問題が発生しました","success_notification":"支払い契約が正常に更新されました。","success_payment_agreement":"支払い契約が正常に送信されました。"},"item-search-filters":{"3d-models-url":"3d-models","3d-print-models-url":"3d-print-models","dedicated-search-page-url":{"animated":"/animated-","free":"/free-","low_poly":"/low-poly-","pbr":"/pbr-","rigged":"/rigged-"}},"item_use_case":{"not_valid":"無効なファイル","pundit":{"not_authorized":"この操作を実行する権限がありません"},"title":"%{item_name}のユースケース"},"items":{"already_purchased":"このアイテムはすでに購入済みです。","filters":{"attributes":"属性","free":"無料","max_value":"$ + 500","min_value":"$ 2","reset":"フィルタをリセット"},"footer":{"next":"次","per_page":"1ページあたり:","previous":"前"},"geometry_type_forbidden":"ジオメトリタイプは禁止されています!","last_card":{"button_text":"カスタム3Dモデルをリクエスト","text":"お探しのものが見つかりませんでしたか?"},"migrations":{"errors":{"invalid_owner":"新しい所有者はアイテムを引き継ぐことができません。","unpublished_items":"すべてのアイテムを公開する必要があります。"}},"new_price_applied":"新しい価格がすべてのアイテムに正常に適用されました。","new_price_applied_to_selected_items":"新しい価格が選択されたアイテムに正常に適用されました。","new_prices_to_filtered_models":"新しい価格がすべてのフィルタ済みアイテムに正常に適用されました。","order_processed":"注文が処理されました。","order_processing":"注文を処理中です。","something_went_wrong":"問題が発生しました","title_error":"タイトルに非ラテン文字を含めることはできません","uniq_user":"このアイテムのユースケースはすでに提出されています","upload_error":"アップロードエラー。","validate_license_for_zero_price":"無料アイテムには無効です","validate_minimum_price":"価格のオファーは元の価格の50%未満にすることはできません","verify_printable_formats_error":"少なくとも1つの .stl、.obj または .wrl ファイルをアップロードしてください。","you_can_not_delete_models":"モデルを削除することはできません"},"jobs":{"about":{"hire_designers_page":{"hero":{"description":"3D Projectsなら、優秀な3Dフリーランサーを簡単に採用でき、あらゆるモデリング課題を解決できます。","heading_html":"最高のフリーランスプラットフォームで \u003cstrong\u003e3Dモデリング案件\u003c/strong\u003e を成功させましょう"},"sections":{"call_to_action":{"description":"プロジェクトを無料で投稿して、3Dプロジェクトで次のモデリング課題を簡単に解決できることを実感。","title":"今日最高の3D人材を採用!"},"freelancer_selection":{"title":"次の3Dプロジェクトを成功に導く","widest_selection":{"description":"少ない数で満足しないでください。4ä¸
16518‡äººã®ãƒ•リーランス3Dデザイナーを擁するCGTraderは、モデラー、アニメーター、リガー、3Dプリントのプロなど、有能な専門家とつながるための最大のプラットフォームです。","title":"最も幅広い3Dフリーランサー"}},"how_it_works":{"steps":{"describe":{"description":"必要なものを教えてください。簡単なフォームに入力し、画像を追加して、数分でフリーランス募集を公開。","title":"プロジェクトを説明"},"pay":{"description":"CGTraderに安全に予算を前払いし、フリーランサーに開始準備ができたことを知らせます。チャット、添付共有、進捗確認を専用ワークスペースで便利に行い、最終結果に満足したら支払いを解放。","title":"結果に支払う"},"select":{"bullets":["資格のあるデザイナーからの見積もりを受信トレイで直接受け取る","デザイナーとリアルタイムでチャット","過去のプロジェクトを閲覧","3Dスキル、評価、ソフトやフォーマットの好みに基づいて検索、フィルタし、フリーランサーを招待することもできます"],"title":"最適なフリーランサーを選択"}},"title":"3つのステップで仕事を完了"},"payment_quality":{"description":"CGTraderは、フリーランスサービスが要件を満たし、期限内に納品されることを保証します。QAチームはいつでも対応可能です。","title":"品質保証"},"secure_payments":{"description":"エスクローサービスにより、資金は保護され、最終結果に満足したときにのみ支払います。","title":"安全な支払い"},"workspace_convenience":{"description":"タブやツールを切り替える煩わしさを忘れましょう。専用ワークスペースで、複数プロジェクト、請求書、会話、ファイル共有などを一括管理できます。","title":"シングルスクリーンの利便性"}},"shared":{"post_project_cta":"プロジェクトを投稿"}}
16518,"navigation":{"find_jobs_cta":"3Dの仕事を探す","hire_freelancers_cta":"3Dフリーランサーを雇う","how_it_works":"仕組み","why_projects":"なぜ3Dプロジェクトなのか"}},"agreed_to_terms_of_use":"続行するには、3D Projects の利用規約に同意する必要があります。","budget_error_message":"が高すぎます","check_designer_uniqueness_error":"デザイナーは一度しか招待できません","check_ownership_error":"招待者はこのプロジェクトの所有者ではありません","check_status_error":"デザイナーは承認されたプロジェクトにのみ招待できます。","deadline_error_message":"締め切りは未来の日付でなければなりません","discount_code_error":"申し訳ありませんが、割引コードが存在しないか有効期限が切れています。","email_subject":"3Dプロジェクトの機会: %{subject}","error_check_application_uniqueness":"応募は1回のみ可能です","error_check_applier":"自分自身の3Dプロジェクトに応募することはできません","error_check_job_availability":"ユーザーは応募を許可されていません","error_check_job_status":"プロジェクトの応募は新しい審査プロセスが完了するまで保留されています。通常3営業日以内に完了します。ご理解ありがとうございます!","modeling_request_failed":"モデリングリクエストに失敗しました!","offer_has_been_sent":"ありがとうございます。オファーが送信されました!","offer_is_closed":"オファーは終了しました。","payment_successful":"支払いが正常に完了しました。","payment_under_review":"支払いは審査中です。","share_with_yourself_error":"自分自身と「Hire Me」テンプレートを共有することはできません","system_messages":{"buyer_cancels_job":"プロジェクトはキャンセルされ、%{archive_url} タブに移動されました。","buyer_edits_hire_me_template":"プロジェクトの詳細がクライアントによって更新されました。","buyer_edits_job_details":"プロジェクトの詳細が更新されました。","buyer_marks_job_as_finished":"プロジェクトは完了としてマークされ、%{archive_url} タブに移動されました。","buyer_marks_job_as_finished_with_resell":"クライアントがプロジェクトを完了としてマークし、結果をCGTraderで販売することを許可しました。作成したモデルを必ず\u003cb\u003e\u003ca href=\"/profile/upload/model\"\u003eアップロード\u003c/a\u003e\u003c/b\u003eしてください!","buyer_negotiates":"クライアントがあなたの価格オファーを交渉しました。","buyer_pays_for_the_job":"プロジェクトは前払いされ、%{in_progress_url} タブに移動されました。","designer_accepts_changes":"デザイナーが更新された詳細を確認しました。","designer_accepts_offer":"デザイナーがあなたのオファーを受け入れました。","designer_edits_offer":"デザイナーがオファーを編集しました。","designer_negotiates":"デザイナーがあなたのオファーを交渉しました。","designer_withdraws_offer":"デザイナーがオファーを撤回しました。","job_expires":"プロジェクトは期限切れとなり、%{archive_url} タブに移動されました。"},"template_error":"ジョブが作成されませんでした!","user_not_exists":"そのユーザー名のユーザーは存在しません。","want_that_job":"この仕事が欲しい!"},"kodama":{"invalid_request":"無効なリクエストです!"},"landing":{"consent_not_saved":"同意が保存されませんでした","failure_notification":"API アクセスリクエストの必須項目はすべて有効である必要があります。","success_notification":"リクエストありがとうございます。近日中にご連絡いたします。"},"layout":{"chunk_load_notice":"ページの読み込み中に問題が発生しました。ページを更新してください。","cookies_policy":{"accept":"受け入れます","learn_more":"もっと詳しく知る","text":"弊社のウェブサイトが使用するクッキーは/ダイレクトマーケティングコミュニケーションと統計訪問者のデータとトラックとの対話を収集し、当社のウェブサイトを改善し、あなたのブラウジング体験を向上させます。それらにアクセスすることができ、クッキー、彼らが収集したデータ、およびあなたの権利についての詳細は、当社のクッキーのお知らせをご覧ください。"},"translations_notice":{"cta":"切り替えるEnglish","text":"こんにちは! CGTraderを翻訳しています. 翻訳は進行中であり、一部のセクションは日本語で利用できない場合があります."}},"model":{"already_purchased":"この製品はすでに購入済みです。\u003ca href='/profile/cart/purchased'\u003eショッピングカート\u003c/a\u003eからダウンロードできます。必要であれば、もう一度購入できます。","animated":"アニメーション付き","artist_profile":"アーティストプロフィール","author":"作者","author_get_your_payment":"作者はあなたの支払いの100%を受け取ります。CGTraderはアーティストが最大限の収益を得られるようにし、高品質のコンテンツ販売を奨励します。","author_products":"\u003ca href=\"%{author_url}\"\u003e%{username}\u003c/a\u003e のその他の製品","authorised_seller":"CGTrader認定販売者","authority":"権限","breadcrumb":{"3d_models":"3Dモデル","home":"ホーム"}
16518,"bulk_download":{"download_all":"すべてのファイルをダウンロード","files_included":{"one":"%{count} 個のファイルを含む","other":"%{count} 個のファイルを含む"},"too_large":"このモデルは大きすぎるため、1つのアーカイブとしてダウンロードできません(%{limit}超)。ファイルを個別にダウンロードしてください。"},"buy":"購入","cannot_download":"%{experience_needed}以上の評判ポイントがあるユーザーのみが、%{models}以上の無料モデルをダウンロードできます。レベルと評判についての詳細は\u003ca href='%{reputation_url}' target='_blank'\u003eこちら\u003c/a\u003eをご覧ください。","collection":"3Dパック","contact_author":"作者に連絡","description":"説明","download":"ダウンロード","download_limit_exceeded":"1日のダウンロード制限を超えました。明日もう一度お試しください。この制限は過去12か月以内に購入を行ったユーザーには適用されません。","download_modal":{"chat_with_us":"チャットで問い合わせる","contact_support":"チャットからお問い合わせください。","download_file":"%{name} をダウンロード","empty":"ファイルが見つかりませんでした","generic_error":"問題が発生しました。もう一度お試しください","link_error":"申し訳ありませんが、ダウンロードリンクに問題があるようです。","pbr_ready":"PBR対応","preparing":"ダウンロードを準備しています...","title":"ファイルをダウンロード"},"format_downloads":"3Dソフトウェア形式のダウンロード","game_ready":"ゲーム対応(ローポリ)","geometry":"ジオメトリ","license":"ライセンス","make_offer":"オファーを出す","materials":"マテリアル","messages":{"no_files":"アップロードされたファイルはありません","no_rating":"このモデルにはまだ評価がありません"},"model_details":"3Dモデルの詳細","pbr_textures":"PBRテクスチャ","plugin_used":"使用プラグイン","polygons":"ポリゴン","price":"価格","print_ready":"3Dプリント対応","product_id":"製品ID","product_support":"製品サポート","product_type":"製品タイプ","published":"公開日","rated":{"error":"このモデルはすでに評価済みです","success":"評価いただきありがとうございます"},"rating":"3Dモデルの評価","ratings":{"desc":{"negative":"この3Dモデルを購入したユーザーは期待よりも低品質だと評価しています","positive":"この3Dモデルを購入したユーザーは高品質だと評価しています"},"negative":"低評価","positive":"高評価"},"recently_viewed_models":"最近閲覧したモデル","renderer":"レンダラー","report":"報告する","rigged":"リギング済み","scripts-plugins":"スクリプトとプラグイン","similar_models":"類似モデル","statistics":"統計","subscription":{"limit_reached":"この請求期間のダウンロード上限に達しました。","limit_reached_detailed":"今月のダウンロード上限%{limit}件に達しました。制限は%{reset_date}にリセットされます。 今すぐさらにモデルをダウンロードするには、個別に購入できます。","limit_reached_detailed_no_date":"今月のダウンロード上限%{limit}件に達しました。 今すぐさらにモデルをダウンロードするには、個別に購入できます。"},"tags":"タグ","textures":"テクスチャ","title":"%{item_title} 3Dモデル","unwrapped_uvs":"展開されたUV","uv_mapping":"UVマッピング","vat_notice":"すべての価格はVAT(付加価値税)抜きです","version":"バージョン","vertices":"頂点","vr":"VRモデル"},"models":{"error":{"cannot_be_blank":" を空にすることはできません。"}},"navigation":{"3d-models":{"3d-printing":"3Dプリントファイル","discounts":"割引","find_designers":"適任のデザイナーを探す","free":"無料の3Dモデル","pbr":"PBR","scanned_3d-models":"スキャンした3Dモデル","scripts_apps":"スクリプト・アプリ","show-all":"すã
16518¹ã¦è¡¨ç¤º","textures":"CGテクスチャ","title":"3Dモデル","vr_low-poly":"VR /ローポリ"},"already_added_message":"すでにカートに追加されています","already_purchased_message":"あなたはすでにこのアイテムを購入しています。","aria":{"account":"アカウント","cart":"カート","close_menu":"メニューを閉じる","logo":"CGTrader - 3Dモデル / 3Dデザイナー","messages":"メッセージ","notifications":"通知","open_menu":"メニューを開く","search":"検索","search_by_image":"画像で検索"},"budgets":{"below_10":"$10未満","collections":"パック","free":"無料","more_than_20":"$20以上","more_than_50":"$50以上","sale":"セール中","subscription":"サブスクリプション"},"cart_limit_reached":"カート内の商品数が500件の制限に達しました。お買い物を続けるには、現在の注文を完了し、追加のアイテムのために新しい注文を開始してください。","cart_widget":{"continue_shopping":"買い物を続ける","discount":"企業のクーポンと割引:","discount_applied":"適用された割引:","final":"最終価格:","have":"アイテム数","items":"がショッピングカートに入っています。","left_to_unlock_discounts":"割引をアンロックするにはあと{{leftToUnlock}}","potential_discount":"割引の可能性:","proceed":"チェックアウトに進む","recently_viewed_empty_text":"探索を始めると、最近見たアイテムがここに表示されます。","recently_viewed_empty_title":"まだ何もありません","recently_viewed_tab":"最近見たアイテム","remainer":"CGTraderポイントを有効にするには、追加で:","sale_discounts":"適用された割引:","sale_time_left":"セール終了まで","sale_tooltip":"セールキャンペーンの割引は、カートが${{threshold}}以上に達した場合にのみ適用されます。","sale_tooltip_text":"この商品はセール中ですが、割引はカートの小計が${{threshold}}以上に達したときにのみ適用されます。","text":"ショッピングカートは空です。","title":"ショッピングカート","total":"合計:","used":"使用クレジット:","view":"カートの中身を見る","wishlist_empty_text":"探索を始めて、お気に入りを保存しましょう — ここに表示されます。","wishlist_empty_title":"ウィッシュリストは空です","wishlist_tab":"ウィッシュリスト"},"community":{"awards":"デザイナー賞 🏆","blog":"ブログ","challenges":"課題","community_ratings":"コミュニティ評価","designers":"3Dデザイナー","digital_art":"デジタルアートコンペ","forum":"フォーラム","galleries":"ギャラリー","invite":"友人を招待","printlist":"3D印刷リスト","sell_3d-models":"3Dモデルを販売","title":"コミュニティ","tutorials":"チュートリアル"},"conversations_widget":{"conversations":"会話","messages":{"loading":"受信トレイを読み込んでいます"},"types":{"cgtrader_support":"CGTraderサポート","file_conversion_request":"ファイル変換要求","job_application":"3Dプロジェクト","model_feedback":"製品フィードバック","model_support":"モデルのサポート","offer":"見積もり","personal":"プライベート・メッセージ","promotions":"プロモーション"}},"create_ai":{"community_gallery":"コミュニティギャラリー","free_trial":"無料トライアル","generate_3d_model":"3Dモデルを生成","generation":{"dismiss":"通知を閉じる","error_title":"生成に関する問題","generating_title":"3Dモデルを生成しています","open_model":"モデルを開く","ready_title":"3Dモデルが完成しました!"}},"create_with_ai":"AIで作成","credits_guest_widget":{"create":"今すぐアカウントを作成!","discount":"値引き","for":"の","get":"をしてもらえる","learn_more":"CGTrader・ロイヤルティ・クレジットの詳細については、こちらをご覧ください ","link":"こちら","purchase":"初回購入特典!","sign-up":"サインアップ","text_below_1":"購入時にクレジットを使用することができます\"","text_below_2":"\"マークされたモデルに署名し、価格の30%割引までカバー。"},"credits_user_widget":{"condition_1":"CGTraderポイントは、\u003ci class=\"custom-font-icon custom-font-icon--small icon-wallet\"\u003e\u003c/i\u003e のマークが付いたモデルの購入に使用でき、価格から最大30%の割引を提供します。","condition_2":"ポイントは、カートの合計金額が\u003cstrong\u003e$%{minimum}\u003c/strong\u003e以上の場合にのみ使用できます。","condition_3":"セールやプロモーション中は、ポイントを使用できません。","condition_4":"ポイントには有効期限があることに注意してください。ポイントは\u003cstrong\u003e%{days}\u003c/strong\u003e日後に期限切れになります。","earn_rate":"クレジット獲得率","first_text_1":"初回特典として、購入時にクレジットを利用できます\"","first_text_2":" \"マークされたモデルとその価格の最大30%割引を取得できます。クレジットは、カートの合計金額がドル以上の場合にのみ使用できます。","important":"重要:","learn_more":"もっと詳しく知る","lifetime_spending":"生涯支出:","list_item":"CGTraderで生涯支出を増加させることで、クレジット獲得率が上がります。","message":"クレジットはセールキャンペーンの際に利用することはできませんが、ロイヤリティのレベルを上げ、次の購入用にクレジットを獲得することができます。","more_credits":"どうすれば、より多くのクレジットを獲得できますか?","next_level":"次のレベル:","notice":"注意: CGTraderポイントは現在のセール中には使用できませんが、セール終了後に再び利用可能になります。それまでの間、お得な3Dモデルをぜひチェックしてください \u003ca href=\"https://www.cgtrader.com/3d-models?sale_off=1\"\u003eこちら\u003c/a\u003e。","par_1":"所持CGTraderクレジットは","par_2":"です。","par_2_variation":" セールが利用できない場合に使用できるポイント。","regular_text_1":"購入時にクレジットを利用できます \"","regular_text_2":" \"マークされたモデルに署名し、その価格の最大30%割引します。","title":"CGTraderのロイヤリティ割引"},"enterprise":{"management":"3D資産管理","title":"3Dコンテンツ制作"},"hire_a_freelancer":"フリーランサーを雇う","item_add_message":"カートに追加されました","item_restricted_message":"デザイナーの国 (RU/BY) に対する制裁のため、この商品は購入できません。","navbar":{"actions":{"new":"新着","top_selling":"売れ筋","trending":"トレンド"},"budgets":{"below_10":"$10未満","free":"無料","more_than_20":"$20以上","more_than_50":"$50以上","sale":"セール中","up_to_10":"$10まで"}},"projects":{"3d_customs":"カスタム3D","3d_printing":"3Dプリント","about":"3Dプロジェクトについて","browse":"プロジェクトを閲覧","buyer_perspective":"3Dデザイナーを雇う","for_designers":"デザイナー向け","my_projects":"マイプロジェクト","seller_perspective":"3Dプロジェクトをフリーランサーに依頼する","start":"プロジェクトを開始","work":"CGTraderと一緒に働く"},"sell_3d-models":"3Dモデルを販売","sellers_hub":"販売者ハブ","subscription_includes_item_message":"この
16518商品はすでにあなたのサブスクリプションに含まれています。","tooltips":{"cart":"ショッピングカート","conversations":"会話履歴","discounts":"CGTraderのロイヤリティ割引","notifications":"通知","publisher":"出版社"},"user":{"ban_notification":"このユーザーアカウントは停止されています。詳細についてはサポートにお問い合わせください。","cannot_refer_yourself":"自分自身を紹介することはできません","email_confirmation":"メールアドレスが確認されました。","email_reconfirmation":"はすでに確認されています。サインインしてください","ends_at":"終了日時: %{date}","handle_error_tfa_code_missmatch":"入力されたセキュリティコードが正しくありません。もう一度お試しください。","handle_error_tfa_code_missmatch_last":"セキュリティコードの有効期限が切れています。続行するには、メールを再送信して新しいコードを入力してください。","handle_error_tfa_default":"問題が発生しました。後でもう一度お試しください。","handle_error_tfa_expired":"セキュリティコードの有効期限が切れています。","hundred_percent_royalties_popover":"あなたは一時的な100%収益キャンペーンに参加しています: 新しいモデルの販売収益はキャンペーン終了まで全てあなたに支払われます","invalid_email_message":"続行するには有効なメールアドレスを入力してください。","login":"ログイン","meta_description":"%{username} のCGTrader 3Dデザイナープロフィール、ポートフォリオ、利用可能な3Dモデルをご覧ください。業界トップのモデリングアーティストによる最高の3Dモデルをチェック。","meta_title_suffix":"3Dデザイナー","not_valid_birthday":"誕生日が無効です。","offline":"オフライン","online":"オンライン","page_title":"デザイナープロフィール","rate_models_limit":"本日はこれ以上モデルを評価できません。","semantic_description":"デザイナーの3Dモデル、作品、ポートフォリオをご覧ください。CGTraderで彼の作品をチェック","semantic_title":"デザイナープロフィール","session_failure":"ユーザー名/メールアドレスまたはパスワードが間違っています","session_password_expired":"アカウントの非アクティブによりパスワードの有効期限が切れました。送信されたメールのリンクからパスワードをリセットしてください。","session_two_fa_failure":"コード認証に失敗しました","session_unconfirmed":"あなたのメールアドレスはまだ確認されていません。","token_not_recognized":"トークンが認識されませんでした","twitter_shareable_text":"フォローする価値のある#3dartist","username_error":"スペースや非ラテン文字を含めることはできません"},"user_menu":{"buyer":{"freelancers":"フリーランサーを探す","purchases":"自分の購入品","sell":"3Dモデルを販売","wishlist":"3Dモデル欲しいものリスト"},"dashboard":"管理ダッシュボード","logout":"ログアウト","public_profile":"公開プロフィール","seller":{"analytics":"解析","find_new_projects":"新しい3Dプロジェクトを探す","freelance":"フリーランス3Dプロジェクト","freelance_cgtrader":"CGTraderでフリーランス","models":"自分のモデル","publish_project":"プロジェクトを公開","publishing":"出版","sales":"自分の販売","subscription":"サブスクリプション","wishlist":"3Dモデル欲しいものリスト"},"settings":"設定"}},"owly":{"failed_set_pending":"保留中に設定できませんでした。","failed_to_approve":"承認に失敗しました。","failed_to_suspend":"一時停止に失敗しました。","failed_to_update_license":"ライセンスの更新に失敗しました。","failed_to_update_status":"承認ステータスの更新に失敗しました。"}
16518,"print_models":{"browse_all_makes":"すべての3Dメイクを見る","browse_all_models":"すべてのモデルを閲覧","description":"ゲーム、アート、家庭プロジェクトなどに使える人気の3Dプリント対応モデル、STLファイルを発見。","makes_description":"コミュニティによってアップロードされた最新の3Dメイクを探そう — 3Dモデルが現実世界でどのように見えるかを示す実例です。","makes_title":"新しい3Dメイク","new_description":"コレクションに追加された最新の3Dプリントをチェックしましょう。次のプロジェクトにすぐにダウンロードして印刷できる新しいデザインです。","new_title":"新しい3Dプリントモデル","title":"人気の3Dプリントモデル"},"profile":{"account_settings":{"account":{"email":"メールアドレス","submit":"保存","title":"プロフィール","update":{"updated":"プロフィールが更新されました"},"username":"ユーザー名"},"email_settings":{"descriptions":{"ai_studio_notifications":"CGTrader AI の生成に関する通知(まだ開いていないモデルの準備が完了した場合を含む)","communication":"他のCGTraderユーザーとのコミュニケーションに関する情報(個人メッセージ、新しい価格とカスタムジョブオファー、フィードバック、製品サポートリクエストを含む)","news":"CGTrader製品、新機能の発表、コミュニティニュースに関する情報","onboarding_and_edu":"オンボーディングプロセスとCGTraderを最大限に活用するためのヒントに関する情報","push_notifications":"重要なアカウントアクティビティと更新に関する通知","sales_and_offers":"特別オファー、3Dモデルセール、CGTraderボーナスクレジットに関する情報","seller_sold_item":"販売した3Dモデルと受け取った支払いに関する情報"},"privacy_policy_html":"詳細については、\u003ca href='https://www.cgtrader.com/pages/privacy-policy' 'target='_blank'\u003eプライバシーポリシー\u003c/a\u003eをご覧ください。 ","titles":{"ai_studio_notifications":"CGTrader AI の通知","communication":"コミュニケーション(個人メッセージ、3Dモデルのフィードバック、コミュニティ活動、オファー)","news":"3Dニュース / CGTraderの更新","onboarding_and_edu":"オンボーディングと教育","push_notifications":"重要なアカウントアクティビティと更新に関する通知","sales_and_offers":"セールと特別オファー","seller_sold_item":"売上と支払い情報"},"tracking_attributes":{"ai_studio_notifications":"CGTrader AI の通知","communication":"コミュニケーション","news":"3Dニュース / CGTraderの更新","onboarding_and_edu":"オンボーディングと教育","sales_and_offers":"セールと特別オファー","seller_sold_item":"売上と支払い情報"},"update":{"updated":"メールが正常に更新されました。"}},"gdpr_success_notification":{"opt_in":"ありがとうございます! メールの再購読に成功しました。","opt_out":"ニュースレターの購読を解除しました。この設定は、いつでもプロフィール設定から変更できます。\n"},"location_tooltip_text":"\u003cdiv\u003eこのチェックボックスをオンにすると、プロフィールページに位置情報を表示できます。\u003c/div\u003e \u003cdiv\u003eただし、コンプライアンス上の理由により、いずれの場合でも、あなたが販売者であれば購入者に、購入者であれば販売者に位置情報が表示されることに注意してください。\u003c/div\u003e","moneybookers":{"account":"Skrillアカウント"},"password":{"current_password":"現在のパスワード","new_password":"新しいパスワード","submit":"パスワードを変更","title":"パスワード","updated":"パスワードが正常に更新されました。新しいパスワードでログインできます。","verify_password":"パスワードの確認"},"paypal":{"account":"PayPalアカウント","submit":"保存","title":"PayPal設定","update":{"updated":"PayPal設定が更新されました"}}
16518,"personal_info":{"areas_of_expertise":"専門分野","country":"国","description":"自己紹介","full_name":"氏名","languages":"言語","preffered_tools":"使用ツール","submit":"保存","title":"個人および専門情報","update":{"updated":"情報が正常に更新されました"},"website":"ウェブサイト"},"personalization":{"no_muted_title":"ミュートされたデザイナーはいません","title":"ミュートされたデザイナー","tooltip":"リストされたデザイナーの3Dモデルは検索結果から除外されます。","unmute_designer":{"confirm":"本当に解除しますか?"}},"professional_info":{"allow_private_job_requests":"購入者はカスタム3Dジョブリクエストを送信できるようになります。","buyer_engagement_program_html":"CGTraderポイントでモデルを購入できるようにして、より多くの顧客を引き付けましょう。\u003ca href='https://www.cgtrader.com/pages/cgtrader-loyalty-discounts-explained' target='_blank'\u003e詳細はこちら\u003c/a\u003e\n","pbr_program":"変換されたファイルがダウンロードされた場合のみ、低い収益率が適用されます。","pbr_program_agreement":"%{date}に24か月間のPBRプログラムに参加することに同意しました。詳細については利用規約をご覧ください。ご質問がある場合は、サポートにお問い合わせください。","product_analytics_participation_html":"\u003ca href='https://www.cgtrader.com/profile/analytics'target='_blank'\u003eAnalytics\u003c/a\u003e に参加し、その利用規約に同意します。\n","product_analytics_participation_tooltip_html":"Analyticsに参加し、Analytics利用規約 (\u003ca href='https://www.cgtrader.com/pages/cgtrader-analytics-terms-of-service'target='_blank'\u003eリンク\u003c/a\u003e) に%{accepted_at}に同意しました。参加を終了したい場合は、CGTraderサポートにお問い合わせください。\n"},"security":{"allow_mfa":"アカウントを保護するために2段階認証を有効にする","use_authenticator_app":"2段階認証に認証アプリを使用する","use_authenticator_app_tooltip":"QRコードを認証アプリ(Google Authenticator / Authy)でスキャンして、認証コードを生成します。","use_email":"2段階認証にメールを使用する"},"sidebar":{"general_settigns":"一般設定","member_since":"登録日","paypal":"PayPal","title":"アカウント情報"},"title":"マイアカウント"},"conversations":{"conversations":"会話","messages":{"loading":"ロードの受信トレイ"},"types":{"cgtrader_support":"CGTraderサポート","file_conversion_request":"ファイル変換要求","job_application":"3Dプロジェクト","model_feedback":"製品フィードバック","model_support":"モデルのサポート","offer":"見積もり","personal":"プライベート・メッセージ","promotions":"プロモーション"}},"meta":{"description":"CGTraderで%{username}による高品質な3Dモデルをチェック。ゲーム、VR/AR、ビジュアライゼーション向けのユニークな素材を閲覧できます。","title":"3Dモデルデザイナー:%{username}"},"my_products":{"suspension_appeal":{"reasons":{"not_available":"N/A"}}},"settings_updated":"設定が正常に更新されました。"},"publishing":{"banned_error":"スパムコンテンツを投稿したため、あなたのアカウントは禁止されました。これが間違いだと思われる場合は、[email protected] にお問い合わせください。","files_not_finalized":"1つ以上のファイルのアップロードが完了していないため、公開できません。削除してから、もう一度アップロードしてから公開してください。","spam_suspect":"あなたのアカウントはこのモデルを公開する権限がありません。これが間違いだと思われる場合は、[email protected] にお問い合わせください。"},"rating":{"anonymous_buyer":"匿名の購入者","errors":{"not_verified_buyer":"購入が確認された購入者のみレビューを残すことができます"}},"routes":{"discover":"hakken","search":"search"},"sale_off":{"no_overlapping_campaigns":"キャンペーンの日付が既存のセールキャンペーンと重複しています。","parse_discount_rules_text":"行 #{index} に必須の 'discount:' がありません"},"search":{"not_found":"検索サブスクリプションが見つかりません","subscription_created":"検索サブスクリプションが作成されました","successfully_unsubscribed":"検索サブスクリプションを正常に解除しました"},"subscription_opt_ins":{"lock_period_error":"Some items cannot be opted out before the six-month lock period ends.","missing_items_error":"Items not found: %{items}."},"subscriptions":{"flash":{"active_subscription":"すでに有効なサブスクリプションをお持ちです。","premium_upgrade_unavailable":"Premium へのアップグレードは一時的にご利用いただけません。しばらくしてからもう一度お試しください。","update_payment_method":"サブスクリプションのお支払いに問題があります。新しいサブスクリプションを購入する代わりに、お支払い方法を更新してください。"},"payment_form":{"current_price":"%{price}","features":{"first":"月%{downloads}アセット — モデルのダウンロードまたはAIで生成","second":"%{models_count}以上の必須3Dモデルに即時アクセス","third":"ロイヤリティフリー、AIなし"},"from_price_label":"から","original_price":24.99,"per_model_label":"1モデルあたり","plan_name":"CGTrader Essentials Subscription — 年間プラン、毎月請求","premium":{"features":{"first":"月%{downloads}アセット — モデルのダウンロードまたはAIで生成","fourth":"ロイヤリティフリー、AIなし","second":"%{models_count}以上のアクセス可能な全モデルに即時アクセス","third":"Essentialsに含まれないプロ仕様のモデル"},"plan_name":"CGTrader Premium Subscription — 年間プラン、毎月請求"},"price_unit":"/月","vat_amount":"%{vat_amount}"}},"tag":{"description":{"bottle":"科学者たちは、ワインのボトルの日があなたのために悪いわけではないが、それはウイスキーやバーボンのような強い飲み物に来るとき、私たちは本当に私たちのアルコール摂取量を見なければならないと言います。明らかに、水のペットボトルよりもバールックスウェイクーラーでシャンパンのグラスが、長寿命とビールの生活の可能性を考えるには、合理的な犠牲のように見えるだろう。ボトルï¼
16518ˆã‚ªãƒªãƒ¼ãƒ–オイルのシャンプーから)3Dモデルのすべての種類のワインラックとコルク栓抜きのようなアルコール関連のオブジェクトに至るまでこのカテゴリには、申し出ボトル3Dモデルの幅広い選択を、。","brain":"脳は人間の体内で最大かつ最も複雑な器官の一つです。その複雑な構造と機能のために、脳の解剖学も非常に複雑です。しかし、科学者たちは気が遠くなる秘密を暴く、人間の脳のアーキテクチャに深く掘ります。 3Dデザインに関連する現実的かつ詳細な脳の広い範囲広告、3Dビジュアライゼーション、3Dゲーム、ウェブや他のフィールドのようなコンピュータグラフィックスアプリケーションのための準備ができている3Dモデルをダウンロードできます。","building":"現代の建築家からインスピレーションを取ることが、世界的に有名な建築物のいくつかは、そう、彼らは未来からのビジョンのように見えることに端をカットしているところは不明です。そして、私たちはただ外装部品を意味するものではありません...有能では標準の建物の建設は関係なく、それはオフィスや都市部の建物のかどうかの革新的なインテリアデザインと息をのむような建築の作成になります。 3Dモデルを構築する私たちの選択は、ダウンロードの準備ができていると様々な分野に応用することができます -  3Dアニメーション、グラフィックデザイン、広告、3Dゲームやウェブ。","camera":"写真と撮影は現在、若者の間で最も人気のある職業分野の一つであるが、プロのカメラで誰もがアーティストや写真家です。携帯電話のカメラの品質は年々向上して、我々は、人々はすべてのステップで写真を撮るとスキルを編集する新開発のイメージを持っている人は、本当に祝っている参照してください。人生は3Dモデルを使用すると、レンズの向こう側にいるときに少し違って見えるが、のは夢中ないようにしましょう...詳細カメラの広い範囲んダウンロードは、グラフィックデザイナー、アニメーション、ゲーム開発者が使用するために提供されていますや専門家は、他の3D関連の分野で活躍します。","car":"これらの非常に詳細かつ非常にリアルな3Dの車のモデルを見ると、あなたはほとんどエンジンの轟音を聞くことができます!あなたはコンピュータグラフィックス、アニメーションや他の3D-関連分野に取り組んでいる場合は車両の広い範囲は、3Dモデルは、あなたの人生はそんなに簡単に作ることができます。","cell":"科学者たちは、人間の体は50億から2億兆個の細胞に含まれていると推定しています。植物や動物も、細胞を持っています。私たちは、私たちがカバーに血液、ウイルスおよびDNAから骨髄および細胞壁にすべてを持っていることを、あなたは、この生物のものとボアにしたいが、細胞の3Dモデルの広い範囲ではありません。神経が-ラッキングこと...ではないですか?","chair":"私たちは、許可され、常に彼らは私たちの家庭やオフィスインテリアに追加することができますどのくらいの魅力気づかないために椅子に座ってください。議長は、実際にはどの家庭で家具の中心的作品の一つです。広告や3Dビジュアライゼーションから3Dゲームに - ここでは、高品質と目を引くデザインの椅子様々な分野で適用することができる3Dモデルを見つけることができます。","city":"都市生活は、偏心ゲームである:上海からニューヨークへの橋や公園を寝レースタクシーで破裂通り、近代的な建物、完璧に仕立て事務服で歩行者、...(!マンハッタン)は、ロンドンやパリへ - すべての都市は、そのを持っています自分の魔法。広告、コンピュータグラフィックス、アニメーション、3Dビジュアライゼーションやゲーム - あなたはフィールドの数で使用することができ、詳細な都市3Dモデルのこの豊かな選択から選択することができます。都市生活を探検。","dragon":"ドラゴンは、おそらくファンタジーの世界に住んで最も人気のある生き物です。それは、伝説となっていることを様々な文化、アニメーション、映画、書籍のように多くの神話に登場しました。それは暗い洞窟やダンジョンにその不思議な魅力を追加するとこれは特に、ゲーム開発者に愛されています。現実的なの広い範囲が、ダウンロード可能な広告、3Dビジュアライゼーション、3Dゲーム、ウェブおよび3Dデザインに関連する他のフィールドされたようなコンピュータグラフィックスアプリケーションのための準備ができているドラゴンの3Dモデルを装備しました。","earth":"Googleマップで最近追加された地球のビューの詳細レベルは、人々の数に衝撃を与えました。あなたの家を離れることなく、都市や隠された道を発見し、海、山、砂漠 - あなたは、全体の惑星を探索することができます。それは母なる地球が前に経験していないいくつかの信じられないほどのものです。 3Dデザインに関連したリアルな地球の選択広告、3Dビジュアライゼーション、3Dゲーム、ウェブや他のフィールドのようなコンピュータグラフィックスアプリケーションのための準備ができている3Dモデルをダウンロードできます。","engine":"一部の人々は、単にそれが作る音を聞くことによってエンジンに名前を付けることができます、それは本当に素晴らしいです。かかわらず、それはジェットエンジンや車のエンジンだかどうかの、彼らは我々がそれほど頻繁に使用することが非常に複雑な機械だが、それについては、あまりにも多くの時間思考を費やすことはありません。 (それが働いて停止するまで)のモータは、モータです。このカテゴリには、エンジン、3Dモデルの異なる種類の範囲を持っており、我々はあなたが必要なものを見つけると確信しているので、それはまた、プロペラやシリンダーなどのエンジン部品を含んでいます。","female":"それは、男性と女性の脳の間に有意な差があること、それは全く異なる方法で平均男と女振る舞い、感触と応答することが正常であることをその手段冗談ません。しかし、それは解剖学についてのすべてではありません。女性の魅力の
16518力は否定できない原因となるような愛の物語、映画やゲームは今までに、強い女の子の文字なしでそれを引っ張っていません。ヌード3Dモデルから女性の呼吸器系へ - 現実的な女性の3Dモデルの豊富な選択はあなたが必要なすべてを提供することができます。","hand":"我々は非常に多くのもののために私たちの手を使う理由は、彼らの異常な解剖学です。親指は、単独の9つの別々の筋肉によって制御されます。それは信じがたいですが、初期の手は、今日どんな手よりもエキゾチックだった - いくつかの種は、7本の指を持っていました。人間の体は、大幅に進化してきました。我々は今日それを見るように腕や手は、その初期のバージョンのようなものが見えません。現実的なの広い範囲が、3Dデザインに関連した広告、3Dビジュアライゼーション、3Dゲーム、ウェブや他のフィールドのようなコンピュータグラフィックスアプリケーションのための準備ができている3Dモデルをダウンロードできるの手を装備しました。","head":"人間の解剖学では、ヘッドは、人体の上部にあります。ノー面白い、目、耳、鼻、唇やあごのライン、してください - 私たちは、対称、リアルなヘッド感謝(非常に多くの異なる形状およびサイズがあるにもかかわらず)ので、3Dの人間の頭部のモデリングは、複雑な少しを証明することができます。顔は敏感な部分であり、ちょうどそれを楽しんでいるのは失礼平野だろう。私たちの選択は現実的な男性、女性、子供、ハリーポッターとバットマンまたはダウンロードする準備ができているトム・クルーズとスカーレット・ヨハンソンの頭のような有名人のようにも物語のキャラクターを包含する。","helmet":"あなたの頭部を保護することは、音のアイデアですので、必ず、安全衛生が最初に来ることを覚えておいてください。中世の騎士、ダフトパンク、さらにはアイアンマン摩耗ヘルメット、自転車やビルダーのようなので、定期的なスーパーヒーローはダサいを見て心配してはいけません。あなたは、グラフィックデザイン、3Dアニメーション、3Dゲームやビジュアライゼーションの分野で作業している場合は、ダウンロードのために利用可能であるヘルメットの3Dモデルのこのコレクションは有用であることが分かるだろう。","house":"それは男の家は彼の城であることを古い真実だが、それはその完璧な家を建てるか、理想のマンションを買いに来るとき、我々はそれの上に私たちの頭を失う傾向があるん。幸いなことに、アーキテクチャの詳細、内部と外部からのすべてを網羅家3Dモデルのこの広いスペクトルは、家具に、あなたはその痛みを和らげるために近代的な3Dビジュアライゼーションを作成するのに役立ちます。","human":"あなたは人間の解剖学を探索を開始するとき、あなたは、人々がそれは息を取るような複雑かつ複雑な機械であることを認識しています。すべての身体の部分、筋肉、関節、臓器や骨の組み合わせが、1つのシステムに接続され、何も孤立して働きません!これらの超リアルな3D人間モデルを使用すると、あらゆる角度から解剖学的構造を調べることができます。","lamp":"あなたの家の周りを見て、あなたが所有していることをランプの種類をカウントした場合、あなたは驚かれることでしょう:読書、テーブル、壁、天井、デスク、三脚ランプ...それはあなたの頭のスピンを作るのに十分です!レトロなスタイリッシュな未来への贅沢な近代的なランプから - 彼らは来ることのスタイルは言うまでもありません。閲覧およびダウンロードは、グラフィックデザイン、3D視覚化または任意の他の3D-関連分野で使用するので、このカテゴリでは、我々は、ランプ3Dモデルのすべての種類を持っています。","light":"電球は素晴らしいアイデアの象徴である理由あなたが疑問に思ったことがありますか?確かに、明るいのアイデアが心のアーキテクチャを啓発することができますが、それはそれではありません。街路灯、シャンデリアやランプの他のタイプは、その場所を取るチャンスがなã
16518„だろう。あなたは天井を見上げると...あなたは間違った雷システムを凝視しているので、天才のアイデアは、それはあるかもしれないあなたに当たらない建築光のいくつかの種類で高品質な光の私達の選択を凝視次回の場合あなたは建築、コンピュータグラフィックス、3Dアニメーションやデザインの分野で働く場合の3Dモデルは、便利なことを証明します。","machine":"無コーヒー、ない便利な洗濯洗濯、通りの角にキャッシュポイント、無ジム時間、無高値自動販売機...それは音それは、ありません押し下げん:すべてのマシンが突然動作を停止しない場合には想像?私たちは、そう、それは文字通り世界の回転を停止することをこれらの家電製品や電子ガジェットに使用されています!幸いなことに、機関銃からコンピュータグラフィックス、広告、3Dアニメーション、3Dゲームやウェブでの作業デザイナーに有用であることが分かるだろう発電機の詳細な機械の3Dモデルのカバーのすべての私たちの選択。","man":"男3Dモデルの膨大なコレクションは、ダウンロードすることができます。みんなの後のことを完璧な男を除いて - 男、単純な男、飛ぶ男や平均男に座って、私たちはすべてを持っています。彼は魚の文字だと、私たちは、このアップをもたらすことを憎むが、それは神話であるかもしれない...でもねえ、人間は完璧ではない、素晴らしいことを生まれています。","robot":"R2D2は、おそらく世界で最も有名なロボットですが、それは確かに一つだけではありません。人間のロボットへのおもちゃの産業から、我々は素晴らしいと少し心配でもあるこれらの機械的なマシンのますます広い選択肢を参照してください。これは、コンピュータ、技術、ロボットが人間にあまりにも大きな影響を与えていると主張する人々を理解することは、あまりにも難しいことではありません。現実的なの広い範囲が、3Dデザインに関連した広告、3Dビジュアライゼーション、3Dゲーム、ウェブや他のフィールドのようなコンピュータグラフィックスアプリケーションのための準備ができている3Dモデルをダウンロードできるロボットを装備しました。","ship":"船は強力な船であり、すべてのサイズ、色や形で来ます。彼らは、貨物船、軍艦や客船として分類することができ、それらはすべて深海海域で動作します。あなたが乗り物酔いの不快感を経験している場合、それは輸送のお好きな手段ではないかもしれないが、多くの人々は非常に上品で楽しいとしてボート旅行を考えています。船の3Dモデルの私達の選択は、ダウンロードの準備ができているとも、最も要求の厳しい船長の基準を満たしています。","skeleton":"人間の骨格または骨格系(解剖学)は、出生時に270個の骨で構成され、本体の内部フレームワークです。これは、ボディ構造、保護や運動を提供し、最も複雑な骨格系の一つです。豊富な選択は、解剖学的に正しい現実的な3Dアニメーション、グラフィックデザイン、広告、3Dゲームやウェブでの使用に最適です3Dモデルスケルトンスケールダウンロードできます。","skull":"最古の最も完全な、遺伝的に無傷のヒトの頭蓋骨を超える40メートル海面下絶滅動物の多様囲ま発見されました。それは新世界で見つかった最古の骨格の一つであり、それはまた、身体の主要な骨や歯の完全な頭蓋とセットのすべてが含まれています。人間の頭蓋骨は最近、ファッションとデザインのものとなっているが、あなたが実際にそれをいくつかの考えを与えるとき、彼ら種類メイクの私たちのすべてのグラフィックデザインに使用される頭蓋骨の3Dモデルの解剖学フリークのような表情...このカテゴリの提供広い範囲、3Dアニメーション、3Dゲームや広告。","sofa":"高級またはシンプル、モダンまたはアンティーク、革やシンプルなデザイン?どのようにあなたのソファが好きですか?我々が持っているソファ3Dモデルの範囲は、最悪の完璧主義者を満足させます。あなたはそれについて考えるために停止すると、デザインを選ぶときあなたは創造的でなければならないので、ソファは、すべてのリビングルームの中心であります!","table":"最新の家具の傾向は現代、ファッショナブルなテーブルを選択すると、あまりにもストレスになってきているように多様です。そこには、あなたのささやかなお祭りのための簡単な夕食のテーブルを持っていた時間にするために使用し、それは十分だったが、今日 - テーブルは、文または少なくとも目を引くインテリアのアクセントであると考えられます。この選択は、あなたのすべての気まぐれに応えるために、テーブルの3Dモデルの広大な範囲を提供します。","tower":"すべての都市は、誰もが本当に誇りに思うのだという特殊なタワーがあります。一人ですべての近代的な高層ビル、未来的な建物や観光客が感心という都市景観を聞かせて、エッフェル塔やピサ、タワー、またはロンドンのタワーブリッジを考えてみてください。それは都市、水や電気の塔の工業部品に来るときしかし、誰もが離れて彼らの目を向ける傾向がある...しかし、我々は関係なく、古代の産業や近代的な、すべての建築の創造に感謝しないことを学ぶ必要があります。詳細タワーの範囲3Dモデルをダウンロードでき、広告、3Dビジュアライゼーション、3Dゲーム、ウェブおよび3Dデザインに関連する他のフィールドのようなコンピュータグラフィックスアプリケーションのための準備ができています。","tree":"あなたには、いくつかの木やエキゾチックな植物を植えることを使命にしている場合は、適切な場所に来ています。このカテゴリの申し出自分のプロジェクトにいくつかの緑を追加するために努力しているCGアーティストや建築家、に簡単に使用できる、非常にリアルな3D樹木モデルの範囲。あなたは良い品質を高く評価した場合、我々はあなたの基準を満たしています3Dツリーのプラントモデルのこの選択確信しています!","truck":"トラックを運転すると、都市の交通æ¸
16518‹æ»žã‚’通してあなたの方法を見つける必要がある場合は特に、大変な仕事です。かかわらず、それは消防車、ごみ収集車、食品のトラックや一般的なものだかどうかの、それはこれらの巨大な車を操縦するために偉大なスキルを要します。私たちは、現実的な、高品質のトラックのこの選択3Dモデルは、3Dアニメーション、グラフィックデザイン、広告やその他の3D関連分野での使用に最適であることを確信しています。","tv":"暖炉がプレーヤーとスピーカーとテレビがリビングルーム(とさえ寝室)の王である今、すべての立派な家の目玉だった時がありました。近年ではテレビは、最新技術に追いつきました。その結果、我々は、スマート3Dテレビ、フラットプラズマスクリーン、万の新機能やタッチスクリーンのデザインを持っています。不思議テレビが市場から映画をプッシュされません。広告、3Dビジュアライゼーション、3Dゲーム、ウェブおよび3Dデザインに関連する他のフィールドのようなコンピュータグラフィックスアプリケーションのための準備ができている現実的な、詳細なテレビの3Dモデルの広い範囲がダウンロードできます。"}},"tag_promotion":{"days_left_error":"1から30の間でなければなりません","no_other_pending_promotions_for_same_item_tag":"すでに保留中のプロモーションがあります"},"tooltips":{"buyer_loyalty_program_current_level_html":"現在、あなたはCGTraderに%{total_price_paid}を費やしていると、あなたのクレジットレートを稼ぐ%{buyer_loyalty_rate} です。 \u003cBR/\u003eそれはあなたがそれぞれの購入からクレジットの%{buyer_loyalty_rate}を取得することを意味します。\n","buyer_loyalty_program_html":"これは、あなたが新しいクレジットとしてご購入から取得されますどのくらいの割合です。 \u003cBR/\u003eそれはCGTraderの増加であなたの生涯支出として30%まで増加します。 \u003cBR/\u003e現在、あなたの信用が得る率は%{buyer_loyalty_rate}です。\n","buyer_loyalty_program_next_level_html":"次のレベル%{next_lvl_expensesは}%{next_lvl_expenses_rate}に率を稼ぐあなたのクレジットが増加します。 \u003cBR/\u003eあなたは$%{left_until_next_lvlは}より次のレベルに到達するために購入する必要があります。\n"},"trending_headers":{"models":"他の人気の3Dモデルを見る","print":"他の人気の3Dプリントモデルを見る"},"tutorial":{"destroy":"チュートリアルが正常に削除されました。","submit_for_review_error":"必須項目をすべて入力してください。","submitted":"チュートリアルのご投稿ありがとうございます。審査後、間もなく公開されます。"}},"pt":{"activerecord":{"attributes":{"content_report":{"categories":{"adult":"Adulto ou conteúdo violento","ai_generated":"Conteúdo gerado por IA","copyright":"violação de direitos autorais"}}},"errors":{"messages":{"embedded_script":"Este arquivo não é compatível e não pode ser enviado. Tente outro arquivo."}}},"api":{"profile":{"actions":{"contact":"Contato","follow":"Seguir","follow_tooltip":"Comece a seguir este designer para receber atualizações sobre o conteúdo dele e filtrar seus favoritos nos resultados de busca.","hide":"Ocultar","hire_me":"Contrate-me","invite_client":"Iniciar um projeto","mute_tooltip":"Oculte os modelos deste designer nos resultados de busca.","take_over":"Assumir","unfollow":"Deixar de seguir","unhide":"Mostrar","view_all_models":"Ver todos os modelos do designer"},"private_project_review":"Projeto privado"}},"browser":{"3d_models":"modelos 3D","3d_print":"3D impressão","active_filters":{"polygons":"%{range} polígonos"},"adult_content":"Mostrar Idade +","animated":"Animado","breadcrumb":{"browse_by_type":"Navegar por tipo","collection":"packs 3D","free":"livre","free_models":"Modelos livre 3D","free_print_models":"Modelos de impressão livre 3D","low_poly":"baixo poli","low_poly_models":"VR / AR / baixa-poli 3D Modelos","low_poly_print_models":"Modelos 3D de impressão Low-Poly","model_3d":"modelo 3D","model_3dp":"modelo de impressão 3D","models":"Modelos 3D","models_3d":"modelos 3D","models_3dp":"modelos de impressão 3D","models_variant_0":"Modelos 3D","models_variant_1":"Ativos 3D","models_variant_2":"Modelos VR","models_variant_3":"Modelos CG","models_variant_4":"Loja de modelos 3D","models_variant_5":"Objetos 3D","print_models":"Arquivos de impressão 3D","print_models_variant_0":"Modelos para impressão 3D","print_models_variant_1":"Modelos de impressora 3D","print_models_variant_10":"Modelos 3D para impressão","print_models_variant_11":"Modelos 3D para imprimir","print_models_variant_12":"Arquivos 3D para impressão","print_models_variant_13":"Modelos para impressora 3D","print_models_variant_14":"Modelos para impressão 3D","print_models_variant_15":"Modelos de impressão 3D","print_models_variant_16":"Arquivos 3D imprimíveis","print_models_variant_2":"Modelos 3D para impressão","print_models_variant_3":"Modelos de impressão 3D","print_models_variant_4":"Arquivos para impressão 3D","print_models_variant_5":"Arquivos para impressão 3D","print_models_variant_6":"Arquivos de impressora 3D","print_models_variant_7":"Modelos 3D imprimíveis","print_models_variant_8":"Modelos 3D para imprimir","print_models_variant_9":"Modelos 3D impressos"},"buyer_engagement_program":"descontos","categories":"categorias","category":{"meta":{"3d_models":"%{count} %{category} 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Download de %{count} %{category} modelos de impressão 3D prontos para FDM, SLA, SLS e outros 3D impressoras, formatos disponíveis STL, OBJ, 3DM e muito mais.%{page}","animated_3d_models":"%{count} %{category} animado 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Download de %{count} %{category} gratuitos 3D modelos, disponíveis em MAX, OBJ, FBX, 3DS, formatos de arquivo C4D, pronto para VR / AR, animação, jogos e outros projetos em 3D.%{page}","free_3d_print_models":"Download de %{count} %{category} modelos de impressão 3D gratuitos prontos para FDM, SLA, SLS e outros 3D impressoras, formatos disponíveis STL, OBJ, 3DM e muito mais.%{page}","lowpoly_3d_models":"Baixar %{count} VR / AR / low-poly %{category} modelos 3D pronto para realidade virtual, jogos e outros aplicativos em tempo real, disponíveis em FBX, OBJ, MAX, 3DS, formatos de arquivo C4D.%{page}","rigged_3d_models":"%{count} %{category} fraudada 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"collection":"pack 3D","discounts":"descontos","ext":{"meta":{"3d_models":"Baixar %{count} %{ext} arquivos - %{software_name} modelos 3D pronto para VR / AR, design 3D, animação, jogos e em tempo real aplicativos%{page}","3d_print_models":"Download de %{count} %{ext} arquivos - %{software_name} modelos de impressão 3D prontos para o VR / AR, design 3D, animação, jogos e em tempo real aplicativos%{page}."}}
16518,"filter":"Filtro","filter_button":"aplicar filtros","filters":{"adult_content_only":"Apenas conteúdo adulto","cgt_standard_only":"Padrão CGT","collections_only":"Apenas pacotes","content_filters":"Filtros de conteúdo","designer":"Designer","exclude_3d_print":"Excluir impressão 3D","exclude_adult_content":"Excluir conteúdo adulto","exclude_ai_generated":"Excluir conteúdo gerado por IA","exclude_editorial_license":"Excluir licença editorial","exclude_free":"Excluir grátis","file_format":"Formatos de arquivo","following":"Seguindo","free":"Grátis","has_marmoset":"Possui visualizador 3D","has_video":"Com vídeo","include_ai_generated":"Incluir conteúdo gerado por IA","label":"filtros","poly_count":"Contagem de polígonos","print_ready":"Impressão 3D","stl_files":"Arquivos STL","subscription":"Assinatura","type":"Tipo"},"formats":"formatos","free":"Livre","free_models":"Mostrar modelos gratuitas apenas","free_to_premium":{"main":"modelos 3D de %{category}","postfix":"na lista premium.","prefix":"Você precisa de algo especial? Você pode encontrar melhor qualidade"},"gallery":{"preview":"pré-visualização"},"game_ready":"jogo pronto","high_poly":"De alta poli","items_page_title":"Modelos 3D","license":{"exclude_branded":"Excluir Branded","exclude_editorial":"Excluir Editorial","prompt":"Licença","tooltip_more":"Consulte Mais informação","tooltip_text":" Não mostrar modelos que podem ser usados ​​apenas em não-comercial ou educacional.","tooltip_title":"Excluir Editorial:"},"low-poly":"Baixa-poli","low_poly":"Baixa-poli","meta":{"3d_models":"%{count} modelos 3D, disponível para download no MAX, OBJ, FBX, MB e outros formatos de arquivo, pronto para VR / AR, animação, jogos e outros projetos em 3D.%{page}","3d_print_models":"Visualize arquivos de impressão 3D de todas as categorias no CGTrader. Refine, classifique e filtre objetos de impressão 3D profissionais para atender às suas necessidades e recursos da impressora.","animated_3d_models":"modelos animados para uso em jogos, filmes e outros projetos. Estes modelos vêm pré-embalados com alguns movimentos e ações específicas. Estes ajudam a poupar tempo e recursos valiosos durante o processo de desenvolvimento ou criação de protótipos. Escolha PBR texturizada modelos para se certificar de que eles muito bem em todas as condições de iluminação.\n","free_3d_models":"Baixar %{count} gratuitos modelos 3D, disponível em MAX, OBJ, FBX, 3DS, formatos de arquivo C4D, pronto para VR / AR, animação, jogos e outros projetos em 3D.%{page}","free_3d_print_models":"Download de %{count} modelos de impressão 3D gratuitos prontos para FDM, SLA, SLS e outros 3D impressoras, formatos disponíveis STL, OBJ, 3DM e muito mais. %{page}","lowpoly_3d_models":"modelos poli baixos para download. Esses modelos têm um menor número de polígonos e exigem menos poder de computação. modelos de baixo-poli são mais utilizados em aplicações como VR, AR, jogos, especialmente jogos móveis. Escolha de nossa coleção de modelos manipuladas e animados para o melhor desempenho em seus aplicativos.\n","metaverse":"Digite o metaverso com a nossa coleção de modelos 3D altos e baixos manipulados, animados e disponíveis qualquer formato de arquivo.","omniverse":"Crie seu mundo virtual com nossos modelos 3D prontos para omniverse. Escolha entre os modelos animados, manipulados e PBR com alta e baixa contagem de polígonos.","page_prefix":"Página %{page_number} com","pbr_3d_models":"Fisicamente render base, ou PBR, indica a modelos que são fundidas com precisão e de forma consistente em todas as condições de iluminação, devido à texturização fluxos de trabalho usado no processo. Os desenvolvedores podem facilmente usar esses modelos em todos os principais motores de renderização.\n","rigged_3d_models":"Precisa modelos móveis? Confira nossa coleção de fraudada 3D modelos, que são o seu go-to escolha quando você precisa de um personagem ou objeto para executar únicas, movimentos específicos. Fraudada 3D modelos estão disponíveis em Autodesk 3ds max (.max), Maya (.ma), Softimage (.xsi), Cinema4D (.c4d) e muitos outros formatos de arquivo.\n","snap-ar":"Com nossos modelos 3D, você pode participar de milhões de Snapchatters usando o Snap AR para criar experiências divertidas de realidade aumentada.","spark-ar":"Ilumine seu Facebook e Instagram com uma variedade de modelos Spark AR 3D que são manipulados, animados e prontos para uso.","stream_3d_print_models":"Navegar através de uma variedade de fluxo 3D categorias modelo de impressão para descobrir algumas das mais extraordinárias 3D projetos imprimíveis de profissionais 3D criadores.","textures":"%{count} ativos e materiais de texturas 3D disponíveis para download em vários formatos, incluindo .tga .sbsar .jpeg .png .tiff e outros. %{página}","tiktok":"Torne seus efeitos de vídeo mais impressionantes com os modelos Tiktok 3D. Nossa biblioteca pode atender a todas as suas necessidades criativas e é constantemente atualizada.","unity":"Encontre modelos 3D para o seu projeto de unidade em qualquer formato de arquivo e qualquer contagem de poli. Tudo o que você precisa para criar um ambiente virtual exclusivo.","unreal":"Crie visualizações impressionantes com modelos 3D compatíveis com motores irreais em qualquer formato de arquivo. Nossa biblioteca possui modelos de baixo e alto poli de todas as categorias."},"optimized":"Baixa-poli / PBR","pbr":"PBR","per_page":"Por página","polygons_options":{"custom"
16518:"intervalo personalizado","gt_250k":"250k +","lt_5k":"Até 5k","more_than_250000":"250K+","prompt":"contagem de polígonos","range_0_5000":"Até 5K","range_100000_250000":"100 - 250K","range_10000_50000":"10 - 50K","range_100k_250k":"100k para 250k","range_10k_50k":"10k para 50k","range_50000_100000":"50 - 100K","range_5000_10000":"5 - 10K","range_50k_100k":"50k para 100k","range_5k_10k":"5k a 10k"},"price":"Preço: $%{from}-%{to}","price_range":"Preço","print_ready":"3D imprimir pronto","printable":"Impressão 3D","reset_button":"filtros de reposição","rigged":"fraudada","sale_off":"Em promoção","search_placeholder":"Procure palavras-chave","show_all_formats":"Mostrar todos os formatos","simulation_ready":"Pronto para simulação","sort_by":"Ordenar por","sort_by_options":{"best_match":"Melhor partida","highest_price":"Preço mais alto","lowest_price":"preço mais baixo","newest":"O mais novo","oldest":"mais velho","sales":"Mais vendido","trending":"Tendência"},"subcategory":{"meta":{"3d_models":"%{count} %{category} 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Download de %{count} %{category} modelos de impressão 3D prontos para FDM, SLA, SLS e outros 3D impressoras, formatos disponíveis STL, OBJ, 3DM e muito mais.%{page}","animated_3d_models":"%{count} %{category} animado 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Download de %{count} %{category} gratuitos 3D modelos, disponíveis em MAX, OBJ, FBX, 3DS, formatos de arquivo C4D, pronto para VR / AR, animação, jogos e outros projetos em 3D.%{page}","free_3d_print_models":"Download de %{count} %{category} modelos de impressão 3D gratuitos prontos para FDM, SLA, SLS e outros 3D impressoras, formatos disponíveis STL, OBJ, 3DM e muito mais.%{page}","lowpoly_3d_models":"Baixar VR / AR / Low poly %{tag} modelos 3D pronto para Realidade Virtual, jogos e outros aplicativos em tempo real, disponível em FBX, OBJ, MAX, 3DS, formatos de arquivo C4D.%{page}","rigged_3d_models":"%{count} %{category} fraudada 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"subtitle":{"aircraft":"Modelos %{count} aeronaves 3D estão disponíveis para download em nosso mercado. CG %{airplanes_link} se encaixam em simulações, visualizações, anúncios e vídeos. %{drone_link} corpos e partes irá deliciar os fãs de pequenos veículos voadores. E os %{rigged_link} modelos estão prontos para serem importados para motores de jogo e ser usadas para animação ou projetos VR / AR. Voando jogos, simuladores e vídeos de ambos CG regular e tipos de baixo poli pode ser preenchido com modelos 3D na maioria dos formatos populares, incluindo FBX, OBJ, MAX, 3DS, C4D.","animals":"%{count} de animais CG 3D -%{cats_link}, %{horses_link}, %{bears_link}, %{rhinos_link}, %{eagles_link}, %{lions_link}, fictício %{dragons_link} e muito mais - estão disponíveis aqui para download. A coleção de fauna 3D irá satisfazer as necessidades de anúncios ou cenas estáticas. Temos também %{rigged_link} modelos para jogos, animação, produção de vídeo e VR / projetos AR. Use filtros para encontrar fraudada, animado, low-poly ou livres 3D modelos disponíveis na maioria dos formatos de arquivo aceitos pela indústria, incluindo FBX, OBJ, MAX, 3DS, C4D, e outros.","animated":"Explore modelos 3D totalmente animados de %{tag} para acelerar seu fluxo de trabalho e dar vida instantaneamente aos seus projetos.\n","architectural":"%{count} de arquitectura modelos 3D disponíveis para download contém elementos e detalhes de arquitetura, construção e projeto de engenharia. Para comprar ou baixar um modelo 3D livre, navegar pelas categorias acima. Você pode usar modelos para animação ou jogos - alguns deles são manipuladas e low poly para servir VR / necessidades do projeto AR. modelos 3D estão disponíveis em formatos de arquivo como FBX, OBJ, MAX, 3DS, C4D.","car":"Modelos %{count} carro 3D estão disponíveis para download. versões de baixo poly de que será atrae
16518nte para os designers de jogos ou VR / AR aplicações desenvolvedores, outros - para cineastas, animadores ou anunciantes. De carros clássicos e antigos para o Racing e conceito que foi visto em filmes, há uma grande variedade de modelos de automóveis 3D em praticamente qualquer formato (FBX, OBJ, MAX, 3DS, C4D, etc.) você pode precisar. Use animados e gratuitos 3D modelos de protótipo e tempo de desenvolvimento encurtar.","character":"%{count} caracteres 3D modelos estão disponíveis para download, assim navegar nesta categoria você vai encontrar modelos 3D de %{human_link}, pessoas, figuras míticas, sci-fi %{robots_link}, vestuário e outras coisas relacionadas com caracteres. Do %{heart_link} e %{hand_link} para detalhado realista %{male_link} ou %{female_link} modelos. Um número de modelos são manipuladas e pode ser facilmente adicionado ao seu jogo, VR / projeto AR, vídeo ou animação. Ambos os modelos de baixo poli alta poli e pode ser encontrado em vários formatos, incluindo FBX, OBJ, MAX, 3DS, C4d.","custom":"Aqui você pode comprar ou baixar livre %{tag} modelos 3D. %{tag_heading_subtitle}","electronics":"%{count} eletrônica 3D modelos disponível para download. Esta é uma categoria para arquitetos, decoradores, designers de níveis, fabricantes de cena e outros criativos. modelos 3D de %{smartphones_link}, %{laptops_link}, %{cameras_link}, %{tvs_link} e mais irá ajudar a capturar com precisão a obsessão da vida moderna com dispositivos e tecnologia. Existem modelos 3D disponíveis em formatos de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D. Grátis download ou comprar modelos premium e uso de animação, jogos e projetos VR / AR.","exterior":"%{count} de arquitectura exteriores modelos 3D disponíveis para faixa de transferência a partir fachadas, edifícios urbanos, objetos paisagem, casas residenciais para construções industriais, edifícios de infra-estrutura e %{city_link} modelos em escala completa. Para comprar ou baixar um modelo 3D livre, navegar pelas categorias acima. Use filtros para encontrar fraudada, animado, low-poly, livre modelos 3D ou classificar por formato necessário, incluindo FBX, OBJ, MAX, 3DS, C4D. Alguns dos ativos são low-poly e poderia ajudá-lo a completar o ambiente em seu jogo ou aplicação VR / AR.","food":"%{count} modelos de alimento 3D estão disponíveis para download..Food serve bem para criar cenas realistas da vida doméstica. 3D frutas, legumes, bebidas e refeições são todos aqui para anúncios completos para restaurantes, aplicações AR para uma alimentação mais saudável, e visualizações arquitetos e designers de interiores estão trabalhando. Se você está olhando para 3D %{pizza_link} e %{ketchup_link} modelos ou um modelo 3D de um casamento %{cake_link}, eles estão aqui. Um número de versões de baixa-poli estão disponíveis para aqueles em jogo, desenvolvimento VR / AR. modelos 3D podem ser encontrados em FBX, OBJ, MAX, 3DS, formatos C4D.","formats":"Estão %{found_items_count} modelos 3D prontos para download no formato arquivo %{required_software_title} no\nO mercado. Sinta-se livre para navegar na coleção e não se esqueça de usar os outros filtros de busca para encontrar\nPBR, manipuladas, modelos 3D imprimíveis animado ou para seus projetos. Se você é um desenvolvedor de jogos uso do filtro ‘Poly contagem’\npara modelos 3D vista de baixo-poli que precisam de energia menos computacional para processar. A maioria dos activos 3D em CGTrader são\ndisponível em vários formatos de arquivo, e se um modelo que você quer não estiver disponível no formato arquivo %{required_software_title},\nvocê pode sempre pedir uma conversão de arquivos, selecionando ‘solicitação de arquivo livre conversão de formato’ na página do produto. Você pode\nTambém visite a nossa página 3D Jobs e postar um pedido de conversão modelo, modelos personalizados e quaisquer outros serviços de modelagem você\npode precisar.\n","free_general":"Modelos livre 3D disponíveis para download a partir de %{link_1} para %{link_2} ativos 3D. Disponível em vários formatos de arquivo, incluindo MAX, OBJ, FBX, 3DS, STL, C4D, mistura, MA, MB. Encontre profissional %{link_3} para quaisquer projetos de design 3D como a realidade virtual (VR), realidade aumentada (AR), jogos, %{link_4} visualização 3D ou animação.","free_no_tag":"%{model_count} free 3D %{tag} modelos prontos para a impressão 3D. Baixar %{tag} modelos de impressão 3D livres, formatos de arquivo disponíveis, incluindo STL, OBJ, 3DM, 3DS, MAX. modelos 3D está pronto para ser usado com o FDM, SLA, SLS e outros tipos de impressoras 3D.","free_printable":"%{model_count} modelos 3D gratuitos pronto para impressão 3D. Download gratuito ou olhar para os modelos de impressão Premium 3D. formatos de arquivo disponíveis, incluindo STL, OBJ, WRL. modelos 3D está pronto para ser usado com o FDM, SLA, SLS e outros tipos de impressoras 3D.","free_tag":"Grátis 3D %{tag} modelos disponíveis para download. Disponível em vários formatos de arquivo, incluindo MAX, OBJ, FBX, 3DS, STL, C4D, mistura, MA, MB. Encontre profissional %{tag_link} para quaisquer projetos de design 3D como a realidade virtual (VR), realidade aumentada (AR), jogos, visualização 3D ou animação.","furniture":"%{count} mobiliário modelos 3D disponíveis para download incluem mesas, cadeiras, sofás, camas e outros móveis em uma variedade de texturas (madeira, metal, etc.) e estilos (%{retro_link},%{modern_link}, etc. ). Esta categoria é para qualquer um envolvido com arquitetura e design de interiores, bem como anunciantes e animadores. ativos em tempo real pode ajudar jogos e aplicativos VR / AR desenvolvedores para projetos completos. Use filtros para encontrar fraudada, animado, low-poly ou livres 3D modelos. Disponível em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.","general":"modelos em 3D para design digital CG e obras de arte","high_poly":"Comprar ou baixar Animated Professional 3D modelos prontos para serem utilizados em projetos de computação gráfica, produção de filmes e vídeo, animação, visualizações e outros. Ativos estão disponíveis para download em vários formatos aceitos pela indústria, incluindo MAX, OBJ, FBX, 3DS, UE4 e outros. Se você está em busca de ativos 3D de alta qualidade poli, temos uma biblioteca de arte digital líder para todas as suas necessidades.\n","household":"Modelos %{count} 3D doméstica disponíveis para download pode ser usado para dar detalhes aos quartos. Você pode querer colocar um jogo de jantar na mesa, tem sal e pimenta recipientes para interior cozinha completa, ou colocar algum %{lights_link} e %{flowers_link} perto da cama para criar sensação de luxo. Se você é um designer de interiores, animador, 
16518VR / aplicação AR ou jogo criador, esta categoria é para você. Use filtros para encontrar fraudada, animado, low-poly ou livres 3D modelos. Disponível em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.","industrial":"%{count} modelos 3D industriais disponíveis para download - materiais de construção, %{engineering_link} ferramentas, máquinas e %{containers_link} - irá fornecer ambos os detalhes e interativos, manipuladas elementos para criar ambientes animados. Esta é uma categoria perfeita para simulações / VR / AR ou desenvolvedores %{construction_link} nível do site no jogo. Os modelos 3D servirá profissionais da construção visualizações precisas que trabalham com software variados como modelos em formatos como FBX, OBJ, MAX, 3DS, C4D estão disponíveis.","interior":"%{count} modelos 3D interior estão disponíveis para download e incluir elementos e detalhes usados ​​na arquitetura, construção e engenharia, design de interiores. Para comprar prémio ou baixar um modelo 3D livre, navegar pelas categorias listadas acima. Você pode usar modelos para animação ou jogos - alguns deles são manipuladas, animado e low poly. Disponível em uma variedade de formatos de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.","low_poly":"%{count} low poly 3D %{tag} modelos estão disponíveis para download. Estes modelos conter um número significativamente menor de polígonos e, portanto, requerem menos energia para tornar computação. Modelos que têm menos polígonos são mais usados ​​em aplicações em tempo real que requerem processamento rápido, como a realidade virtual (VR), realidade aumentada (AR), realidade mista (MR), a realidade cruz (XR) e jogos, especialmente jogos móveis. Escolha de nossa coleção de modelos manipuladas e animados para facilmente usá-los em suas aplicações em tempo real. Obter 3D ativos para ambientes, escolher adereços, objetos ou comprar completas modelo 3D coleções, pacotes e pacotes com tudo sua necessidade jogo poder. Economize tempo e custos de desenvolvimento, experiências protótipos fazem uso ou 3D modelos como espaços reservados em seu projeto. Para encontrar modelos que tornam previsível em vários motores, use o filtro PBR ao lado da barra de pesquisa.\n","military":"%{count} militares 3D modelos de armas, braços, %{swords_link} e artilharia moderna e histórica usadas pelas forças militares e de defesa pessoal estão disponíveis para download. Você pode usar modelos para jogos ou conteúdo de VR / AR - alguns deles são polígonos otimizado e pronto para ser adicionado a um motor de jogo. modelos manipuladas e animadas pode facilitar ainda mais o seu trabalho. Você certamente vai encontrar ativos relevantes, pois há muitos modelos 3D disponíveis em qualquer formato de arquivo que você pode precisar (FBX, OBJ, MAX, 3DS, C4D, etc.).","not_custom":"%{found_items_count} 3D %{tag} modelos disponíveis para download. %{tag} modelos 3D estão prontos para a animação, jogos e projetos VR / AR. Use filtros para encontrar fraudada, animado, low-poly ou livres 3D modelos. Disponível em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D","optimized":"Modelos que têm menos polígonos são mais usados ​​em aplicações em tempo real que requerem processamento rápido, como a realidade virtual (VR), realidade aumentada (AR), realidade mista (MR), a realidade cruz (XR) e jogos, especialmente jogos móveis. Escolha de nossa coleção de modelos manipuladas e animados para facilmente usá-los em suas aplicações em tempo real. Obter 3D ativos para ambientes, escolher adereços, objetos ou comprar completas modelo 3D coleções, pacotes e pacotes com tudo sua necessidade jogo poder. Economize tempo e custos de desenvolvimento, experiências protótipos fazem uso ou 3D modelos como espaços reservados em seu projeto. Fisicamente renderização baseada, ou PBR, denota modelos que são prestados com precisão e de forma consistente em todas as condições de iluminação devido à texturização fluxos de trabalho utilizada no processo de criação.\n","pbr":"Fisicamente renderização baseada, ou PBR, denota modelos que são prestados com precisão e de forma consistente em todas as condições de iluminação devido à texturização fluxos de trabalho utilizada no processo de criação. Reflexividade, rugosidade da superfície e a coloração, em conjunto com o princípio da conservação de energia e as vantagens da função de distribuição de reflectância bidireccional (BRDF), forneça o melhor padrão para qualquer objectos 3D que pode ser processado em quaisquer condições de iluminação. modelo A PBR 3D nunca vai refletir mais luz que atinge a sua superfície. texturas PBR estão disponíveis em todos os formatos de arquivo padrão. CGTrader também oferece conversão modelo 3D para PBR para alguns modelos. modelos de uso da PBR em todos os principais motores de renderização, como Unity, motor Unreal, Marmoset Toolbag 3 e outros, que suportam renderização baseada fisicamente.\n","plant":"%{count} planta 3D modelos disponíveis para download contêm flora organismos que crescem como plantas domésticas e na natureza, como flores, %{trees_link}, grama, trepadeiras, hortaliças, ervas, plantas perenes, mudas, brotos, arbustos, deslizamentos, brotos, videiras, plantas daninhas. Para comprar prémio ou baixar um modelo 3D livre, navegar pelas categorias acima para encontrar o melhor ajuste entre os modelos disponíveis. Utilização filtros para encontrar manipuladas, animado, baixa-poli (para utilização em jogos e VR / aplicações AR) modelos, ou para filtrar os formatos requeridos (em modelos FBX, OBJ, MAX, 3DS, C4d e mais estão disponíveis).","printable":"Professional 3D imprimir modelos adequados para todas as suas necessidades de impressão, se você deseja imprimi-los em casa usando um FDM desktop, ou com uma impressora 3D SLS industrial. Nossa coleção de modelos 3D prontos para impressão são agrupados em nove categorias, incluindo esculturas artísticas, arte matemática, artigos de moda, acessórios gadgets e muitos outros objetos. modelos de impressão estão disponíveis em todos os formatos de arquivo padrão da indústria, incluindo OBJ, STL, e WRL. Melhorar a sua vida diária, hábitos de trabalho, dispositivos ou simplesmente imprimir e recolher personagens em miniatura, decorações, figuras geométricas alucinantes bonitos e encontrar 3D caracteres imprimíveis para o seu jogo de tabuleiro.\n","printable_merged_tag":"Aqui você encontra %{tag} arquivos de impressão 3D (STL, OBJ, FBX e muitos outros) prontos para impressão. Compre e baixe modelos 3D gratuitos, transmita e imprima com sua impressora 3D.","printable_tag":"Aqui você pode encontrar arquivos de impressão 3D %{tag} (STL, OBJ, FBX e muitos outros) prontos para impressão. Compre e baixe modelos 3D, transmita e imprima com sua impressora 3D ou compre um produto impresso em 3D – nós o imprimiremos em 3D e enviaremos para sua casa.","rigged":"Navegue por modelos 3D rigged de %{tag} prontos para animação, ideais para desenvolvimento de jogos e pipelines de produção.\n","scanned":"%{count} digitalizados 3D modelos estão disponíveis para compra ou download gratuito. 3D digitalizados modelos estão prontos para a animação, jogos e projetos VR / AR. Você pode encontrar modelos 3D de objetos encontrados na natureza, como pedras e superfícies naturais, humanos e outros objetos da vida real aqui ... e não pode ficar mais realista do que isso. Use filtros para encontrar fraudada, animado, low-poly ou livres 3D modelos. Disponível em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.","science":"%{count} ciência 3D modelos disponível para download nesta faixa de categoria da medicina, biologia, física, campos de química e outros objetos do mundo real relacionados à ciência e tecnologia: equipamentos de laboratório, os corpos microscópicos e mundo subatômico. Não procure mais, %{cell_link}, %{skull_link} e %{skeleton_link} 3D modelos podem ser encontrados aqui! Para comprar ou baixar um livre 3D ativos, navegar pelas categorias acima. Você pode usar modelos manipuladas para animação ou de baixa poli queridos para jogos e projetos VR / AR. Ambos os modelos 3D gratuitos e premium pode ser encontrado em um número de formatos de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.","scripts_plugins":"%{count} Scripts 3D / Plugins estão disponíveis para download. Scripts 3D / Plugins pode ser adicionado a diversos softwares e usado para fazer o seu pipeline de trabalho de forma mais eficiente. Filtrar por formatos de encontrar ferramentas de montagem seu software preferido.\n","space":"%{count} modelos espaço 3D estão disponíveis para download no CGTrader. O espaço é uma categoria cheia de low poly, modelos de jogo-
16518pronto de sci-fi %{spaceships_link}. Há também %{asteroids_link}, planetas (incluindo Lua e Marte, a colonização de simulação) e espaço realista %{shuttles_link} para os mais cientificamente inclinado. Os modelos pode definitivamente ser usado em VR jogos espaço simulador, aplicações AR e animações para demonstrar ao mundo exterior do planeta %{earth_link}. Filtros estão aqui para ajudá-lo a encontrar bens manipuladas, animado, baixos-poli ou modelos em formatos específicos, incluindo FBX, OBJ, MAX, 3DS, C4D.","sports":"%{count} esportes 3D modelos disponíveis equipamentos juntos ninho que é usado para atividades recreativas, entretenimento, exercício, lazer, passatempo prazeroso, divertido, bem como jogos, brinquedos para crianças e uma gama completa de instrumentos musicais. Para comprar ou baixar um modelo 3D livre em qualquer formato de arquivo necessário, incluindo FBX, OBJ, MAX, 3DS, C4D, navegar pelas categorias acima. Você pode usar modelos para animação, jogos, projetos VR / AR - muitas delas são manipuladas, low-poly ou já animado.","test":{"airplane":"Entusiastas da aviação, escutem, nunca foi tão fácil para obter o modelo de avião impresso 3D perfeito para o seu jogo de tabuleiro ou coleção modelo. Pesquisar modelos de avião de impressão %{model_count} 3D e encontrar um modelo que você só precisa ter na prateleira. Descubra verdadeiro modelos de avião ou aqueles confeccionados. Procurar modelos mais recentes ou algo que a contagem de volta da época dos irmãos Wright. É também um presente perfeito para qualquer criança que sonha ser piloto quando crescer. Compra e download de um modelo de impressão 3D, imprimi-lo por si mesmo ou enviar o modelo para um dos nossos fornecedores de serviços de impressão 3D para tê-lo impresso e enviado para você em nenhum momento!\n","anatomy":"%{model_count} modelos 3D anatomia disponíveis para download em nosso mercado. Explorar a anatomia humana com elevada - osso poli, músculo, modelos de órgãos. Perfeito para qualquer projeto educacional ou científico. Use modelos 3D de impressão para melhorar a experiência de educação. Criar animações fantásticas e jogos de vídeo com o nosso\n vasta biblioteca de modelos manipuladas. Use nosso sistema de filtragem avançada para encontrar facilmente low poly, animado, PBR,\n modelos manipuladas ou qualquer coisa que se adapte às suas necessidades melhor. Escolha entre uma grande quantidade de formatos de arquivo, incluindo o\n a maioria das mais populares como FBX, OBJ, MAX, 3DS, C4D.\n","castle":"Seu próprio surpreendente 3D impresso castelo é um clique de distância! Pesquisar vários modelos castelo 3D pronto para impressão 3D. Criar uma fortaleza original da fantasia para a sua sessão de D \u0026 D ou qualquer outro jogo de RPG de mesa medieval. Estas fortificações medievais são um dom incrível para qualquer entusiasta de jogo de tabuleiro ou uma grande adição à sua própria coleção. Encontre o modelo perfeito 3D para a pintura amadores ou uma parte de um brinquedo tipo para as crianças. Compra e download seu projeto castelo 3D escolhido, imprimi-lo com sua própria impressora, ou enviar o modelo para um dos nossos fornecedores de serviços de impressão 3D para tê-lo impresso e enviado a você.\n","dice":"Sorte não vai ficar sem com 3D impresso dados! Se você quiser dados do jogo autêntico para a sua noite de jogo de tabuleiro, não procure mais. Aqui você pode escolher entre %{model_count} gratuitos modelos dados 3D prontos para impressão 3D. Quando o jogo acabou, usar essas belas modelos como acessório. Download gratuito modelos 3D dados imprimíveis, formatos de arquivos disponíveis, incluindo STL, OBJ, 3DM, 3DS, MAX. modelos Nossa 3D está pronto para ser usado com FDM, SLA, SLS e outros tipos de impressoras 3D. Fazer o seu jogo um pouco mais interessante e obter seus dados impressos em 3D!\n","drone":"3D imprimir e construir seu próprio robô ou encontrar as peças para substituição. 3D impresso drones são fáceis de montar e reparação após um pouso forçado. Descubra um modelo com a câmera posição de montagem perfeita ou pedido de alterações mínimas. Perfeito para divertidas actividades de lazer, fotografia aérea, videografia ou mapeamento. Encontrar e comprar um modelo de robô 3D design único que se encaixa todas as suas necessidades. Imprimi-lo por si mesmo ou enviar o modelo para um dos nossos fornecedores de serviços de impressão 3D para tê-lo impresso e enviado para você em nenhum momento! Obtenha seu próprio robô impresso em 3D e percorrer os céus ampla azuis!\n","pokemon":"Pikachu, eu escolho você! Escolha a partir de um monte de monstros de bolso pronto para impressão 3D. Ótimo para expandir a sua coleção pokemon impresso 3D. Pesquisar e tropeçar em cima os melhores modelos pokemon, mais detalhados para o seu próximo jogo de mesa com os amigos. Compra e download seu pokemon 3D, imprimi-lo com sua própria impressora 3D, ou enviar o modelo para um dos nossos fornecedores de serviços de impressão 3D para tê-lo impresso e enviado a você. Afinal, esta é uma das coisas mais legais para impressão 3D!\n","ring":"Jóias impresso 3D é o novo preto na indústria, assim como os anéis impressos em 3D! Pesquisar uma enorme quantidade de modelos anel de impressão 3D gratuitos %{model_count}. Navegar através de projetos originais e encontrar algo feito a
16518penas para você. Não consigo encontrar nada para si mesmo na loja? Ou talvez você quer um acessório exatamente como o seu personagem favorito filme tem? Escolha entre uma grande quantidade de formatos de arquivo, incluindo STL, OBJ, 3DM, 3DS, MAX. modelos 3D está pronto para ser usado com FDM, SLA, SLS e outros tipos de impressoras 3D, então navegar através de nossa seleção e tentar em seu novo anel favorito.\n","shoes":"Grandes marcas estão a bordo com o 3D impresso sapatos e agora você pode compartilhar a experiência bem. Procurar e localize livres imprimíveis design dos calçados - de tênis para saltos da moda, há uma série de modelos de ready-to-print apenas esperando por você para experimentá-los. Faça o perfeito resultado, selecionando materiais, ajustando suavidade e tamanho. Escolha a partir de 146 modelos gratuitas sapato 3D e transferi-los em formatos de arquivo disponíveis, incluindo STL, OBJ, 3DM, 3DS, MAX. Designs está pronto para ser usado com FDM, SLA, SLS e outros tipos de impressoras 3D. Obter seus sapatos impressos 3D hoje!\n","textures":"Pesquisar uma grande quantidade de %{model_count} CG texturas disponíveis para download, incluindo materiais bem-feitas. Navegue pela categoria e encontrar tecido sem costura, madeira, metal, plástico, texturas tijolo para ajudar a terminar o seu projeto mais rápido. Usar nossa busca avançada para encontrar texturas manipuladas e animados para o jogo ou criação de vídeo. Nossos materiais estão disponíveis para download em vários formatos, incluindo TGA, SBSAR, JPG, PNG, TIFF e outros, então não perca tempo e encontrar texturas CG ready-to-use para quase todo o imaginável projeto.\n"},"various":"%{count} vários modelos 3D que não facilmente encontrar o seu lugar em outras categorias estão disponíveis para download. %{rocks_link}, %{clouds_link}, %{logos_link}, %{cigarette_link} e outros modelos não-categorizadas em 3D que você pode precisar para completar o seu jogo, animação ou projeto VR / AR está aqui. Use filtros para encontrar fraudada, animado, low-poly ou livres 3D modelos, ou resolver por formatos disponíveis, tais como FBX, OBJ, MAX, 3DS, C4D.","vehicle":"%{count} veículo 3D modelos disponíveis para download incluem caminhões, %{ambulances_link}, ônibus, %{bikes_link} e moto - esta categoria é para as pessoas que querem suas ruas da cidade para ser preenchido com veículos se realista ou sci-fi. Aqueles que fazem jogos de simulação ou aplicações VR / AR, criativos na produção de vídeo ou até mesmo criando cartazes será mais provável encontrar formatos de arquivos mais populares, como FBX, OBJ, MAX, 3DS, C4D utilizável. Outros veículos de transporte terrestre como trem ou modelos %{tractor_link} 3D também podem ser encontradas nesta vasta coleção. Use filtros para encontrar fraudada, animado, low-poly ou livre modelos 3D mais rápido.","watercraft":"%{count} watercraft 3D modelos disponíveis para download incluem %{boats_link}, %{yachts_link}, %{ships_link} e outros veículos de água vai de todos os tipos. Os manipuladas 3D modelos de baixo-poli fará bem em jogos de vídeo, VR / projetos AR, bem como animações, enquanto os modelos CG está aqui para as suas cenas e outras aplicações onde a fidelidade visual é mais importante do que polycount. Os modelos estão disponíveis em qualquer formato de arquivo, incluindo, mas não limitado a FBX, OBJ, MAX, 3DS, C4D."},"tag":{"meta":{"3d_merged_premium":"Baixe %{count} modelos 3D %{tag} gratuitos e premium, disponíveis nos formatos de arquivo MAX, OBJ, FBX, 3DS e C4D, prontos para VR/AR, animação, jogos e outros projetos 3D.","3d_merged_premium_print":"Baixe %{count} %{tag} arquivos de impressão 3D gratuitos e premium prontos para FDM, SLA, SLS e outras impressoras 3D, formatos disponíveis STL, OBJ, 3DM e muito mais.","3d_models":"%{count} %{tag} 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Download de %{count} %{tag} modelos de impressão 3D prontos para FDM, SLA, SLS e outros 3D impressoras, formatos disponíveis STL, OBJ, 3DM e muito mais.%{page}","animated_3d_models":"%{count} %{tag} animado 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Download de %{count} %{tag} gratuitos 3D modelos, disponíveis em MAX, OBJ, FBX, 3DS, formatos de arquivo C4D, pronto para VR / AR, animação, jogos e outros projetos em 3D.%{page}","free_3d_print_models":"Download de %{count} %{tag} modelos de impressão 3D gratuitos prontos para FDM, SLA, SLS e outros 3D impressoras, formatos disponíveis STL, OBJ, 3DM e muito mais.%{page}","lowpoly_3d_models":"Baixar %{count} VR / AR / low-poly %{tag} modelos 3D pronto para Realidade Virtual, jogos e outros aplicativos em tempo real, disponível em FBX, OBJ, MAX, 3DS, formatos de arquivo C4D.%{page}","rigged_3d_models":"%{count} %{tag} fraudada 3D modelos disponíveis para download em qualquer formato de arquivo, incluindo FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"title":{"download":"| baixar","files":"arquivos 3D","formats":"%{required_extension_title} %{product_type_title} - download %{required_software_title} de formato de arquivo  3D ativos","free":"Modelos 3D grátis","free_printing":"Impressão livre modelos 3D","free_tag":"Livre %{selected_tag} modelos 3D","general":"%{selected_filter} 3D Models","high_poly":"%{selected_filter} Modelos 3D","low_poly":"%{selected_filter} 3D Modelos","newest_cg":"Novos e Últimos Modelos 3D","newest_print":"Novos e Últimos Modelos de Impressão 3D","page_prefix":"Página %{page_number} de ","pbr":"PBR 3D modelos","printing":"Impressão 3D %{selected_filter} Modelos","sales_cg":"Modelos 3D Mais Vendidos","sales_print":"Modelos de Impressão 3D Mais Vendidos","tag_heading":{"cg":"Livre %{tag} Modelos de impressão 3D","filter":"%{selected_filter} %{tag} 3D Modelos","general":" %{tag} Modelos 3D","merged_premium_print_tag":"Arquivos de impressão %{selected_tag} gratuitos e premium","merged_premium_tag":"Modelos 3D %{selected_tag} gratuitos e premium","printable":"%{tag} Arquivos e modelos de impressão 3D"},"trending_cg":"Modelos 3D em Tendência","trending_print":"Modelos de Impressão 3D em Tendência"}},"cart":{"request_invoice_failed":"Opa. Algo deu errado.","request_invoice_successful":"Solicitação de fatura enviada com sucesso.","shareable_cart_created":"Carrinho compartilhado com sucesso.","shareable_cart_failed":"Opa. Algo deu errado.","subscription_covered_items_removed":"Removido do seu carrinho: %{titles}. Já incluído na sua assinatura.","subscription_covered_order_refused":"Sua assinatura agora inclui %{titles}, portanto não efetuamos a cobrança. Baixe pela sua assinatura."},"categories":{"cg":{"header_title_html":"Comprar \u003cstrong\u003emodelos 3D\u003c/strong\u003e profissionais","text":"Encontre exatamente o conteúdo 3D certo para suas necessidades, incluindo AR/VR,\njogos, publicidade, entretenimento e outros\n"},"head-title":{"cg":"Modelos Comprar Professional 3D","cg-models":{"sort-by":{"newest":"Novos Modelos 3D","top-selling":"Modelos 3D Mais Vendidos","trending":"Modelos 3D em Tendência"}},"print":"Compre arquivos profissionais de impressão 3D","print-models":{"sort-by":{"newest":"Novos Modelos de Impressão 3D","top-selling":"Modelos de Impressão 3D Mais Vendidos","trending":"Itens de Impressão 3D Mais Populares"}}
16518,"product-type":{"plural":{"capital":{"cg":"Modelos 3D","cg-merge-premium":"Modelos 3D – downloads gratuitos e premium","print":"Arquivos e Modelos de Impressão 3D","print-merge-premium":"%{tag} arquivos de impressão 3D - modelos STL gratuitos e premium"},"lower":{"cg":"3d modelos","print":"arquivos e modelos de impressão 3D"}},"singular":{"capital":{"cg":"Modelo 3D","print":"Modelo 3D Printing"},"lower":{"cg":"modelo 3d","print":"modelo de impressão 3D"}}}},"index":{"browse_all":"Navegar por todos os modelos","info-section__description_1_html":"Compre ou baixe gratuitamente modelos 3D profissionais prontos para serem usados em projetos CG, produção de filmes e vídeos, animação, visualizações, jogos, VR/AR e outros. Os recursos estão disponíveis em muitos \u003ca href='https://www.cgtrader.com/blog/3d-file-formats' target='_blank' rel='noopener noreferrer'\u003eformatos aceitos pela indústria\u003c/a\u003e, incluindo MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB e outros. Se você procura assets high poly ou em tempo real, temos uma biblioteca líder de arte digital para todas as suas necessidades.\n","info-section__description_1_printable_html":"Arquivos, impressões e modelos 3D profissionais prontos para serem impressos em casa com impressoras 3D FDM de mesa ou SLS industriais, disponíveis em muitos formatos industriais, incluindo OBJ, \u003ca href=\"https://www.cgtrader.com/3d-print-models/stl-files\"\u003eSTL\u003c/a\u003e, WRL. Se você é um entusiasta de impressão 3D, maker, educador ou uma empresa que cria protótipos por meio de impressão 3D, nossas decorações para casa, acessórios de gadgets modernos, peças de moda chamativas, joias únicas, peças mecânicas e uma variedade de miniaturas certamente lhe darão um impulso de inspiração.","info-section__heading_1_html":"Explorar \u003cstrong\u003ecategorias\u003c/strong\u003e de modelos 3D","info-section__heading_1_printable_html":"Explorar Modelo 3D Printing \u003cstrong\u003e Categorias \u003c/strong\u003e","info-section__heading_2_html":"\u003cstrong\u003eMarketplace\u003c/strong\u003e de modelos 3D","info-section__heading_2_printable_html":"O que \u003cstrong\u003emais\u003c/strong\u003e?"},"marketplace":{"3d-printing":{"link_text":"Navegar por modelos de impressão 3D","text":"Encontre modelos 3D para impressão em sua impressora 3D.","title":"Modelos de impressão 3D"},"blender_text":"Descubra milhares de modelos 3D Blender disponíveis no formato blend.","browse":"Navegar","cinema-4d_text":"Descubra milhares de modelos Cinema 4D de alta qualidade disponíveis no formato c4d.","discounted":{"link_text":"Procurar desconto modelos 3D","text":"Compre modelos 3D profissionais com um desconto.","title":"Descontados modelos 3D"},"low-poly":{"link_text":"Navegar por modelos 3D low poly","text":"Modelos 3D low poly prontos para videogames e aplicações VR \u0026 AR em tempo real.","title":"Modelos 3D low poly"},"pbr":{"link_text":"Navegar por modelos 3D PBR","text":"Modelos 3D baseados em renderização física.","title":"Modelo 3D PBR"},"scripts_apps":{"link_text":"Navegar por todos","text":"Scripts e plugins 3D para tornar seu pipeline de modelagem 3D mais eficiente.","title":"Scripts e Apps"},"sketchup_text":"Navegue pela vasta seleção de modelos 3D Sketchup para seus designs arquitetônicos, interiores e de videogame.","textures":{"link_text":"Navegar por texturas","text":"Texturas prontas para uso para economizar tempo na preparação de seus modelos 3D.","title":"Texturas"},"zbrush_text":"Encontre assets 3D em formato ZTL para seus projetos de escultura digital, modelagem 3D, texturização e pintura."},"more":{"designers":{"link_text":"Navegar pela lista de designers 3D","text":"Navegue por milhares de portfólios de designers 3D e encontre exatamente o que você\nprecisa para sua aplicação.\n","title":"Encontre os melhores designers 3D"},"projects":{"link_text":"Saiba mais sobre projetos 3D","text":"Contrate artistas 3D freelancers qualificados e resolva todos os seus desafios de\nmodelagem personalizada.\n","title":"Projetos 3D CGTrader"},"visualization":{"link_text":"Saiba mais","text":"Transforme seus produtos em modelos 3D fotorrealistas para e-commerce e outras\naplicações empresariais.\n","title":"Visualização 3D para e-Commerce"}},"navbar":{"aircraft":"Aeronave","animal":"Animais","animals":"Animais","architectural":"Arquitetônico","art":"Arte","car":"Carro","character":"Personagem","exterior":"Exterior","fashion":"Moda","food":"Comida","furniture":"Mobília","gadgets":"Aparelhos","games-toys":"Jogos e Brinquedos","hobby-diy":"Hobby \u0026 DIY","house":"Casa","household":"Casa","industrial":"Industrial","interior":"Interior","jewelry":"Joalheria","military":"Militares","miniatures":"Miniatures","plant":"Plantar","science":"Ciência","space":"Espaço","vehicle":"Veículo","watercraft":"Embarcação"},"print_models":{"subtitle":"Encontre exatamente o conteúdo de impressão 3D certo para suas necessidades","title_html":"Comprar \u003cstrong\u003emodelos 3D profissionais para impressão\u003c/strong\u003e"},"profile":{"collection":"packs 3D","free":"Modelos 3D grátis","high_poly":"Modelos High-Poly 3D","optimized":"Low-Poly / Modelos Pbr 3D","printable":"Modelos De Impressão 3D"},"seo":{"cg":{"aircraft":"Esta categoria abrange aeronaves 3D. Os aviões CG se encaixam em simulações,\nvisualizações, anúncios e vídeos. Corpos e peças de drones vão agradar os fãs de\npequenos veículos aéreos. E os modelos rigged estão prontos para serem importados em\nmotores de jogo.\n","animals":"A coleção de fauna 3D – gatos, cavalos, ursos, rinocerontes, águias, leões e mais –\npode ser encontrada aqui. Os animais CG satisfazem as necessidades de anúncios ou cenas\nestáticas. Também temos modelos de animais rigged para jogos.\n","architectural":"Escadas, lareiras, modelos 3D, bem como decorações como quadros, travesseiros ou\nlâmpadas, podem ser encontrados na extensa biblioteca de modelos arquitetônicos detalhados.\nInteriores e exteriores podem ser usados para construir cozinhas digitais, quartos, salas\nde castelo ou masmorras, seja você arquiteto, engenheiro ou desenvolvedor independente.\n","car":"A c
16518ategoria Carros contém modelos 3D de automóveis, cujas versões low poly atraem\ndesigners de jogos, outras cineastas ou publicitários. De carros clássicos a carros de\ncorrida e conceito, há uma grande variedade de modelos 3D de automóveis.\n","character":"Nesta categoria você encontrará modelos 3D de humanos, pessoas, ficção científica,\nroupas e outros. De coração e mão a modelos realistas detalhados masculinos ou\nfemininos. Vários modelos são rigged e podem ser facilmente adicionados ao seu jogo ou\nanimação.\n","electronics":"Electronics é uma categoria para arquitetos, decoradores, designers de níveis, fabricantes de cena e outros criativos. modelos 3D de { \"slug\": \"smartphones\", \"title\": \"smartphones\"}, { \"slug\": \"laptop\", \"title\": \"laptops\"}, { \"slug\": \"câmera\", \"título \": 'câmeras'}, {\"slug\": 'tV', 'title': 'TVS'} e muito mais vai ajudar a capturar com precisão a obsessão da vida moderna com dispositivos e tecnologia.\n","engineering-parts":"Janelas, encanamentos, deslizantes mecanismos das portas e outros pedaços que são necessários para projectos de CAD e até mesmo planos de DIY se enquadram nesta categoria. Isto é onde você pode encontrar modelos 3D de fabricantes globais como Anver, Dynatec e FCi.\n","exterior":"Navegue por exteriores arquitetônicos de fachadas, edifícios urbanos até objetos de\npaisagem, casas residenciais, construções industriais e modelos completos de cidades 3D.\nAlguns assets são low poly e podem ajudá-lo a completar seu jogo ou aplicativo VR/AR.\n","food":"A comida ajuda a criar cenas realistas da vida doméstica. Frutas, vegetais, bebidas e\nrefeições 3D estão todos aqui. Anúncios para restaurantes, aplicativos AR para alimentação\nmais saudável. Se você estava procurando um modelo de pizza 3D ou bolo de casamento, eles\nestão aqui.\n","furniture":"Mesas, cadeiras, sofás, camas 3D em uma variedade de texturas (madeira, metal, etc.)\ne estilos (retrô, moderno, etc.) estão disponíveis. Esta categoria é destinada a todos os\nenvolvidos com arquitetura, design de interiores e publicidade.\n","furniture-set":"Se você precisa de olhar tanto cadeira antiga e sofá olhando, ou conjunto completo de utensílios de cozinha, este é um lugar para ser como categoria conjuntos de coleta de modelos de mobiliário 3D a partir de cozinha e sala de estar para o escritório e sala de jantar conjuntos de mobiliário salão. Isso ajuda os interiores ou design de nível quartos de construção especialista com elementos correspondentes.\n","household":"Modelos 3D de itens domésticos são usados para dar detalhes aos cômodos. Você pode colocar\num conjunto de jantar 3D na mesa ou recipientes de sal e pimenta para completar a cozinha,\ntalvez adicionar luzes e flores perto da cama para criar um ambiente luxuoso.\n","industrial":"Materiais de construção, ferramentas de engenharia, máquinas e contêineres que\nfornecem detalhes ambientais e elementos interativos rigged. Perfeito para\ndesenvolvedores de jogos de simulação ou pessoas que constroem níveis de canteiros de obras.\n","interior":"Esta categoria contém elementos 3D realistas e detalhes de arquitetura, construção\ne design de interiores. Você pode usar esses modelos 3D para animações ou jogos –\nalguns são rigged e low poly.\n","military":"Nesta categoria você pode comprar modelos 3D de armas, armaduras, espadas e artilharia –\nmodernas e históricas – usadas por forças militares e para autodefesa. Você pode usar os\nmodelos em jogos – alguns são otimizados em polígonos e prontos para motores de jogo.\n","plant":"Esta categoria contém modelos 3D de organismos de flora, como flores, árvores, grama,\ntrepadeiras, vegetação, ervas, perenes, mudas, brotos, arbustos, estacas, rebentos,\nvideiras e ervas daninhas. Para comprar ou baixar gratuitamente um modelo 3D, navegue\npelas categorias abaixo.\n","scanned":"Modeladores criar esses modelos utilizando tecnologias 3D digitalização como RealSense. Árvore tocos, estátuas, mesmo machos e fêmeas são populares aqui. O produto está pronto para ser usado em cenas e animações, oferecendo níveis inigualáveis ​​de realismo.\n","scie
16518nce":"Esta categoria contém 3D modelos relacionados com a medicina, biologia, física, química e tecnologia, tais como equipamento de laboratório, corpos microscópicos e mundo subatômico. Não procure mais, { \"slug\": \"célula\"}, { \"slug\": \"crânio\"} e { \"slug\": \"esqueleto\"} 3D modelos podem ser encontrados aqui.\n","scripts-plugins":"Scripts e plug-ins fornecem atalhos de software. Renderização, modelagem, texturização - tudo isso pode ser facilitado usando plug-ins que vêm para todos os tipos de software, como Maya e V-Ray.\n","space":"O espaço é uma categoria cheia de modelos low poly, prontos para jogos, de naves espaciais\nsci-fi, asteroides, planetas (incluindo a Terra e ônibus espaciais realistas para os mais\ncientíficos). Modelos 3D de espaço são úteis para jogos simuladores de espaço VR.\n","sport-hobby":"Esta categoria encompases tudo o que está relacionado com { \"slug\": \"campo\"} eventos, esportes e hobbies em geral. De { \"slug\": \"football\"} e pistas de esqui para { \"slug\": \"guitarra\"} e { \"slug\": \"piano\"}, estes são úteis 3D modelos para anúncios, cenas e animação. versões de baixa-poli estão lá para jogos de vídeo de esportes.\n","textures":"Navegue por uma biblioteca de materiais 3D lindamente feitos e encontre as texturas prontas\npara uso mais adequadas para preparar modelos prontos para jogos ou fotorrealistas, atualizar\nsua biblioteca de ativos e melhorar seus trabalhos 3D.\n","various":"Estes são os modelos 3D que não facilmente se encaixam em outras categorias, por isso criamos um que iria ace
16518itá-los todos. { \"title\": \"Rocks\", \"slug\": \"rochas\"}, { \"slug\": \"nuvens\"}, { \"title\": \"logos\", \"slug\": \"logo\"}, { \"título\" : \"guarda-chuvas\", \"slug\": \"guarda-chuva\"} e outros modelos não-categorizadas em 3D estão aqui.\n","vehicle":"Caminhões, ambulâncias, ônibus, trens, tratores, bicicletas e motocicletas — esta\ncategoria é para as pessoas que querem suas ruas da cidade cheias de veículos realistas e\nsci-fi, e para aqueles que fazem jogos de simulação ou cartazes.\n","watercraft":"Embarcações cobrem barcos, iates, navios e outros veículos aquáticos. Os modelos 3D rigged\nfuncionam bem em videogames e animações, enquanto os modelos CG servem para cenas ou outras\naplicações onde a fidelidade visual é mais importante que a contagem de polígonos.\n"},"forwarded":{"cg":{"aircraft":"Esta categoria abrange aeronaves 3D. CG { \"slug\": \"airplane\", \"title\": \"aviões\"} vai caber em simulações, visualizações, anúncios e vídeos. { \"slug\": \"drone\", \"title\": \"Drone\"} corpos e partes irá deliciar os fãs de pequenos veículos voadores. E o { \"slug\": \"aircraft\", \"title\": \"fraudada modelos\", \"active_filter\": \"rigged\"} está pronto para ser importado para motores de jogo.\n","animals":"A coleção de fauna 3D - { \"slug\": \"cat\", \"title\": \"gatos\"}, { \"slug\": \"horse\", \"title\": \"cavalos\"}, { \"slug\": \"bear\" , \"title\": \"ursos\"}, { \"slug\": \"rhino\", \"title\": \"rinocerontes\"}, { \"slug\": \"eagle\", \"title\": \"águias\"}, { \"slug\": \"lion\", \"title\": \"leões\"} e muito mais - pode ser encontrada aqui. Os animais CG 3D irá satisfazer as necessidades de anúncios ou cenas estáticas. Temos também { \"slug\": \"animal\", \"title\": \"fraudada animal\", \"active_filter\": \"rigged\"} modelos para jogos.\n","architectural":"{ \"slug\": \"stairs\", \"title\": \"Escada\"}, { \"slug\": \"fireplace\", \"title\": \"lareira\"}, modelos 3D, bem como decorações como { \"slug\": \"frame\", \"title\": \"frame\"}, { \"slug\": \"pillow\", \"title\": \"travesseiro\"} ou { \"slug\": \"lamp\", \"title\": \"lâmpada\"} podem ser encontrados na extensa categoria detalhe do carro de arquitetura contém automóvel 3D modelos, { \"slug\": \"car\", \"title\": \"poli baixa\", \"active_filter\": \"low_poly\"} biblioteca modelo. Interior e exterior pode ser usado para construir as duas cozinhas digitais e quartos, bem como salas de castelos e masmorras se você é um arquiteto, engenheiro ou desenvolvedor de jogos indie.\n","car":"Categoria de carro contém automóvel 3D modelos, versões baixo poli \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e low-poly \u003c/a\u003e de que será atraente para os designers de jogos, outros - para cineastas ou anunciantes. De carros clássicos para corrida e pista de carros para aqueles conceito, há uma grande variedade de modelos de automóveis 3D.\n","character":"Nesta categoria você vai encontrar modelos 3D de { \"slug\": \"human\", \"title\": \"humano\"}, as pessoas, sci-fi, roupas e outros. De { \"slug\": \"heart\", \"title\": \"coração\"} e { \"slug\": \"hand\", \"title\": \"mão\"} para detalhada realista { \"slug\": \"male\", \"title\": \"macho\"} ou { \"slug\": \"female\", \"title\": \"feminino\"} modelos. Um número de modelos são manipuladas e pode ser facilmente adicionado ao seu jogo ou animação.\n","electronics":"Electronics é uma categoria para arquitetos, decoradores, designers de níveis, fabricantes de cena e outros criativos. modelos 3D de { \"slug\": \"smartphone\", \"title\": \"smartphones\"}, { \"slug\": \"laptop\", \"title\": \"laptops\"}, { \"slug\": \"camera\", \"title\": \"câmeras\"}, {\"slug\": \"tV\", \"title\": \"TVS\"} e muito mais vai ajudar a capturar com precisão a obsessão da vida moderna com dispositivos e tecnologia.\n","engineering-parts":"Janelas, encanamentos, deslizantes mecanismos das portas e outros pedaços que são necessários para projectos de CAD e até mesmo planos de DIY se enquadram nesta categoria. Isto é onde você pode encontrar modelos 3D de fabricantes globais como Anver, Dynatec e FCi.\n","exterior":"Exteriores arquitectónicas navegar a partir de fachadas, edifícios urbanos para objetos paisagem, casas residenciais, construções industriais, e em grande escala 3D { \"slug\": \"city\", \"title\": \"Cidade\"} modelos. Alguns dos ativos são baixos poli e poderia ajudá-lo a completar o seu jogo ou aplicativo VR / AR.\n","food":"Food ajuda a criar cenas realistas da vida doméstica. 3D frutas, legumes, bebidas e refeições estão todos aqui. Anúncios para restaurantes, aplicações AR para uma alimentação mais saudável. Se você estava procurando 3D { \"slug\": \"pizza\", \"title\": \"pizza\"} ou um casamento { \"slug\": \"cake\", \"title\": \"bolo\"} modelo, eles estão aqui.\n","furniture":"Mesas 3D, cadeiras, sofás, camas em uma variedade de texturas (madeira, metal, etc.) e estilos ({ \"slug\": \"retro\", \"title\": \"retro\"}, { \"slug\": \"modern\", \"title\": \"modernas\"}, etc.) estão on- mão. Esta categoria é para qualquer um envolvido com arquitetura e design de interiores, bem como anunciantes.\n","furniture-set":"Se você precisa de olhar tanto cadeira antiga e sofá olhando, ou conjunto completo de utensílios de cozinha, este é um lugar para ser como categoria conjuntos de coleta de modelos de mobiliário 3D a partir de cozinha e sala de estar para o escritório e sala de jantar conjuntos de mobiliário salão. Isso ajuda os interiores ou design de nível quartos de construção especialista com elementos correspondentes.\
16518n","household":"Modelos item doméstico 3D são usadas para dar detalhes aos quartos. Você pode colocar um jogo de jantar 3D sobre a mesa, ou ter sal e pimenta recipientes para interior cozinha completa, talvez colocar alguma { \"slug\": \"light\", \"title\": \"luzes\"} e { \"slug\": \"flowers\", \"title\": \"flores\"} perto da cama para criar sensação de luxo.\n","industrial":"Materiais de construção, { \"slug\": \"engineering\", \"title\": \"engenharia\"} ferramentas, máquinas e { \"slug\": \"container\", \"title\": \"containers\"} que fornecem ambos os detalhes ambientais e interativos, manipuladas elementos. Isto é perfeito para desenvolvedores de jogos de simulação ou pessoas construindo { \"slug\": \"construction\", \"title\": \"construção\"} nível jardim.\n","interior":"Esta categoria contém realistas 3D elementos e detalhes de arquitetura, construção e engenharia de design de interiores. Você pode usar esses modelos 3D para animação ou jogos - alguns deles são manipuladas e low poly.\n","military":"Nesta categoria você pode comprar 3D modelos de armas, braços, { \"title\": \"espadas\", \"slug\": \"sword\"} e artilharia moderna e histórico usado por forças militares e de defesa pessoal. Você pode usar modelos para jogos - alguns deles são polígonos otimizados e prontos para motores de jogo.\n","plant":"Esta categoria contém 3D modelos de flora organismos, tais como flores, { \"slug\": \"tree\", \"title\": \"árvores\"}, grama, trepadeiras, hortaliças, ervas, plantas perenes, mudas, brotos, arbustos, deslizamentos, couve, videiras, planta daninha. Para comprar ou baixar um modelo 3D livre, navegar pelas categorias abaixo.\n","scanned":"Modeladores criar esses modelos utilizando tecnologias 3D digitalização como RealSense. Árvore tocos, estátuas, mesmo machos e fêmeas são populares aqui. O produto está pronto para ser usado em cenas e animações, oferecendo níveis inigualáveis ​​de realismo.\n","science":"Esta categoria contém 3D modelos relacionados com a medicina, biologia, física, química e tecnologia, tais como equipamento de laboratório, corpos microscópicos e mundo subatômico. Não procure mais, { \"slug\": \"cell\", \"title\": \"célula\"}, { \"slug\": \"skull\", \"title\": \"crânio\"} e { \"slug\": \"skeleton\", \"title\": \"esqueleto\"} 3D modelos podem ser encontrados aqui.\n","scripts-plugins":"Scripts e plug-ins fornecem atalhos de software. Renderização, modelagem, texturização - tudo isso pode ser facilitado usando plug-ins que vêm para todos os tipos de software, como Maya e V-Ray.\n","space":"O espaço é uma categoria cheia de low poly, modelos de jogo-pronto de sci-fi { \"slug\": \"spaceship\", \"title\": \"naves espaciais\"}, { \"slug\": \"asteroid\", \"title\": \"asteróides \"}, planetas (incluindo {\"slug\": \"earth\", \"title\": \"terra\"} e espaço realista {\"slug\": \"shuttle\", \"title\": \" shuttles\"} para os mais cientificamente inclinado 3D modelos espaciais são úteis para VR jogos espaço simulador.\n","sport-hobby":"Esta categoria encompases tudo o que está relacionado com { \"slug\": \"field\", \"title\": \"campo\"} eventos, esportes e hobbies em geral. De { \"slug\": \"football\", \"title\": \"football\"} e pistas de esqui para { \"slug\": \"guitar\", \"title\": \"guitarra\"} e { \"slug\": \"piano\", \"title\": \"piano\"}, estes são úteis 3D modelos para anúncios, cenas e animação. versões de baixa-poli estão lá para jogos de vídeo de esportes.\n","textures":"Navegar através de uma biblioteca de materiais 3D lindamente feitas e encontrar os melhores, texturas prontas para uso adequados à preparação \u003ca href=\"%{link_to_low_poly_items_url}\"\u003ejogo pronto\u003c/a\u003e ou modelos fotorrealistas, atualizar sua biblioteca de ativos e melhorar suas obras 3D.\n","various":"Estes são os modelos 3D que não facilmente se encaixam em outras categorias, por isso criamos um que iria ace
16518itá-los todos. { \"title\": \"Pedras\", \"slug\": \"rocks\"}, { \"slug\": \"clouds\", \"title\": \"nuvens\"}, { \"title\": \"logos\", \"slug\": \"logo\"}, { \"title\" : \"guarda-chuvas\", \"slug\": \"umbrella\"} e outros modelos não-categorizadas em 3D estão aqui.\n","vehicle":"Caminhões, { \"title\": \"ambulâncias\", \"slug\": \"ambulance\"}, ônibus, trens, tratores, { \"title\": \"bicicletas\", \"slug\": \"bike\"} e motocicletas - esta categoria é para as pessoas que querem suas ruas da cidade para ser preenchido com veículos realistas e sci-fi, e para aqueles que fazem jogos de simulação ou formatação cartazes.\n","watercraft":"Coberturas de embarcações { \"title\": \"barcos\", \"slug\": \"boat\"}, { \"title\": \"iates\", \"slug\": \"yacht\"}, { \"title\": \"navios\", \"slug\": \"ship\"} e outros veículos aquáticos. O fraudada modelos 3D vai fazer bem em jogos de vídeo, animação, enquanto os modelos CG estão lá para suas cenas ou outras aplicações onde a fidelidade visual é mais importante do que polycount.\n"},"printable":{"art":"Este é o lugar onde as artísticas impressões 3D ir. Procurando por design extravagante escultura recriar obras clássicas e estátuas de personalidades históricas, ou inspirando estatuetas, talvez até mesmo logotipos 3D imprimíveis? Essa é a categoria.\n","fashion":"Coisas bonitas e elegantes que você pode usar são encontrados nesta categoria. Veja se você pode encontrar qualquer uso para a idade espaço fantasia { \"slug\": \"footwear\", \"title\": \"calçado\"}, óculos de novidade e cosplay { \"slug\": \"accessories\", \"title\": \"acessórios\"} encontrada aqui.\n","gadgets":"Todos os { \"title\": \"iPhone\", \"slug\": \"iphone\"}, { \"title\": \"iPad\", \"slug\": \"ipad\"} (e outros dispositivos inteligentes) casos podem ser encontrados na categoria gadget. De simples 3D add-ons para partes complexas e “hacks” para seus itens, esta é a categoria para os criativos, e os amantes da tecnologia.\n","games-toys":"Jogos e brinquedos é a categoria que abrange impressão alienígenas ou monstros miniaturas 3D, minúsculo { \"title\": \"tanques\", \"slug\": \"tank\"}, funky { \"slug\": \"chess\", \"title\": \"xadrez\"} peças, capacetes personagem do filme, passatempo parafernália e acessórios. Esta é a categoria-para ir para todas as coisas geeky.\n","hobby-diy":"Se você é o nosso próximo grande inventor e quer construir seu projeto de DIY ou obter algumas peças de reposição para o seu { \"slug\": \"drone\", \"title\": \"zangão\"}, este é o lugar para estar. Além disso, possui um monte de coisas para as pessoas que querem fazer as suas próprias figuras ou réplicas de armas de plástico.\n","house":"Todos os tipos de bugigangas pode ser encontrada aqui. { \"title\": \"Lâmpadas\", \"slug\": \"lamp\"}, artístico { \"title\": \"taças\", \"slug\": \"bowl\"}, e todos os tipos de { \"title\": \"titulares\", \"slug\": \"holder\"} se você não consegue encontrar um local adequado para o seu telefone, manter a ordem de palitos, pilha de penas ou jóias em sua mesa.\n","jewelry":"A jóia é onde você vá para obter pulseiras e pingentes ou anéis com diamantes. Embora as impressoras 3D não são bons o suficiente para fazer os diamantes - ainda - você pode ainda imprimir um anel metálico como uma base para o seu TI anel proposta geek.\n","miniatures":"3D impressão { \"title\": \"caracteres\", \"slug\": \"character\"}, pessoas famosas, { \"title\": \"marcos\", \"slug\": \"landmark\"}, animais, { \"title\": \"Macho\",\"slug\": \"male\"}, { \"slug\": \"female\", \"title\": \"feminino\"}, { \"title\": \"monstros\", \"slug\": \"monster\"}, { \"title\": \"alienígenas\",\"slug\": \"alien\"} - está tudo aqui. Se você estiver imprimindo veículos utilitários para colocar em sua mesa ou criar miniaturas para seus jogos de mesa, é a sua categoria.\n","science":"O que pode ser classificada como ciência? Bem, modelos de esqueleto { \"slug\": \"bones\", \"title\": \"ossos\"}, os médicos { \"slug\": \"tools\", \"title\": \"ferramentas\"} e astrofísica relacionadas com 3D modelos imprimíveis que podem ser usados ​​em muitos processos de estudo.\n"}},"printable":{"art":"Este é o lugar onde as artísticas impressões 3D ir. Procurando por design extravagante escultura recriar obras clássicas e estátuas de personalidades históricas, ou inspirando estatuetas, talvez até mesmo logotipos 3D imprimíveis? Essa é a categoria.\n","fashion":"Coisas bonitas e elegantes que você pode usar são encontrados nesta categoria. Veja se você pode encontrar qualquer uso para a idade espaço fantasia { \"slug\": \"calçado\"}, óculos de novidade e cosplay { \"slug\": \"acessórios\"} encontrada aqui.\n","gadgets":"Todos os { \"title\": \"iPhone\", \"slug\": \"iphone\"}, { \"title\": \"iPad\", \"slug\": \"ipad\"} (e outros dispositivos inteligentes) casos podem ser encontrados na categoria gadget. De simples 3D add-ons para partes complexas e “hacks” para seus itens, esta é a categoria para os criativos, e os amantes da tecnologia.\n","games-toys":"Jogos e brinquedos é a categoria que abrange impressão alienígenas ou monstros miniaturas 3D, minúsculo { \"title\": \"tanques\", \"slug\": \"tanque\"}, funky { \"slug\": \"xadrez\"} peças, capacetes personagem do filme, passatempo parafernália e acessórios. Esta é a categoria-para ir para todas as coisas geeky.\n","hobby-diy":"Se você é o nosso próximo grande inventor e quer construir seu projeto de DIY ou obter algumas peças de reposição para o seu { \"slug\": \"zangão\"}, este é o lugar para estar. Além disso, possui um monte de coisas para as pessoas que querem fazer as suas próprias figuras ou réplicas de armas de plástico.\n","house":"Todos os tipos de bugigangas pode ser encontrada aqui. { \"title\": \"Lâmpadas\", \"slug\": \"lâmpada\"}, artístico { \"title\": \"taças\", \"slug\": \"tigela\"}, e todos os tipos de { \"title\": \"titulares\", \"slug\": \"titular\"} se você não consegue encontrar um local adequado para o seu telefone, manter a ordem de palitos, pilha de penas ou jóias em sua mesa.\n","jewelry":"A jóia é onde você vá para obter pulseiras e pingentes ou anéis com diamantes. Embora as impressoras 3D não são bons o suficiente para fazer os diamantes - ainda - você pode ainda imprimir um anel metálico como uma base para o seu TI anel proposta geek.\n","miniatures":"3D impressão { \"title\": \"caracteres\", \"slug\": \"caráter\"}, pessoas famosas, { \"title\": \"marcos\", \"slug\": \"marco\"}, animais, { \"title\": \"Male \",\"slug\": \"macho\"}, { \"slug\": \"feminino\"}, { \"title\": \"monstros\", \"slug\": \"monstro\"}, { \"title\": \"alienígenas\",\"slug\": \"alien\"} - está tudo aqui. Se você estiver imprimindo veículos utilitários para colocar em sua mesa ou criar miniaturas para seus jogos de mesa, é a sua categoria.\n","scie
16518nce":"O que pode ser classificada como ciência? Bem, modelos de esqueleto { \"slug\": \"ossos\"}, os médicos { \"slug\": \"ferramentas\"} e astrofísica relacionadas com 3D modelos imprimíveis que podem ser usados ​​em muitos processos de estudo.\n"},"titles":{"aircraft":"Modelos 3D de aeronaves","animals":"Modelos 3D de animais","architectural":"Modelos 3D arquitetônicos","car":"Modelos 3D de carros","character":"Modelos 3D de personagens","exterior":"Modelos 3D de exteriores","food":"Modelos 3D de comida","furniture":"Modelos 3D de móveis","household":"Modelos 3D domésticos","industrial":"Modelos 3D industriais","interior":"Modelos 3D de interiores","military":"Modelos 3D militares","plant":"Modelos 3D de plantas","space":"Modelos 3D do espaço","textures":"Modelos 3D de texturas CG","vehicle":"Modelos 3D de veículos","watercraft":"Modelos 3D de embarcações"}},"title":{"cg":" Modelos 3D","print":" Arquivos de impressão 3D"}},"competition":{"digital-art-competition":{"categories":{"page_description":"Participar do Concurso de Arte Digital CGTrader através da apresentação de seus trabalhos para a ilustração do personagem, Character Concept Design, Ilustração Meio Ambiente, Meio Ambiente conceito de design, objeto de design e objeto categorias conceito de design. Se você tem vários grandes obras que atendam às diferentes categorias, não hesite em enviá-los para as categorias correspondentes. Selecione uma categoria para ver os prêmios para essa categoria."}}},"conversations":{"action_button_confirmation":"Você realmente deseja reabrir esta solicitação?","reply_posted":"A resposta foi publicada.","with_yourself":"não pode ser o mesmo que o remetente."},"date":{"month_names":[null,"Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"]},"devise":{"failure":{"too_many_requests":"Você já solicitou um e-mail para redefinir a senha. Por favor, aguarde alguns minutos antes de tentar novamente."}},"disabled_cookies_modal":{"button":"Cancelar","text_1":"Você não será capaz explorar milhões de grandes modelos 3D, adicioná-los ao carrinho ou Buy.","text_2":"Se você quiser experimentar CGTrader totalmente, sugerimos ativar os cookies neste site.","title":"Parece que você tem cookies do seu navegador desativada"},"emails":{"error_notification":"Assinatura não encontrada.","success_subscribed_notification":"Você se inscreveu com sucesso.","success_unsubscribed_notification":"Você cancelou a assinatura com sucesso."},"errors":{"messages":{"invalid_vat":"O NIF %{country_adjective} não é válido."}},"file_size_validator":{"errors":{"invalid_carrierwave":"Era esperado um objeto CarrierWave::Uploader::Base","invalid_key":":%{key} deve ser um número inteiro não negativo","range_format":":in e :within devem ser um intervalo (Range)","range_unspecified":"Intervalo não especificado. Especifique a opção :within, :maximum, :minimum ou :is."}},"file_type":{"default_error":"Nenhum caminho para o arquivo fornecido."},"footer":{"bottom-row":{"privacy":"Privacidade","terms-link":"Termos e Condições"},"links":{"buyLinks":[{"text":"Comprar modelos 3D","link":"/pt/3d-modelos"},{"text":"Para empresas","link":"/discover/business-account"},{"text":"Conjuntos de dados 3D gratuitos","link":"/discover/datasets"},{"text":"Contratar designer 3D","link":"/3d-modeling-jobs"},{"text":"Modelos 3D gratuitos","link":"/pt/livre-3d-modelos"},{"text":"Modelos 3D para impressão gratuitos","link":"/pt/livre-3d-impressao-modelos"}],"communityLinks":[{"text":"Comunidade","link":""},{"text":"Melhores designers","link":"/designers"},{"text":"Desafios","link":"/challenges"},{"text":"Fórum","link":"/forum"}],"companyLinks":[{"text":"Empresa","link":"/discover/about-us"},{"text":"Blogue","link":"/blog"},{"text":"Carreiras","link":"/pages/careers"},{"text":"Centro de ajuda","link":"https://help.cgtrader.com"}],"sellLinks":[{"text":"Vender modelos 3D","link":"/discover/sell-3d-models"},{"text":"Aumente suas vendas","link":"/discover/sell-more"},{"text":"Insights do mercado 3D","link":"/discover/3d-market-trends"},{"text":"Principais pesquisas de modelos 3D","link":"/discover/3d-models-top-searches"},{"text":"Principais pesquisas de impressão 3D","link":"/discover/3d-print-models-top-searches"}]},"subscribe":{"button-active":"Se inscrever","button-disabled":"Subscrito","header":"Assine a nossa notícia:","placeholder":"Digite seu e-mail","terms-link":"Termos de uso","text":"Ao subscrever você confirma que leu e aceitou o nosso"}},"general":{"description":"CGTrader é um mercado online onde 3D artistas e designers podem comprar, vender ou compartilhar modelos 3D. Foi fundada e está sendo desenvolvido por uma equipe de designers 3D e engenheiros. Nosso objetivo é democratizar o mercado de ações 3D e mudar a maneira de vender os modelos 3D online. Ao permitir que autores ganhar royalties máximo, nós nos esforçamos para aumentar a sua motivação e aumentar a qualidade dos modelos 3D.","navigation":{"buy_3d_models":"Modelos Comprar 3D","sell_3d_models":"Vender modelos 3D"},"search_placeholder":"Pesquisar em mais de 2 milhões de modelos","site-search__field":"Pesquise entre %{model_count}
16518 modelos 3D","site_title":"3D loja Modelo","site_title_postfix":" | CGTrader"},"generator":{"landing_pages":{"three_d_model":{"defaults":{"closing_cta":{"heading":"Deixe a IA realizar os seus sonhos","subtitle":"Converta um prompt ou uma imagem de referência num modelo 3D pronto para produção, depois refine, retexturize e exporte-o no formato de que necessita."},"faq":{"heading":"Perguntas Frequentes","items":{"commercial":{"answer":"A utilização comercial do seu modelo 3D de %{object} está disponível com um plano pago do CGTrader AI. Por favor reveja os Termos e Condições da CGTrader para as condições de licença aplicáveis aos modelos gerados por IA.","question":"Posso usar o modelo de %{object} gerado para fins comerciais?"},"formats":{"answer":"Os modelos 3D de %{object} estão disponíveis em formato GLB, FBX, OBJ, STL ou USDZ.","question":"Em que formato de ficheiro posso obter o meu modelo de %{object}?"},"no_skills":{"answer":"Não são necessários conhecimentos de modelação 3D. Toda a modelação é feita através do gerador de modelos de %{object}. O sistema trata da geometria, proporções e estrutura quando introduz o seu prompt ou carrega uma imagem. Mesmo os principiantes podem criar um modelo 3D completo de %{object} em minutos.","question":"É necessário conhecimento de modelação 3D para usar um gerador de modelos de %{object}?"},"what_is":{"answer":"O gerador de modelos 3D de %{object} da CGTrader, uma ferramenta baseada em IA, cria um modelo tridimensional de %{object} a partir de instruções escritas ou imagens de referência. Analisa a sua ideia e cria um modelo descarregável para design, prototipagem e visualização. As nossas subscrições pagas oferecem limites mensais mais elevados.","question":"O que é um gerador de modelos 3D de %{object}?"},"why_cgtrader":{"answer":"Os utilizadores podem confiar na CGTrader para uma experiência fluida, tempos de processamento rápidos e resultados 3D de alta qualidade. Esta ferramenta é adequada tanto para profissionais como para principiantes que procuram uma solução fiável para criar modelos 3D de %{object}.","question":"Por que usar o gerador de %{object} com IA da CGTrader?"}}},"h1":"Gerador de modelos 3D de %{object} com IA","hero_alt":"Gerador de modelos 3D de %{object} com IA","hero_height":617,"hero_width":1080,"how_to":{"heading":"Gerar %{object} a partir de imagens ou prompts de texto","paragraphs":["É muito fácil desenhar um modelo tridimensional de %{object}. Na barra lateral esquerda, no canto superior esquerdo, clique no botão para carregar a sua imagem de %{object} e criar um modelo 3D. Os utilizadores também podem introduzir um prompt no campo «Descreva o que pretende criar» na parte inferior. O CGTrader AI melhora o seu prompt automaticamente, pelo que uma descrição curta é suficiente para começar.","A barra lateral direita dá-lhe um bom controlo do seu modelo 3D de %{object}. Escolha definições como o tipo de malha, material, orientação de prompt e formato de ficheiro. Também pode utilizar texturização para personalizar o seu modelo de %{object}. Após introduzir as suas instruções de texto ou imagem, clique em «Gerar». A ferramenta processará a sua entrada em alguns minutos, dependendo da carga do sistema, material, tipo de malha, formas, valores de seed e qualidade."],"steps":["Escreva a sua descrição de texto e/ou anexe uma imagem de referência para melhorar a precisão final do seu modelo 3D de %{object}.","Selecione um dos nossos predefinidos, ou escolha um motor de IA, adicione um número personalizado de polígonos e selecione o tipo de material para o seu modelo 3D de %{object}.","Clique em Gerar para criar um modelo 3D de %{object} a partir do seu prompt e/ou imagem de referência.","Descarregue o seu modelo 3D de %{object} criado em diferentes formatos (GLB, FBX, OBJ, STL ou USDZ)."]},"intro":"Está à procura de uma forma simples e rápida de criar um modelo 3D de %{object} de alta qualidade em minutos? O nosso avançado gerador de modelos 3D de %{object}, CGTrader AI, pode transformar comandos escritos ou imagens de referência em modelos totalmente funcionais. Funciona nos principais motores de geração 3D, incluindo Meshy, Hunyuan e Tripo3D. Isto ajuda na modelação rápida de modelos 3D tanto para principiantes como para especialistas em design, para visualização, experimentação e prototipagem. Suporta texto para 3D e imagem para 3D, e é compatível com impressoras 3D. Pode carregar uma imagem em formato JPG ou PNG, ou escrever uma descrição em texto. O CGTrader AI trata dos passos seguintes por si.","meta_description":"Gerador de modelos 3D de %{object} com IA da CGTrader ☑️ Crie modelos 3D de %{object} detalhados a partir de texto ou imagens. Comece gratuitamente.","primary_cta":"Começar a gerar gratuitamente","title":"Gerador de modelos 3D de %{object} com IA (grátis) | CGTrader"}}},"rules":{"errors":{"front_view_required":"É necessária uma imagem da vista frontal."}}},"home":{"index":{"3D-projects":{"info-section__cta":"Saber mais","info-section__description":"Onde as pessoas com emocionantes 3D Projects atender os designers 3D talentosos","info-section__heading_html":"\u003cStrong\u003e Projetos em 3D \u003c/strong\u003e Freelance Hub"},"categories-conatainer":{"browse_all":"Navegar por todos os modelos","browse_all_tooltip":"Consultar todos os modelos 3D","discounts":"descontos"},"cgdream":{"cta":"Comece de graça!","heading_html":"\u003cstrong\u003eExplore CGDream,\u003c/strong\u003e\u003c/br\u003e onde a IA encontra os modelos 3D.","text_html":"\u003cstrong\u003e CGDReam \u003c/strong\u003e é uma aplicação de ponta que aproveita o poder dos modelos generativos de IA e 3D para produzir imagens 2D impressionantes. \u003cstrong\u003e Sua jornada criativa começa agora. \u003c/strong\u003e"},"community":{"info-section__cta":"Junte-se à comunidade","info-section__heading_html":"Junte CGTrader \u003cstrong\u003e Comunidade \u003c/strong\u003e","info-section__icon-list_1":{"de
16518scription":"Apresente seu melhor torna, obter feedback e reconhecimento","heading":"galerias"},"info-section__icon-list_2":{"description":"Aprender novas habilidades e ensinar os outros","heading":"tutoriais"},"info-section__icon-list_3":{"description":"Encontrar tutoriais e discutir outros assuntos relacionados 3D","heading":"Fórum"}},"covid":{"timer-strip__item":"Liberar recursos para impressão em 3D para COVID-19 Proteção","timer-strip__item--button":"Pesquisar modelos 3D"},"enterprise":{"info-section__cta":"Saber mais","info-section__heading_html":"Serviços 3D para o \u003cstrong\u003e Empresa \u003c/strong\u003e","info-section__icon-list_1":"Nós podemos fazer a implementação AR para você","info-section__icon-list_2":"Mais conversões de vendas, menos devoluções de produtos","info-section__icon-list_3":"Qualidade assegurada com QA rigorosa"},"header":"Modelos 3D para impressão e computação gráfica VR / AR, 3D","marketplace":{"info-section__description":"Escolha a partir de %{models_count} 3D modelos para aplicações como jogos, AR / VR, publicidade, animação, filmes e impressão 3D","info-section__heading_html":"Modelo 3D \u003cstrong\u003e mercado \u003c/strong\u003e","info-section__image-list_1":{"description":"Comprar profissionais modelos 3D para seu projeto","heading":"Modelos 3D"},"info-section__image-list_2":{"description":"Modelos de baixo poli PBR 3D pronto para jogos de vídeo, VR \u0026 AR aplicações em tempo real","heading":"Baixa-poli Modelos PBR 3D"},"info-section__image-list_3":{"description":"Encontrar 3D imprimir desenhos para a impressora 3D","heading":"Impressão 3D Models"},"view_all":"Ver todos"},"search_dropdown":{"ai_cards":{"image_to_3d":{"description":"Transforme uma imagem em um modelo 3D","title":"Imagem para 3D"},"text_to_3d":{"description":"Descreva sua ideia para criar um modelo 3D","title":"Texto para 3D"}},"ai_section_title":"Criar com IA","aria_label":"Sugestões de pesquisa","columns":{"cg":{"search_label":"Pesquisar em Modelos 3D","title":"Modelos 3D"},"print":{"search_label":"Pesquisar em Impressão 3D","title":"Modelos para impressão 3D"}}},"sell":{"info-section__heading_html":"\u003cstrong\u003e Vender \u003c/strong\u003e seus modelos 3D e encontrar freelance projectos","info-section__icon-list_1":{"description":"Vender seus modelos 3D e ganhe até 85% de royalties","link":"Vender modelos 3D\u003e"},"info-section__icon-list_2":{"description":"Trabalho com os clientes corporativos","link":"Obter Qualificado\u003e"},"info-section__icon-list_3":{"description":"Terceirizar suas habilidades e obter até 95% de royalties","link":"Navegar 3D Modeling Jobs\u003e"},"info-section__icon-list_4":{"description":"Construa o seu portfólio e ser visível","link":"Navegar 3D Designers\u003e"}},"seo":{"description":"Explore mais de 2 milhões de modelos 3D online para projetos de impressão, jogos, VR/AR e design. Baixe, personalize ou contrate especialistas 3D - tudo no CGTrader.","title":"CGTrader - Modelos 3D e Marketplace de Impressão 3D"}}},"homepage":{"budget_counter":{"range_0_10":"Menos de $10","range_10_20":"$10 - $20","range_20_15000":"Mais de $20"},"quotes":{"quote_0":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Abaixe comissões retiradas dos meios vendedores preços melhores para os compradores. Confira e comparar os preços em CGTrader e as outras lojas."},"quote_1":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"A vantagem mais marcante CGTrader é sistema de ativação de taxa fixa que nos dá retornos elevados. Você não está tendo um percentual de comissão de nossas vendas. Após a venda, recebemos a 100% do preço. Só se quisermos continuar a vender esse produto, que pode voltar a ativá-lo novamente, pagando US $ 5. solução elegante."},"quote_10":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"O sistema de activação de taxa fixa é engenhoso. Fiquei me perguntando por que ninguém tinha pensado nisso antes. Após a venda, pagar uma pequena taxa para ativar um produto e é isso. O vendedor está em pleno controle."},"quote_11":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"Há duas coisas que mais gosto sobre CGTrader. Primeiro, não há escondido que significa nada aqui. Você não precisa ter cuidado com CGTrader. Em segundo lugar, esta loja é pelos artistas e para os artistas. Eles compreendê-lo melhor. Algo está incomodando você aqui? ... informá-los e eles vão ter certeza de que você se sinta confortável aqui."},"quote_12":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"sistema de ativação de taxa fixa é a melhor! (Pelo menos eu não encontrei nada melhor do que isso). É muito realista e justo para os vendedores. Eles não se importam o quanto de lucro você faz, basta pagar o aluguel fixo. MAS se você não fazer nenhuma venda, você não tem que pagar o aluguel também. O comércio justo CG está aqui!"},"quote_13":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"CGTrader atrai clientes até mesmo de países em desenvolvimento, porque os preços são mais baratos do que em outras lojas. Ao mesmo tempo, os autores podem ganhar mais, porque não existe tal coisa como altas taxas de comissão. É uma maneira certa de valorizar o trabalho de artistas 3D."},"quote_14":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"A interface intuitiva do editor e ferramentas inovadoras como recomendação tag e formas de descrição flexíveis faz com que o processo de publicação fácil e rápido."},"quote_15":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"abordagem de venda 3D de CGTrader é diferente e não tem nada em comum com outras lojas que eu costumava vender. Pagar uma baixa taxa de 5 $ após a venda é uma grande idéia e permita-me oferecer preços mais baixos aqui. Cliente e artista são beneficiados com esta forma de trabalhar."},"quote_16":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"O sistema de carregamento é fácil e clara. É bom saber que por trás do projeto são pessoas com anos de experiência em matéria gráficos de computador. Sempre que eu tinh
16518a um problema, a equipe CGTrader fixas rapidamente."},"quote_17":{"author":{"name":"Javier Almazor","username":"jvr3D"},"text":"CGTrader me permite ligar meu perfil com a minha página pessoal. Os clientes podem entrar em contato comigo, se eles não estão certos sobre uma característica de um item que eles querem comprar ou apenas quer uma modificação do mesmo. É como possuir uma loja 3D pessoal em um grande mercado!"},"quote_2":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Temos tido uma grande e responsivo apoio da equipe CGTrader. A equipe gentilmente se ofereceu para fazer a publicação de todos os modelos para nós, o que é uma grande ajuda para os vendedores profissionais."},"quote_3":{"author":{"name":"Juan Carlos Jiménez","username":"CGShape"},"text":"Nesta plataforma, podemos oferecer preços muito melhores para nossos clientes. Por exemplo todos os nossos produtos estão com preços 20% mais baixos do que em qualquer outro lugar e ainda obter mais lucros em comparação com outras lojas!"},"quote_4":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"Este é o melhor sistema para a data. Todas as outras lojas online 3D dar uma% de nossos lucros. Este foi o recurso que pegou nosso interesse em CGTrader desde o início. Clientes, compra on CGTrader, sabemos que a maior parte de seu dinheiro ir para o autor, que no final vai colocar mais e melhores modelos se os seus lucros melhorar."},"quote_5":{"author":{"name":"Jesús Pedrosa Vidal","username":"NoneCG"},"text":"CGTrader é a loja online 3D que tornou mais rápido crescimento de vendas e lucro para nós. O sistema de ativação de taxa fixa é o melhor que você pode encontrar, como ele recompensa os criadores de conteúdo em vez de intermediários."},"quote_6":{"author":{"name":"Ričardas Marozas","username":"pumper"},"text":"Quando recebi US $ 160 para a minha conta Paypal, não sei o que está acontecendo como se nunca tivesse acontecido antes, mas depois pensei que poderia ser uma venda CGTrader e eu estava certo. Vendido o mesmo pacote em Turbosquid no mês passado por US $ 189, mas depois de 40% taxa de royalties e imposto de 30%, tudo o que tenho foi de US $ 52,92 - centavos em comparação com $ 160 de CGTrader. Além disso, o cliente pagamos US $ 29 menos - agora que é um bom negócio."},"quote_7":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"No começo eu estava cético sobre como um bom negócio para os vendedores que não é a norma para grandes 3D online lojas. Quando eu aprendi sobre o sistema de ativação de taxa fixa, fiquei impressionado com a forma tão criativa de vender ativos digitais. Sell, em seguida, ativar, pagando uma taxa de US $ 5. Tudo faz sentido para mim. Fiz upload de alguns para experimentar e realmente, quando eu tenho a minha primeira venda, que foi de 100% a realeza!"},"quote_8":{"author":{"name":"Threed Ken","username":"3DKen"}
16518,"text":"Eu estava tão acostumado a muitos outros sites que cobram entre 30% - 60% de comissão até que eu descobri CGTrader. Como um vendedor, eu definitivamente como a alta porcentagem de royalties retornos."},"quote_9":{"author":{"name":"Threed Ken","username":"3DKen"},"text":"A experiência vendedor é fantástico. submissão do produto é uma brisa. E sim, o sistema me permitiu salvar um rascunho da minha submissão produto a cada etapa do caminho."}}},"hyperwallet":{"failed_payment_agreement":"Algo inesperado aconteceu","success_notification":"O acordo de pagamento foi atualizado com sucesso.","success_payment_agreement":"O acordo de pagamento foi enviado com sucesso."},"item-search-filters":{"3d-models-url":"3d-modelos","3d-print-models-url":"3d-impressao-modelos","dedicated-search-page-url":{"animated":"/animado-","free":"/livre-","low_poly":"/de-baixo-poli-","pbr":"/pbr-","rigged":"/fraudada-"}},"item_use_case":{"not_valid":"Arquivo inválido","pundit":{"not_authorized":"Você não está autorizado a executar esta ação."},"title":"Caso de uso para %{item_name}"},"items":{"already_purchased":"Você já comprou este item.","filters":{"attributes":"Atributos","free":"Livre","max_value":"$ 500 +","min_value":"$ 2","reset":"filtros de reposição"},"footer":{"next":"Próximo","per_page":"Por página:","previous":"Anterior"},"geometry_type_forbidden":"Tipo de geometria proibido!","last_card":{"button_text":"Pedir um modelo personalizado 3D","text":"Não foi possível encontrar o que você está procurando?"},"migrations":{"errors":{"invalid_owner":"O novo proprietário não pode assumir os itens.","unpublished_items":"Todos os itens devem ser publicados."}},"new_price_applied":"Os novos preços foram aplicados com sucesso a todos os itens.","new_price_applied_to_selected_items":"Os novos preços foram aplicados com sucesso aos itens selecionados.","new_prices_to_filtered_models":"Os novos preços foram aplicados com sucesso a todos os itens filtrados.","order_processed":"Pedido processado.","order_processing":"Processando pedido.","something_went_wrong":"Algo deu errado","title_error":"O título não deve conter símbolos não latinos","uniq_user":"já enviou um caso de uso para este item","upload_error":"Erro de upload.","validate_license_for_zero_price":"não é válido para item gratuito","validate_minimum_price":"As ofertas de preço não devem ser inferiores a 50% do preço original","verify_printable_formats_error":"Por favor, envie pelo menos um arquivo .stl, .obj ou .wrl.","you_can_not_delete_models":"Você não pode excluir modelos"},"jobs":{"about":{"hire_designers_page":{"hero":{"description":"O 3D Projects facilita a contratação de artistas 3D freelancers de qualidade e ajuda você a resolver todos os desafios de modelagem.","heading_html":"Conheça a melhor plataforma de freelancers para seus \u003cstrong\u003etrabalhos de modelagem 3D\u003c/strong\u003e"},"sections":{"call_to_action":{"description":"Publique seu projeto gratuitamente e veja como é fácil resolver seu próximo desafio de modelagem com Projetos 3D.","title":"Contrate os melhores talentos 3D hoje mesmo!"},"freelancer_selection":{"title":"Faça do seu próximo projeto 3D um sucesso","widest_selection":{"description":"Não se contente com menos. Com 40.000 designers 3D freelancers, a CGTrader é a maior plataforma para conectar você com especialistas qualificados: modeladores, animadores, riggers, profissionais de impressão 3D e muito mais.","title":"A maior seleção de freelancers 3D"}},"how_it_works":{"steps":{"describe":{"description":"Diga-nos o que você precisa. Use nosso formulário simples, adicione imagens e publique sua oferta freelance em minutos.","title":"Descreva seu projeto"},"pay":{"description":"Pague o orçamento antecipadamente com segurança à CGTrader e informe seu freelancer que você está pronto para começar. Converse, compartilhe anexos e acompanhe o progresso do projeto convenientemente no seu espaço de trabalho. Libere o pagamento quando estiver satisfeito com o resultado final.","title":"Pague pelo resultado"},"select":{"bullets":["Receba orçamentos de designers qualificados diretamente na sua caixa de entrada","Converse em tempo real com os designers","Navegue pelos projetos anteriores deles","Você também pode pesquisar, filtrar e convidar freelancers de acordo com habilidades 3D, avaliações e preferências de softw
16518are ou formato"],"title":"Selecione o melhor freelancer"}},"title":"Conclua seu trabalho em 3 etapas simples"},"payment_quality":{"description":"A CGTrader garante que os serviços freelancers atenderão aos seus requisitos e serão entregues no prazo. Nossa equipe de QA está disponível sempre que necessário.","title":"Qualidade garantida"},"secure_payments":{"description":"Nosso serviço de custódia garante que seus fundos estejam protegidos e que você só pague quando estiver satisfeito com o resultado final.","title":"Pagamentos seguros"},"workspace_convenience":{"description":"Esqueça a frustração de alternar entre abas e diferentes ferramentas. Com o espaço de trabalho dedicado, você pode gerenciar tudo em um só lugar: múltiplos projetos, faturas, conversas, compartilhamento de arquivos e muito mais.","title":"Conveniência em uma única tela"}},"shared":{"post_project_cta":"Publicar um projeto"}},"navigation":{"find_jobs_cta":"Encontrar trabalhos 3D","hire_freelancers_cta":"Contratar freelancers 3D","how_it_works":"Como funciona","why_projects":"Por que Projetos 3D"}},"agreed_to_terms_of_use":"Você deve concordar com os Termos e Condições do 3D Projects para continuar.","budget_error_message":"é muito alto","check_designer_uniqueness_error":"O designer só pode ser convidado uma vez","check_ownership_error":"O convidador não é o proprietário do projeto","check_status_error":"Designers só podem ser convidados para projetos aprovados.","deadline_error_message":"O prazo deve estar no futuro","discount_code_error":"Desculpe, o código de desconto não existe ou expirou.","email_subject":"Oportunidade de projeto 3D: %{subject}","error_check_application_uniqueness":"Você só pode se candidatar uma vez","error_check_applier":"Você não pode se candidatar ao seu próprio projeto 3D","error_check_job_availability":"Usuário não autorizado a se candidatar","error_check_job_status":"As candidaturas a projetos estão suspensas até a conclusão do nosso novo processo de moderação, geralmente em até 3 dias úteis. Agradecemos sua compreensão!","modeling_request_failed":"Falha na solicitação de modelagem!","offer_has_been_sent":"Obrigado, sua oferta foi enviada!","offer_is_closed":"A oferta está encerrada.","payment_successful":"Pagamento realizado com sucesso.","payment_under_review":"O pagamento está em revisão.","share_with_yourself_error":"Não é permitido compartilhar modelos de Contrate-me consigo mesmo","system_messages":{"buyer_cancels_job":"O projeto foi cancelado e movido para a aba %{archive_url}.","buyer_edits_hire_me_template":"Os detalhes do projeto foram atualizados pelo cliente.","buyer_edits_job_details":"Os detalhes do projeto foram atualizados.","buyer_marks_job_as_finished":"O projeto foi marcado como concluído e movido para a aba %{archive_url}.","buyer_marks_job_as_finished_with_resell":"O cliente marcou o projeto como concluído e permitiu que você vendesse os resultados no CGTrader. Certifique-se de \u003cb\u003e\u003ca href=\"/profile/upload/model\"\u003eenviar\u003c/a\u003e\u003c/b\u003e o modelo criado!","buyer_negotiates":"O cliente negociou sua oferta de preço.","buyer_pays_for_the_job":"O projeto foi pré-pago e movido para a aba %{in_progress_url}.","designer_accepts_changes":"O designer revisou os detalhes atualizados.","designer_accepts_offer":"O designer aceitou sua oferta.","designer_edits_offer":"O designer editou sua oferta.","designer_negotiates":"O designer negociou sua oferta.","designer_withdraws_offer":"O designer retirou sua oferta.","job_expires":"O projeto expirou e foi movido para a aba %{archive_url}."},"template_error":"Nenhum projeto foi criado!","user_not_exists":"Usuário com esse nome de usuário não existe.","want_that_job":"Eu quero este trabalho!"},"kodama":{"invalid_request":"Solicitação inválida!"},"landing":{"consent_not_saved":"O consentimento não foi salvo","failure_notification":"Todos os campos obrigatórios da solicitação de acesso à API devem ser válidos.","success_notification":"Obrigado pela sua solicitação. Entraremos em contato em breve."},"layout":{"chunk_load_notice":"Algo deu errado ao carregar esta página. Por favor, atualize-a.","cookies_policy":{"accept":"Aceitar","learn_more":"Saber mais","text":"Nossos utilizações do website cookies para coletar dados de visitantes estatística e interação pista com comunicação de marketing direto / melhorar o nosso website e melhorar a sua experiência de navegação. Por favor, consulte o nosso Aviso de cookie para obter mais informações sobre cookies, dados que eles coletam, que podem acessá-los, e os seus direitos."},"translations_notice":{"cta":"Troque para English","text":"Olá! Estamos traduzindo CGTrader. As traduções estão em andamento e algumas seções podem não estar disponíveis em português."}},"model":{"already_purchased":"Você já comprou este produto. Você pode baixá-lo no seu \u003ca href='/profile/cart/purchased'\u003ecarrinho de compras\u003c/a\u003e. Se desejar, pode comprá-lo novamente.","animated":"Animado","artist_profile":"Perfil do artista","author":"Autor","author_get_your_payment":"O autor receberá 100% do seu pagamento. A CGTrader permite que os artistas maximizem seus ganhos e sejam incentivados a vender conteúdo de alta qualidade.","author_products":"Mais produtos de \u003ca href=\"%{author_url}\"\u003e%{username}\u003c/a\u003e","authorised_seller":"Vendedor autorizado da CGTrader","authority":"Autoridade","breadcrumb":{"3d_models":"Modelos 3D","home":"Início"},"bulk_download":{"download_all":"Baixar todos os arquivos","files_included":{"one":"%{count} arquivo incluído","other":"%{count} arquivos incluídos"},"too_large":"Este modelo é grande demais para ser baixado como um único arquivo (mais de %{limit}). Por favor, baixe os arquivos individualmente."},"buy":"Comprar","cannot_download":"Apenas usuários com %{experience_needed} pontos de reputação podem baixar mais de %{models} modelos gratuitos. Saiba mais sobre níveis e reputação \u003ca href='%{reputation_url}' target='_blank'\u003eaqui\u003c/a\u003e.","collection":"pack 3D","contact_author":"Contatar o autor","description":"Descrição","download":"Baixar","download_limit_exceeded":"Você excedeu seu limite diário de downloads. Por favor, tente novamente amanhã. Esse limite não se aplica a usuários que fizeram uma compra nos últimos 12 meses.","download_modal":{"chat_with_us":"Fale conosco","contact_support":"Entre em contato conosco pelo chat.","download_file":"Baixar %{name}","empty":"Nenhum arquivo encontrado","generic_error":"Algo deu errado, por favor tente novamente","link_error":"Pedimos desculpas, mas parece haver um pequeno problema com o seu link de download.","pbr_ready":"Compatível com PBR","preparing":"Preparando seu download...","title":"Baixar arquivos"}
16518,"format_downloads":"Downloads de formato de software 3D","game_ready":"Pronto para jogos (lowpoly)","geometry":"Geometria","license":"Licença","make_offer":"Fazer oferta","materials":"Materiais","messages":{"no_files":"Nenhum arquivo enviado","no_rating":"Ainda não há avaliações disponíveis para este modelo"},"model_details":"Detalhes do modelo 3D","pbr_textures":"Texturas PBR","plugin_used":"Plugin usado","polygons":"Polígonos","price":"Preço","print_ready":"Pronto para impressão 3D","product_id":"ID do produto","product_support":"Suporte do produto","product_type":"Tipo de produto","published":"Publicado em","rated":{"error":"Você já avaliou este modelo","success":"Obrigado por avaliar este modelo"},"rating":"Avaliação do modelo 3D","ratings":{"desc":{"negative":"Usuários que compraram este modelo 3D acham que tem qualidade inferior à esperada","positive":"Usuários que compraram este modelo 3D acham que é de boa qualidade"},"negative":"negativo","positive":"positivo"},"recently_viewed_models":"Modelos visualizados recentemente","renderer":"Renderizador","report":"Denunciar","rigged":"Rigado","scripts-plugins":"Scripts e Plugins","similar_models":"Modelos semelhantes","statistics":"Estatísticas","subscription":{"limit_reached":"Você atingiu o limite de downloads para este período de cobrança.","limit_reached_detailed":"Você atingiu seu limite de %{limit} downloads deste mês. Seu limite é redefinido em %{reset_date}. Para baixar mais modelos agora, você pode comprá-los individualmente.","limit_reached_detailed_no_date":"Você atingiu seu limite de %{limit} downloads deste mês. Para baixar mais modelos agora, você pode comprá-los individualmente."},"tags":"Tags","textures":"Texturas","title":"%{item_title} modelo 3D","unwrapped_uvs":"UVs desdobradas","uv_mapping":"Mapeamento UV","vat_notice":"Todos os preços são exclusivos de IVA","version":"Versão","vertices":"Vértices","vr":"Modelos VR"},"models":{"error":{"cannot_be_blank":" não pode estar em branco."}},"navigation":{"3d-models":{"3d-printing":"Arquivos de impressão 3D","discounts":"Descontos","find_designers":"Encontrar melhores designers","free":"Modelos livre 3D","pbr":"PBR","scanned_3d-models":"Modelos digitalizados 3D","scripts_apps":"Scripts e Aplicativos","show-all":"Mostre tudo","textures":"Texturas","title":"Modelos 3D","vr_low-poly":"VR / Baixa-poli"},"already_added_message":"Já foi adicionado ao carrinho","already_purchased_message":"Você já comprou este item.","aria":{"account":"Conta","cart":"Carrinho","close_menu":"Fechar menu","logo":"CGTrader - Modelos 3D / Designers 3D","messages":"Mensagens","notifications":"Notificações","open_menu":"Abrir menu","search":"Buscar","search_by_image":"Buscar por imagem"},"budgets":{"below_10":"Abaixo de $10","collections":"Pacotes","free":"Grátis","more_than_20":"Mais de $20","more_than_50":"Mais de $50","sale":"Em Oferta","subscription":"Assinatura"},"cart_limit_reached":"Você atingiu o limite de 500 itens no seu carrinho. Para continuar comprando, por favor, complete seu pedido atual e inicie um novo para qualquer item adicional.","cart_widget":{"continue_shopping":"Continuar comprando","discount":"Desconto com cupom empresa: ","discount_applied":"Descontos aplicados:","final":"Preço final: ","have":"Você tem ","items":" itens no seu carrinho de compras.","left_to_unlock_discounts":"Faltam {{leftToUnlock}} para desbloquear descontos","potential_discount":"Desconto potencial:","proceed":"Fazer o check-out","recently_viewed_empty_text":"Comece a explorar e seus itens vistos recentemente aparecerão aqui.","recently_viewed_empty_title":"Nada aqui ainda","recently_viewed_tab":"Vistos recentemente","remainer":"Para ativar os pontos de CGTrader, adicione: ","sale_discounts":"Descontos aplicados: ","sale_time_left":"Tempo restante para a venda","sale_tooltip":"Os descontos das nossas campanhas de promoção só são aplicados quando o seu carrinho atinge ${{threshold}} ou mais.","sale_tooltip_text":"Este item está em promoção, mas o desconto só será aplicad
16518o quando o subtotal do seu carrinho atingir ${{threshold}} ou mais.","text":"Seu carrinho de compras está vazio.","title":"Carrinho de compras","total":"Total: ","used":"Créditos utilizados: ","view":" Ver carrinho","wishlist_empty_text":"Comece a explorar e salve seus favoritos para mais tarde — eles aparecerão aqui.","wishlist_empty_title":"Sua lista de desejos está vazia","wishlist_tab":"Lista de desejos"},"community":{"awards":"Prêmios de Designers 🏆","blog":"Blog","challenges":"Desafios","community_ratings":"Classificações da comunidade","designers":"Designers 3D","digital_art":"Concurso de Arte Digital","forum":"Fórum","galleries":"Galerias","invite":"Convidar um amigo","printlist":"3D imprimeLista","sell_3d-models":"Vender modelos 3D","title":"Comunidade","tutorials":"Tutoriais"},"conversations_widget":{"conversations":"conversações","messages":{"loading":"Carregando caixa de entrada"},"types":{"cgtrader_support":"Suporte CGTrader","file_conversion_request":"pedido de conversão de arquivos","job_application":"Projeto 3D","model_feedback":"Product feedback","model_support":"Suporte modelo","offer":"Oferta de preço","personal":"Mensagem pessoal","promotions":"promoções"}},"create_ai":{"community_gallery":"Galeria da comunidade","free_trial":"Teste gratuito","generate_3d_model":"Gerar modelo 3D","generation":{"dismiss":"Dispensar notificação","error_title":"Problema na geração","generating_title":"Seu modelo 3D está sendo gerado","open_model":"Abrir modelo","ready_title":"Seu modelo 3D está pronto!"}},"create_with_ai":"Criar com IA","credits_guest_widget":{"create":"Crie uma conta agora!","discount":" desconto","for":" Para o seu ","get":" e chegar até a ","learn_more":"Saiba mais sobre créditos Fidelidade CGTrader ","link":"aqui","purchase":"primeira compra!","sign-up":"inscrever-se","text_below_1":"Os créditos podem ser usados ​​na compra \"","text_below_2":"\"Assinar modelos marcados e cobrir até 30% de desconto do preço."},"credits_user_widget":{"condition_1":"Os pontos CGTrader podem ser aplicados na compra de modelos marcados com o símbolo \u003ci class=\"custom-font-icon custom-font-icon--small icon-wallet\"\u003e\u003c/i\u003e, proporcionando até 30% de desconto no preço.","condition_2":"Os pontos só podem ser usados se o valor total do seu carrinho for de \u003cstrong\u003e$%{minimum}\u003c/strong\u003e ou mais.","condition_3":"Durante promoções ou vendas ativas, os pontos não podem ser usados.","condition_4":"Lembre-se de que os pontos têm uma data de validade - seus pontos expirarão em \u003cstrong\u003e%{days}\u003c/strong\u003e dias.","earn_rate":"Crédito ganhar taxa ","first_text_1":"Considere isso como um presente para sua primeira compra - você pode usar créditos ao comprar \"","first_text_2":" \"Os modelos marcados e obter até 30% de desconto de seu preço. Credits só podem ser utilizados se o valor total de seu carrinho for igual ou superior a US","important":"IMPORTANTE:","learn_more":"Saber mais","lifetime_spending":"Gastos Lifetime: ","list_item":"Ao aumentar seus gastos vida na CGTrader, o seu crédito ganhar aumentos da taxa também.","message":" Os créditos não podem ser usados ​​durante as campanhas venda, mas você ainda pode aumentar seu nível de royalties e recolher mais créditos para a sua próxima compra.","more_credits":"Como faço para obter mais créditos?","next_level":"Próximo nível: ","notice":"AVISO: Os pontos CGTrader não podem ser usados durante a venda em andamento, mas estarão disponíveis novamente assim que a venda terminar. Enquanto isso, confira ótimas ofertas em modelos 3D \u003ca href=\"https://www.cgtrader.com/3d-models?sale_off=1\"\u003eaqui\u003c/a\u003e","par_1":"Você tem ","par_2":" créditos CGTrader.","par_2_variation":" em pontos que você pode gastar quando nenhuma promoção estiver disponível.","regular_text_1":"Os créditos podem ser usados ​​na compra \"","regular_text_2":" \"Assinar modelos marcados e cobrir até 30% de desconto de seu preço.","title":"CGTrader desconto de fidelidade"},"enterprise":{"management":"Gerenciamento de ativos 3D","title":"Produção de Conteúdo 3D"},"hire_a_freelancer":"Contratar freelancer","item_add_message":"Com sucesso, foi adicionado ao carrinho","item_restricted_message":"Este item não pode ser comprado devido a sanções impostas ao país do designer (RU/BY).","navbar":{"actions":{"new":"Novos","top_selling":"Mais vendidos","trending":"Em alta"},"budgets":{"below_10":"Abaixo de $10","free":"Grátis","more_than_20":"Mais de $20","more_than_50":"Mais de $50","sale":"Em Oferta","up_to_10":"Até $10"}},"projects":{"3d_customs":"3D personalizado","3d_printing":"Impressão 3D","about":"Sobre Projetos 3D","browse":"Procurar Projetos","buyer_perspective":"Contratar designers 3D","for_designers":"Para designers","my_projects":"Meus projetos","seller_perspective":"Projetos 3D Freelance","start":"Iniciar Um Projeto","work":"Trabalho Com Cgtrader"},"sell_3d-models":"Vender modelos 3D","sellers_hub":"Central do vendedor","subscription_includes_item_message":"Este item já está incluído na sua assinatura.","tooltips":{"cart":"Carrinho de compras","conversations":"Minhas conversas","discounts":"CGTrader desconto de fidelidade","notifications":"Notificações","publisher":"Editor"},"user":{"ban_notification":"Esta conta de usuário está suspensa. Entre em contato com nosso suporte para obter uma explicação.","cannot_refer_yourself":"você não pode se indicar a si mesmo","email_confirmation":"Seu endereço de e-mail foi verificado.","email_reconfirmation":"já foi confirmado, por favor tente fazer login","ends_at":"Termina em: %{date}","handle_error_tfa_code_missmatch":"O código de segurança que você digitou está incorreto. Por favor, tente novamente.","handle_error_tfa_code_missmatch_last":"Seu código de segurança expirou. Se quiser continuar, clique em reenviar e-mail e insira o 
16518novo código fornecido.","handle_error_tfa_default":"Algo deu errado, por favor tente novamente mais tarde.","handle_error_tfa_expired":"Seu código de segurança expirou.","hundred_percent_royalties_popover":"Você está participando de uma campanha temporária de 100% de ganhos: toda a receita das vendas de novos modelos será sua até o final da campanha","invalid_email_message":"Por favor, insira um endereço de e-mail válido para continuar.","login":"Conecte-se","meta_description":"Confira o perfil de designer 3D %{username} na CGTrader, seu portfólio e modelos 3D disponíveis. Descubra os melhores modelos 3D dos principais artistas de modelagem da indústria.","meta_title_suffix":"Designer 3D","not_valid_birthday":"A data de nascimento não é válida.","offline":"Offline","online":"Online","page_title":"Perfil do designer","rate_models_limit":"Você não pode avaliar mais modelos hoje.","semantic_description":"Modelos 3D, obras e portfólio do designer. Confira suas criações na CGTrader","semantic_title":"Perfil do designer","session_failure":"Nome de usuário/e-mail ou senha incorretos","session_password_expired":"Sua senha expirou devido à inatividade. Redefina sua senha pelo link enviado ao seu e-mail.","session_two_fa_failure":"Falha na verificação do código","session_unconfirmed":"Seu endereço de e-mail ainda não foi confirmado.","token_not_recognized":"O token não foi reconhecido","twitter_shareable_text":"Um #3dartist que vale a pena seguir","username_error":"não deve conter espaços ou símbolos não latinos"},"user_menu":{"buyer":{"freelancers":"Encontrar Freelancers","purchases":"Minhas compras","sell":"Vender modelos 3D","wishlist":"Modelo 3D Lista de Desejos"},"dashboard":"Admin dashboard","logout":"Sair","public_profile":"Perfil público","seller":{"analytics":"Analítica","find_new_projects":"Encontrar novos projetos 3D","freelance":"Projetos 3D Freelance","freelance_cgtrader":"Freelance com CGTrader","models":"Meus modelos","publish_project":"Publicar um projeto","publishing":"Editor","sales":"Minhas vendas","subscription":"Assinatura","wishlist":"Modelo 3D Lista de Desejos"},"settings":"Configurações"}},"owly":{"failed_set_pending":"Falha ao definir como pendente.","failed_to_approve":"Falha ao aprovar.","failed_to_suspend":"Falha ao suspender.","failed_to_update_license":"Falha ao atualizar a licença.","failed_to_update_status":"Falha ao atualizar o status de aprovação."},"print_models":{"browse_all_makes":"Navegar por todos os Criações 3D","browse_all_models":"Navegar por todos os modelos","description":"Descubra modelos 3D prontos para impressão, assets imprimíveis e arquivos STL para jogos, arte, projetos domésticos e muito mais.","makes_description":"Explore as mais recentes Criações 3D enviadas pela comunidade – exemplos reais que mostram como os modelos 3D ficam no mundo físico.","makes_title":"Novas Criações 3D","new_description":"Confira as impressões 3D mais recentes adicionadas à nossa coleção – designs frescos prontos para baixar e imprimir em seu próximo projeto.","new_title":"Novos modelos 3D para impressão","title":"Modelos 3D de impressão em alta"},"profile":{"account_settings":{"account":{"email":"E-mail","submit":"Salvar","title":"Perfil","update":{"updated":"Perfil atualizado"},"username":"Nome de Usuário"},"email_settings":{"descriptions":{"ai_studio_notifications":"Notificações sobre suas gerações do CGTrader AI, incluindo quando um modelo que você ainda não abriu está pronto","communication":"Informações sobre sua comunicação com outros usuários da CGTrader, incluindo mensagens pessoais, novas ofertas de preço e trabalhos personalizados, feedback e solicitações de suporte a produtos","news":"Informações sobre produtos CGTrader, anúncios de novos recursos e notícias da comunidade","onboarding_and_edu":"Informações sobre seu processo de integração e dicas para ajudá-lo a aproveitar ao máximo a CGTrader","push_notifications":"Notificações sobre atividades e atualizações importantes da conta","sales_and_offers":"Informações sobre ofertas especiais, vendas de modelos 3D e créditos bônus CGTrader","seller_sold_item":"Informações sobre seus modelos 3D vendidos e pagamentos recebidos"},"privacy_policy_html":"Por favor, leia nossa \u003ca href='https://www.cgtrader.com/pages/privacy-policy' 'target='_blank'\u003ePolítica de Privacidade\u003c/a\u003e para mais informações. ","t
16518itles":{"ai_studio_notifications":"Notificações do CGTrader AI","communication":"Comunicação (Mensagens pessoais, feedback de modelos 3D, atividade da comunidade, ofertas)","news":"Notícias 3D / Atualizações CGTrader","onboarding_and_edu":"Onboarding \u0026 Educação","push_notifications":"Notificações Push","sales_and_offers":"Vendas \u0026 ofertas especiais","seller_sold_item":"Informações de Vendas \u0026 Pagamentos"},"tracking_attributes":{"ai_studio_notifications":"Notificações do CGTrader AI","communication":"Comunicação","news":"Notícias 3D / Atualizações CGTrader","onboarding_and_edu":"Onboarding \u0026 Educação","sales_and_offers":"Vendas \u0026 ofertas especiais","seller_sold_item":"Informações de Vendas \u0026 Pagamentos"},"update":{"updated":"Seu e-mail foi atualizado com sucesso."}},"gdpr_success_notification":{"opt_in":"Obrigado! Você se re-inscreveu com sucesso em nossos e-mails.","opt_out":"Você cancelou a inscrição de nossos boletins com sucesso. Você pode alterar esta preferência a qualquer momento visitando as configurações do seu perfil.\n"},"location_tooltip_text":"\u003cdiv\u003eEsta caixa de seleção permite mostrar a localização na página de Perfil.\u003c/div\u003e \u003cdiv\u003eObserve que, em qualquer caso, por razões de conformidade, sua localização será mostrada aos seus compradores (se você for um vendedor) e aos vendedores de quem você comprou.\u003c/div\u003e","moneybookers":{"account":"Conta Skrill"},"password":{"current_password":"Senha atual","new_password":"Nova senha","submit":"Alterar Senha","title":"Senha","updated":"Sua senha foi atualizada com sucesso. Agora você pode usar sua nova senha para fazer login.","verify_password":"Verificar senha"},"paypal":{"account":"Conta do PayPal","submit":"Salvar","title":"Configurações do PayPal","update":{"updated":"Suas configurações do PayPal foram atualizadas"}},"personal_info":{"areas_of_expertise":"Áreas de Especialização","country":"País","description":"Algumas frases sobre você","full_name":"Nome completo","languages":"Idiomas","preffered_tools":"Ferramentas Preferidas","submit":"Salvar","title":"Informações pessoais e profissionais","update":{"updated":"Suas informações foram atualizadas com sucesso"},"website":"Website"},"personalization":{"no_muted_title":"Nenhum designer silenciado","title":"Designers Silenciados","tooltip":"Os modelos 3D dos designers listados são excluídos dos seus resultados de pesquisa.","unmute_designer":{"confirm":"Você tem certeza?"}},"professional_info":{"allow_private_job_requests":"Os compradores poderão enviar solicitações de trabalho 3D personalizadas para você.","buyer_engagement_program_html":"Atraia mais clientes permitindo que eles comprem seus modelos com CGTrader Points. \u003ca href='https://www.cgtrader.com/pages/cgtrader-loyalty-discounts-explained' target='_blank'\u003eMais informações\u003c/a\u003e\n","pbr_program":"A taxa de ganho mais baixa é aplicada apenas quando o arquivo convertido é baixado","pbr_program_agreement":"Você concordou em participar do programa PBR por 24 meses em %{date}. Veja os Termos \u0026 Condições para mais detalhes. Por favor, entre em contato com nosso suporte caso tenha dúvidas.","product_analytics_participation_html":"Eu participo de \u003ca href='https://www.cgtrader.com/profile/analytics'target='_blank'\u003eAnalytics\u003c/a\u003e e concordo com seus Termos e Condições\n","product_analytics_participation_tooltip_html":"Você concordou em participar do Analytics e aceitou os Termos e Condições do Analytics (\u003ca href='https://www.cgtrader.com/pages/cgtrader-analytics-terms-of-service'target='_blank'\u003elink\u003c/a\u003e) em %{accepted_at}. Se você deseja encerrar sua participação, entre em contato com o suporte da CGTrader\n"},"security":{"allow_mfa":"Ative a verificação em 2 etapas para proteger sua conta","use_authenticator_app":"Use um aplicativo autenticador para a verificação em 2 etapas","use_authenticator_app_tooltip":"Digitalize o código QR com seu aplicativo autenticador (Google Authenticator / Authy) para gerar códigos de verificação.","use_email":"Use e-mail para a verificação em 2 etapas"},"sidebar":{"general_settigns":"Configurações Gerais","member_since":"Membro Desde","paypal":"PayPal","title":"Informações da Conta"},"title":"Minha Conta"},"conversations":{"conversations":"conversações","messages":{"loading":"Carregando caixa de entrada"},"types":{"cgtrader_support":"Suporte CGTrader","file_conversion_request":"pedido de conversão de arquivos","job_application":"Projeto 3D","model_feedback":"Product feedback","model_support":"Suporte modelo","offer":"Oferta de preço","personal":"Mensagem pessoal","promotions":"promoções"}},"meta":{"description":"Explore modelos 3D de alta qualidade de %{username} no CGTrader. Navegue por ativos exclusivos para jogos, VR/AR e visualização.","title":"Designer de modelos 3D: %{username}"},"my_products":{"suspension_appeal":{"reasons":{"not_available":"N/A"}}},"settings_updated":"Suas configurações foram atualizadas com sucesso."},"publishing":{"banned_error":"A sua conta foi banida por publicar conteúdo spam. Se acredita que isto é um erro, por favor contacte [email protected].","files_not_finalized":"Um ou mais ficheiros não terminaram de carregar e não podem ser publicados. Remova-os e carregue-os novamente antes de publicar.","spam_suspect":"A sua conta não está autorizada a publicar este modelo. Se acredita que isto é um erro, por favor contacte [email protected]."},"rating":{"anonymous_buyer":"Comprador Anônimo","errors":{"not_verified_buyer":"Apenas compradores verificados podem deixar uma avaliação"}},"routes":{"3d-models":"3d-modelos","3d-print-models":"3d-impressão-modelos","animated-3d-models":"animado-3d-modelos","controllers":{"three_d_studio":{"3d-models":"3d-models","images":"images"}},"discover":"descobrir","free-3d-models":"livre-3d-modelos","free-3d-print-models":"livre-3d-impressão-modelos","low-poly-3d-models":"de-baixo-poli-3d-modelos","pbr-3d-models":"pbr-3d-modelos","pbr-textures":"pbr-textures","rigged-3d-models":"fraudada-3d-modelos","search":"search"},"sale_off":{"no_overlapping_campaigns":"As datas da campanha se sobrepõem a outra campanha de venda existente.","parse_discount_rules_text":"A linha #{index} não contém o parâmetro obrigatório 'discount:'"},"search":{"not_found":"Assinatura de pesquisa não encontrada","subscription_created":"Assinatura de pesquisa criada","successfully_unsubscribed":"Você cancelou a assinatura de pesquisa com sucesso"},"subscription_opt_ins":{"lock_period_error":"Some items cannot be opted out before the six-month lock period ends.","missing_items_error":"Items not found: %{items}."},"subscriptions":{"flash":{"active_subscription":"Você já tem uma assinatura ativa.","premium_upgrade_unavailable":"A atualização para Premium está temporariamente indisponível. Tente novamente mais tarde.","update_payment_method":"Sua assinatura tem um problema de pagamento. Atualize sua forma de pagamento em vez de comprar uma nova assinatura."},"payment_form":{"current_price":"%{price}","features":{"first":"%{downloads} recursos / mês — baixe modelos ou gere com IA","second":"Acesso instantâneo a mais de %{models_count} modelos 3D essenciais","third":"Livre de royalties, sem IA"},"from_price_label":"a partir de","original_price":24.99,"per_model_label":"por modelo","plan_name":"Assinatura CGTrader Essentials — plano anual, cobrado mensalmente","premium":{"features":{"first":"%{downloads} recursos / mês — baixe modelos ou gere com IA","fourth":"Livre de royalties, sem IA","second":"Acesso instantâneo a mais de %{models_count} modelos acessíveis no total","third":"Modelos de nível profissional não incluídos no Essentials"},"plan_name":"Assinatura CGTrader Premium — plano anual, cobrado mensalmente"},"price_unit":"/mês","vat_amount":"%{vat_amount}"}},"tag":{"description":{"bottle":"Os cientistas dizem que garrafa de vinho por dia não é ruim para você, mas quando se trata de bebidas mais fortes, como uísque ou bourbon, devemos realmente prestar atenção a nossa ingestão de álcool. Obviamente, uma taça de champanhe no refrigerador da maneira do bar parece que uma garrafa plástica de água, mas pensar sobre a possibilidade de viver uma vida mais longa e cerveja vai parecer um sacrifício razoável. Esta categoria oferece uma vasta selecção de garrafa 3D modelos, que variam a partir de objectos relacionados com o álcool como cremalheiras do vinho e saca-rolhas para todos os tipos de garrafa modelos em 3D (a partir de champô para o azeite).","brain":"O cérebro é um dos órgãos maiores e mais complexas do corpo humano. Devido à sua estrutura e função complexa, a anatomia do cérebro também é muito complexo. No entanto, os cientistas aprofundar a arquitetura do cérebro humano, descobrindo segredos incompreensível. Uma vasta gama de realistas e detalhados modelos 3D do cérebro que estão prontos para aplicações de computação gráfica como a publicidade, a visualização 3D, jogos 3D, web e qualquer outro domínio relacionado com o desenho em 3D está disponível para download.","building":"Não está claro onde arquitetos modernos se inspiram, mas alguns dos edifícios famosos do mundo são tão de vanguarda que eles se parecem com visões do futuro. E não me refiro apenas a parte exterior ... Em mãos capazes de Construção padrão se torna uma criação arquitetônica de tirar o fôlego com um inovador design interior, independentemente de se tratar de um escritório ou um edifício urbano. Nossa selecção de construção de modelos 3D está pronto para download e pode ser aplicado em vários campos - animação 3D, design gráfico, publicidade, jogos 3D ou web.","camera":"Fotografia e filmagem é atualmente um dos mais campos de carreira populares entre os jovens, mas nem todo mundo com uma câmera profissional é um artista ou um fotógrafo. Com a qualidade de câmeras de telefones melhorando ano a ano, vemos pessoas tirar fotografias em cada passo e aqueles que têm a imagem recém-desenvolvida editar habilidades são verdadeiramente célebre. A vida parece um pouco diferente quando você está do outro lado da lente, mas não vamos deixar levar ... Uma vasta gama de câmera detalhada 3D modelos está disponível para download para ser usado por designers gráficos, animação e desenvolvedores de jogos e profissionais que trabalham em outros campos relacionados com o 3D.","car":"Olhando para estes modelos muito detalhados e altamente realistas carro 3D, você pode quase ouvir o ronco do motor! Uma vasta gama de veículo 3D modelos pode tornar sua vida muito mais fácil, se você está lidando com computação gráfica, animação ou qualquer outro campo relacionado 3D.","cell":"Os cientistas estimam que o corpo humano contém entre 5 bilhões para 200 milhões de trilhões de células. Plantas e animais têm células também. Não quero aborrecê-lo com este material biologia, mas a vasta gama de modelos 3D de células que temos cobre tudo, desde o sangue, vírus e DNA de medula óssea e paredes celulares. Não é isso ... desesperador?","chair":"Tomamos sentados em cadeiras para concedido e nem sempre perceber o quanto charme eles podem adicionar à nossa casa ou escritório interior. Cadeira é realmente uma das peças centrais de móveis em todo o agregado familiar. Aqui você pode encontrar modelos 3D cadeira design apelativo que podem ser aplicados em vários campos de alta qualidade e - de publicidade e visualização 3D para jogos 3D.","city":"A vida da cidade é um jogo excêntrico: ruas repleta de táxis corrida, edifícios modernos, pedestres no escritório veste perfeitamente adaptados, dormindo pontes e parques ... de Xangai, para Nova Iorque (! Manhattan), a Londres ou Paris - cada cidade tem o seu própria magia. Você pode escolher a partir desta rica seleção de modelos em 3D da cidade detalhado que pode ser usado em um número de campos - publicidade, computação gráfica, animação, visualização 3D ou jogos. Explore a vida urbana.","dragon":"Dragão é provavelmente a criatura mais popular viver no mundo da fantasia. Ele tem destaque em tantos mitos de culturas diversas, animações, filmes e livros que se tornou lendária. É especialmente amado por desenvolvedores de jogos, como acrescenta o charme mágico para cavernas escuras e masmorras. Uma vasta gama de realista, equipou modelos dragão 3D que estão prontos para aplicações de computação gráfica como a publicidade, a visualização 3D, jogos 3D, web e qualquer outro domínio relacionado com o desenho em 3D está disponível para download.","earth":"O nível de detalhe na vista de terra recentemente adicionados em mapas do Google chocou um número de pessoas. Você pode explorar todo o planeta - oceanos, montanhas e desertos, descobrir as cidades e estradas escondidas, sem sair de sua casa. Isso é algumas coisas incríveis Mãe Terra não tenha experimentado antes. Uma seleção de modelos 3D terra realistas que estão prontos para aplicações de computação gráfica como a publicidade, a visualização 3D, jogos 3D, web e qualquer outro domínio relacionado com o desenho em 3D está disponível para download.","engine":"Algumas pessoas são capazes de citar o motor simplesmente ouvir o som que faz, e que é verdadeiramente surpreendente. Independentemente de se tratar de um motor a jato ou um motor de carro, eles estão extremamente máquinas complicado que usamos com tanta frequência, mas não gastar muito tempo pensando sobre isso. Um motor é um motor (até que ele pára de funcionar). Esta categoria tem uma gama de diferentes tipos de motor 3D modelos, e também inclui peças de motor como hélice ou cilindro, por isso estamos confiantes de que você vai encontrar o que você precisa.","female":"Não é brincadeira que existem diferenças significativas entre o cérebro masculino e feminino, o que significa que é normal que um homem e uma mulher se comportam média, sentir e responder de maneiras completamente diferentes. Mas não é tudo sobre a anatomia. Sem história de amor, filme ou jogo de sempre puxou-lo sem um caráter forte menina, causa o poder de charme senhora é inegável. Uma rica selecção de realistas 3D modelos femininos pode oferecer-lhe tudo o que precisa - a partir de modelos 3D de nudez para as vias respiratórias feminino.","hand":"A razão que nós usamos nossas mãos para muitas coisas é a sua extraordinária anatomia. O polegar sozinho é controlado por nove músculos separados. É difícil de acreditar, mas os primeiros mãos eram mais exótico do que qualquer mão hoje - algumas espécies tinha sete dedos. O corpo humano evoluiu significativamente. Um braço ou uma mão como a vemos hoje parece em nada com suas primeiras versões. Uma vasta gama de realista, manipuladas mão modelos 3D que estão prontos para aplicações de computação gráfica como a publicidade, a visualização 3D, jogos 3D, web e qualquer outro domínio relacionado com o desenho em 3D está disponível para download.","head":"Na anatomia humana, a cabeça é a porção superior do corpo humano. Modelando uma cabeça humana 3D pode provar um pouco complicado, porque (ainda que há tantas formas e tamanhos diferentes) nós apreciamos, cabeças de apar
16518ência realista simétricas - há engraçados olhos, orelhas, nariz, lábios ou linha da mandíbula, por favor. Rosto é uma área sensível e que seria apenas a planície rude para se divertir com isso. Nossa selecção engloba masculino realista, fêmea, criança e personagens da história até mesmo como Harry Potter e Batman ou celebridades como Tom Cruise e Scarlett Johansson cabeças que estão prontos para download.","helmet":"Lembre-se sempre que a saúde e a segurança vem em primeiro lugar, para proteger a sua cabeça é uma boa idéia. cavaleiros medievais, Daft Punk e até mesmo os capacetes desgaste Ironman, super-heróis tão regulares como os ciclistas ou os construtores não deve se preocupar sobre olhar uncool. Esta coleção de capacete 3D modelos que está disponível para download será útil se você estiver trabalhando nas áreas de design gráfico, animação 3D, jogos 3D ou visualização.","house":"É uma verdade antiga que a casa de um homem é seu castelo, mas quando se trata de construir a casa perfeita ou comprar um apartamento ideal, nós tendemos a perder a cabeça sobre ele. Felizmente, este amplo espectro de modelos de casa em 3D que engloba tudo, desde detalhes arquitetônicos, interior e exterior, para móveis, irá ajudá-lo a criar modernos 3D visualizações para aliviar essa dor.","human":"Quando você começar a explorar a anatomia humana, você percebe que as pessoas são tão intrincada e máquinas complexas que leva o seu fôlego. Cada combinação parte do corpo, músculos, articulações, órgão ou osso está conectado em um único sistema, nada funciona isoladamente! Estes super-realistas modelos humanos 3D permitirá que você examinar as estruturas anatômicas de cada ângulo.","lamp":"Se você olhar em torno de sua casa e contar os tipos de lâmpadas que você possui, você ficará surpreso: leitura, mesa, parede, teto, mesa, tripé lâmpadas ... É o suficiente para fazer girar a cabeça! para não mencionar os estilos que eles vêm em - de lâmpadas modernas extravagantes para retro ou futurista elegante. Nesta categoria temos todos os tipos de lâmpada 3D modelos, assim navegar e baixar para usar em design gráfico, 3D visualizações ou qualquer outro campo relacionadas com 3D.","light":"Alguma vez você já se perguntou por que uma lâmpada é o símbolo de uma idéia brilhante? Certamente, uma idéia brilhante pode iluminar a arquitetura da mente, mas não é isso. Uma luz de rua, um lustre ou qualquer outro tipo de lâmpada não teria a menor chance de tomar esse ponto. Se da próxima vez quando você olha para o teto e olhar para algum tipo de luz de arquitetura a idéia de gênio não bater em você, pode ser porque você está olhando para um sistema de iluminação errado ... Nossa selecção de luz de alta qualidade modelos 3D vai provar útil se você trabalhar nas áreas de arquitetura, computação gráfica, animação 3D ou design.","machine":"Imagine se todas as máquinas de repente parou de funcionar: no café, sem lavar roupa conveniente, há dinheiro pontos em uma esquina da rua, sem tempo de ginásio, não superfaturada máquinas de venda automática ... soa deprimente, não é? Estamos tão acostumados a esses eletrodomésticos e aparelhos eletrônicos que seria, literalmente, parar o giro mundo! Felizmente, a nossa selecção de máquina detalhado 3D modelos cobre tudo, desde metralhadoras a produtores de electricidade que serão úteis para os designers que trabalham com computação gráfica, publicidade, animação 3D, jogos 3D ou web.","man":"Uma vasta coleção de modelos 3D homem está disponível para download. Homem de assento, homem simples, o homem voar ou homem médio, nós temos tudo - exceto que o homem perfeito que todos está atrás. Ele é um personagem suspeito e nós odiamos para trazer isto, mas pode ser um mito ... Mas hey, os seres humanos nascem para ser incrível, não perfeito.","robot":"R2D2 é provavelmente o robô mais famoso do mundo, mas certamente não é o único. De industrial de brinquedo para robôs humanos, vemos uma escolha cada vez mais amplo dessas máquinas mecânicas, que é ao mesmo tempo maravilhoso e um pouco preocupante. Não é muito difícil entender as pessoas que insistem que os computadores, tecnologia, robôs estão fazendo um impacto muito significativo sobre os seres humanos. Uma vasta gama de realista, manipuladas robô modelos 3D que estão prontos para aplicações de computação gráfica como a publicidade, a visualização 3D, jogos 3D, web e qualquer outro domínio relacionado com o desenho em 3D está disponível para download.","ship":"Os navios são vasos poderosos e vêm em todos os tamanhos, cores e formas. Eles podem ser classificados como navios de carga, navios de guerra ou navios de passageiros, mas todos eles operam em águas de profundidade. Se você tem experimentado o desconforto da doença de movimento, pode não ser um de seus meios favoritos de transporte, mas muitas pessoas consideram passeios de barco como bastante elegante e agradável. Nossa selecção de modelos 3D navio está pronto para download e irá cumprir as normas de até mesmo o capitão mais exigente.","skeleton":"O esqueleto humano ou sistema esquelético (anatomia) é a estrutura interna do corpo que é composto por 270 ossos no nascimento. Ele proporciona a estrutura do corpo, e a protecção e o movimento é um dos mais complexos sistemas esquelético. Uma rica selecção de anatomicamente correto, realista e escalado esqueleto modelos 3D que são perfeitos para uso em animação 3D, design gráfico, publicidade, jogos 3D e web está disponível para download.","skull":"O mais antigo mais completo crânio humano, geneticamente intacto foi descoberto rodeado por uma variedade de animais extintos mais de 40 metros abaixo do nível do mar. É um dos esqueletos mais antigos encontrados no Novo Mundo e também inclui todos os principais ossos do corpo e um crânio intacto e conjunto de dentes. crânios humanos tornaram-se recentemente uma coisa na moda e design, mas quando você realmente dar-lhe algum pensamento, que tipo de make-nos a todos olhar como aberrações anatomia ... Esta categoria oferece uma ampla gama de modelos de crânio em 3D para ser usado em design gráfico , animação 3D, jogos 3D ou publicidade.","sofa":"Luxo ou simples, moderno ou antigo, couro ou design minimalista? Como você gosta do seu sofá? A gama de modelos sofá 3D que temos irá satisfazer até mesmo o pior perfeccionista. Quando você parar para pensar nisso, sofá é o centro de cada sala de estar, por isso você deve ser criativo ao escolher o design!","table":"As mais recentes tendências de mobiliário são tão diversos que escolher, uma mesa moda moderno está se tornando muito estressante. Costumava haver um tempo em que você tinha uma mesa de jantar simples para as suas festas modestas e isso era suficiente, mas hoje - a tabela é suposto ser uma declaração ou pelo menos um sotaque interior atraente. Esta seleção dá-lhe uma vasta gama de modelos de mesa 3D para atender a todos os caprichos.","tower":"Cada cidade tem uma torre especial que todo mundo é realmente motivo de orgulho. Pense torre Eiffel ou Torre de Pisa, ou torre da ponte de Londres, muito menos todos os modernos arranha-céus, prédios futuristas e da paisagem urbana que os turistas admiram. No entanto, quando se trata de peças industriais da cidade, água ou torres elétricas, todos tendem a voltar seus olhos para longe ... Mas devemos aprender a apreciar cada criação arquitectónica, não importa antiga, industrial ou moderno. A gama de modelos 3D torre detalhada estão disponíveis para download e pronto para aplicações de computação gráfica como a publicidade, a visualização 3D, jogos 3D, web e qualquer outro domínio relacionado com o desenho em 3D.","tree":"Se você está em uma missão para plantar algumas árvores ou plantas exóticas, você veio ao lugar certo. Esta categoria oferece uma gama de utilização fácil, muito realistas modelos de árvore 3D para artistas e arquitetos CG, que estão se esforçando para adicionar algumas hortaliças para os seus projectos. Se você aprecia boa qualidade, estamos confiantes de que essa seleção de modelos de planta árvore 3D vai atender às suas normas!","truck":"Dirigindo um caminhão é uma tarefa difícil, especialmente se você precisa encontrar o seu caminho através de um congestionamento de tráfego da cidade. Independentemente de se tratar de um caminhão de bombeiros, caminhão de lixo, caminhão do alimento ou um genérico, é preciso uma grande habilidade de manobrar estes veículos gigantes. Estamos confiantes de que esta seleção de realistas, caminhão de alta qualidade 3D modelos são perfeitos para uso em animação 3D, design gráfico, publicidade ou qualquer outro campo 3D-relacionadas.","tv":"Costumava haver um momento em que lareira era a peça central de cada lar respeitável, agora aparelho de TV com jogadores e alto-falantes é o rei da sala de estar (e até mesmo o quarto). Nos últimos anos, a televisão tem pego com as mais recentes tecnologias. Como resultado, temos telas de TV 3D inteligente, planas e de plasma, milhões de novas funções e design touchscreen. Sem televisão maravilha está empurrando cinema fora do mercado. Uma vasta gama de, detalhados modelos realistas TV 3D que estão prontos para aplicações de computação gráfica como a publicidade, a visualização 3D, jogos 3D, web e qualquer outro domínio relacionado com o desenho em 3D está disponível para download."}},"tag_promotion":{"days_left_error":"deve estar entre 1 e 30","no_other_pending_promotions_for_same_item_tag":"já possui uma promoção pendente"},"tooltips":{"buyer_loyalty_program_current_level_html":"Atualmente você gastou %{total_price_paid} no CGTrader e seu crédito ganhar taxa é de %{buyer_loyalty_rate}. \u003cbr/\u003e Isso significa que você terá %{buyer_loyalty_rate} dos créditos de cada compra.\n","buyer_loyalty_program_html":"Esta é a porcentagem de quanto você vai começar a partir de suas compras como novos créditos. \u003cbr/\u003e Ela vai aumentar até 30% de seus gastos de vida no aumento CGTrader. \u003cbr/\u003e Atualmente seu crédito ganhar taxa é de %{buyer_loyalty_rate}.\n","buyer_loyalty_program_next_level_html":"Próximo nível %{next_lvl_expenses} irá aumentar o seu crédito ganhar taxa de até %{next_lvl_expenses_rate}. \u003cbr/\u003e você precisa comprar por US $ %{left_until_next_lvl} mais para chegar próximo nível.\n"},"trending_headers":{"models":"Confira outros modelos 3D em alta","print":"Confira outros modelos 3D para impressão em alta"},"tutorial":{"destroy":"O tutorial foi excluído com sucesso.","submit_for_review_error":"Por favor, preencha todos os campos obrigatórios.","submitted":"Obrigado por enviar seu tutorial. Ele será revisado e publicado em breve."},"valvat":{"country_adjectives":{"at":"austríaco","be":"belga","bg":"búlgaro","cy":"cipriota","cz":"checo","de":"alemão","dk":"dinamarquês","ee":"estónio","es":"espanhol","eu":"europeu","fi":"finlandês","fr":"francês","gb":"britânico","gr":"grego","hu":"húngaro","ie":"irlandês","it":"italiano","lt":"lituano","lu":"luxemburguês","lv":"letão","mt":"maltês","nl":"holandês","pl":"polaco","pt":"português","ro":"romeno","se":"sueco","si":"esloveno","sk":"eslovaco"}}},"ru":{"activerecord":{"attributes":{"content_report":{"categories":{"adult":"Взрослый или насилия","ai_generated":"Контент, сгенерированный ИИ","copyright":"Нарушение авторского права"}}},"errors":{"full_messages":{"format":"%{attribute} %{message}"},"messages":{"accepted":"нужно подтвердить","blank":"не может быть пустым","confirmation":"не совпадает со значением поля %{attribute}","embedded_script":"Этот файл не поддерживается и не может быть загружен. Попробуйте другой файл.","empty":"не может быть пустым","equal_to":"может иметь лишь значение, равное %{count}","even":"может иметь лишь четное значение","exclusion":"имеет зарезервированное значение","greater_than":"может иметь лишь значение больше %{count}","greater_than_or_equal_to":"может иметь лишь значение не меньше %{count}","inclusion":"имеет непредусмотренное значение","invali
16518d":"имеет неверное значение","less_than":"может иметь лишь значение меньше %{count}","less_than_or_equal_to":"может иметь лишь значение не больше %{count}","not_a_number":"не является числом","odd":"может иметь лишь нечетное значение","record_invalid":"Возникли ошибки: %{errors}","restrict_dependent_destroy":{"has_many":"Невозможно удалить запись, так как существуют зависимости: %{record}","has_one":"Невозможно удалить запись, так как существует зависимость: %{record}"},"taken":"уже существует","too_long":{"few":"слишком большой длины (не может быть больше чем %{count} символа)","many":"слишком большой длины (не может быть больше чем %{count} символов)","one":"слишком большой длины (не может быть больше чем %{count} символ)","other":"слишком большой длины (не может быть больше чем %{count} символа)"},"too_short":{"few":"недостаточной длины (не может быть меньше %{count} символов)","many":"недостаточной длины (не может быть меньше %{count} символов)","one":"недостаточной длины (не может быть меньше %{count} символа)","other":"недостаточной длины (не может быть меньше %{count} символа)"},"wrong_length":{"few":"неверной длины (может быть длиной ровно %{count} символа)","many":"неверной длины (может быть длиной ровно %{count} символов)","one":"неверной длины (может быть длиной ровно %{count} символ)","other":"неверной длины (может быть длиной ровно %{count} символа)"}},"models":null,"template":{"body":"Проблемы возникли со следующими полями:","header":{"few":"%{model}: сохранение не удалось из-за %{count} ошибок","many":"%{model}: сохранение не удалось из-за %{count} ошибок","one":"%{model}: сохранение не удалось из-за %{count} ошибки","other":"%{model}: сохранение не удалось из-за %{count} ошибки"}}}},"api":{"profile":{"actions":{"contact":"Связаться","follow":"Подисаться","follow_tooltip":"Start following this designer to get updates about his content and easily filter favorite designers in Search results","hide":"Скрыть","hire_me":"Нанять меня","invite_client":"Начать проект","mute_tooltip":"Hide this designer’s models in the Search results","take_over":"Take over","unfollow":"Отписаться","unhide":"Показать","view_all_models":"View all Designer's Models"},"private_project_review":"Private Project"}},"browser":{"3d_models":"3д модели","3d_print":"3д-печать","active_filters":{"polygons":"%{range} полигонов"},"adult_content":"Показать контент для взрослых","animated":"Анимированные","breadcrumb":{"browse_by_type":"Просмотр по типу","collection":"3D-паки","free":"свободно","free_models":"Бесплатные 3д модели","free_print_models":"Free 3д модели печати","low_poly":"низкополигональная","low_poly_models":"VR / AR / низкополигональная 3д-модели","low_poly_print_models":"Низкополигональные файлы 3д-печати","model_3d":"3д модели","model_3dp":"3д модель печати","models":"3д модели","models_3d":"3д модели","models_3dp":"Модели 3д печати","models_variant_0":"3D-модели","models_variant_1":"3D-ассеты","models_variant_2":"VR-модели","models_variant_3":"CG-модели","models_variant_4":"МагаÐ
16518·Ð¸Ð½ 3D-моделей","models_variant_5":"3D-объекты","print_models":"Файлы 3д-печати","print_models_variant_0":"Модели для 3D-печати","print_models_variant_1":"Модели для 3D-принтера","print_models_variant_10":"3D-модели для печати","print_models_variant_11":"3D-модели для печати","print_models_variant_12":"3D-файлы для печати","print_models_variant_13":"Модели для 3D-принтера","print_models_variant_14":"Модели для 3D-печати","print_models_variant_15":"Модели 3D-печати","print_models_variant_16":"Печатаемые 3D-файлы","print_models_variant_2":"3D-модели для печати","print_models_variant_3":"Модели для 3D-печати","print_models_variant_4":"Файлы для 3D-печати","print_models_variant_5":"Файлы для 3D-печати","print_models_variant_6":"Файлы для 3D-принтера","print_models_variant_7":"Печатаемые 3D-модели","print_models_variant_8":"3D-модели для печати","print_models_variant_9":"Распечатанные 3D-модели"},"buyer_engagement_program":"Скидки","categories":"Категории","category":{"meta":{"3d_models":"%{count} %{category} 3д модели, доступные для скачивания в любом формате файла, включая FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Скачать %{count} %{category} 3д модели печати, доступны для скачивания в STL, OBJ и другие форматы файлов, готовых к печати с FDM, SLS и других 3д принтеров.%{page}","animated_3d_models":"%{count} %{category} анимированные 3д модели, доступные для скачивания в любом формате файлов, в том числе FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Скачать %{count} %{category} бесплатные 3д модели, доступные в MAX, OBJ, FBX, 3DS, форматы файлов C4D, готовые к VR / AR, анимации, игр и других 3д-проектов.%{page}","free_3d_print_models":"Скачать %{count} %{category} бесплатные 3д модели печати готовые для FDM, ОАС, SLS и других 3д принтеры, доступные форматы STL, OBJ, 3дM и многое другое.%{page}","lowpoly_3d_models":"Скачать %{count} VR / AR / низкополигональная %{category} 3д моделей, готовых к виртуальной реальности, игр и других приложений реального времени, имеющихся в FBX, OBJ, MAX, 3DS, форматы файлов C4D.%{page}","rigged_3d_models":"%{count} %{category} сфальсифицированы 3д модели, доступные для скачивания в любом формате файлов, в том числе FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"collection":"3D-пак","discounts":"Скидки","ext":{"meta":{"3d_models":"Скачайте %{rounded_count} 3D моделей в формате %{format_upper}: %{format_lower} модели скачать бесплатно или купить.","3d_print_models":"Скачать %{count} %{ext} файлов - %{software_name} 3д модели печати готовые для VR / AR, 3д дизайн, анимация, игры и в режиме реального времени приложений%{page}."}},"filter":"Фильтр","filter_button":"Применить фильтры","filters":{"adult_content_only":"Только контент для взрослых","cgt_standard_only":"CGT Standard","collections_only":"Только наборы","content_filters":"Фильтры контента","designer":"Дизайнер","exclude_3d_print":"Исключить 3D-печать","exclude_adult_content":"Исключить контент для взрослых","exclude_ai_generated":"Исключить контент, созданный ИИ","exclude_editorial_license":"Исключить редакционные лицензии","exclude_free":"Исключить бесплатные","file_format":"Форматы файлов","following":"Подписки","free":"Бесплатно","has_marmoset":"С 3D-вьювером","has_video":"С видео","include_ai_generated":"Включать контент, созданный ИИ","label":"Фильтры","poly_count":"Количество полигонов","print_ready":"3д-печати","stl_files":"Файлы STL","subscription":"Подписка","type":"Тип"},"formats":"Форматы","free":"Бесплатно","free_models":"Показать только бесплатные модели","free_to_premium":{"main":"3D-модели %{category}","postfix":"в премиум-листинге.","prefix":"Вам нужно что-то особенное? Вы можете найти лучшее качество"},"gallery":{"preview":"превью"},"game_ready":"Готовые для игровых движков","high_poly":"Высокополигональные","items_page_title":"3д модели","license":{"exclude_branded":"Исключить брендированные","exclude_editorial":"Исключить редакционные","prompt":"Лицензия","tooltip_more":"Читать далее","tooltip_text":"Не показывать модели, которые могут быть использованы только в некоммерческих, образовательных целях.","tooltip_title":"Исключить редакционные:"},"low-poly":"Низкополигональные","low_poly":"Низкополигональные","meta":{"3d_models":"Скачайте или купите %{rounded_count} профессиональных 3D моделей. Бесплатные и платные 3д модели для игр, кино, VFX, AR/VR и 3D печати. FBX, OBJ, BLEND, GLB.","3d_print_models":"Скачайте %{rounded_count} моделей для 3д принтера. Бесплатные и платные STL модели для 3D печати: фигурки, миниатюры, декор, запчасти.","animated_3d_models":"Скачайте %{rounded_count} 3D моделей с анимацией. Готовые анимированные персонажи и объекты в FBX, BLEND, GLB.","free_3d_models":"Скачайте %{rounded_count} бесплатных 3D моделей. 3д модели бесплатно в форматах FBX, OBJ, BLEND, GLB для ваших проектов.","free_3d_print_models":"%{rounded_count} бесплатных 3д моделей для печати. Скачайте STL файлы для 3D принтера бесплатно: фигурки, игрушки, полезные вещи.","lowpoly_3d_models":"Скачайте %{rounded_count} низкополигональных (low poly) 3D моделей для игр, AR/VR и рендера в реальном времени.","metaverse":"Введите Metaverse с нашей коллекцией высоких и низкополи-3D-моделей, сфальсифицированных, анимированных и доступных в любом формате файлов.","omniverse":"Создайте свой виртуальный мир с помощью наших 3D-моделей, готовых к всему,. Выберите из анимированных, сфальсифицированных и моделей PBR с высоким и низким Ð
16518ºÐ¾Ð»Ð¸Ñ‡ÐµÑÑ‚вом многоугольника.","page_prefix":"Page %{page_number} с","pbr_3d_models":"Скачайте %{rounded_count} 3D моделей с PBR текстурами для Unity, Unreal и движков реального времени.","rigged_3d_models":"Скачайте %{rounded_count} 3D моделей с ригом (скелетом), готовых к анимации в Blender, Maya, 3ds Max, Unity и Unreal.","snap-ar":"С нашими 3D -моделями вы можете присоединиться к миллиону Snapchatters, используя Snap AR, чтобы создать веселый опыт дополненной реальности.","spark-ar":"Осветите свой Facebook и Instagram с различными моделями Spark AR 3D, которые сфальсифицированы, анимированы и готовы к использованию.","stream_3d_print_models":"Просмотр с помощью различных категорий потока 3д печать моделей обнаружить некоторые из самых необычных 3д печатных проектов от профессиональных 3д создателей.","textures":"% {count} Текстуры 3д активы и материалы, доступные для скачивания в различных форматах, включая .tga .sbsar .jpeg .png .tiff и другие. %{page}","tiktok":"Сделайте ваши видео -эффекты более впечатляющими с моделями Tiktok 3D. Наша библиотека может удовлетворить все ваши творческие потребности и постоянно обновляется.","unity":"Найдите 3D -модели для вашего проекта Unity в любом формате файлов и любого количества поли. Все, что вам нужно для создания уникальной виртуальной среды.","unreal":"Создайте впечатляющие визуализации с нереальными двигателями, совместимыми с 3D -моделями в любом формате файла. Наша библиотека имеет низкие и высокополитические модели из каждой категории."},"optimized":"Низкополигональные/PBR","page_title":{"animated_3d_models":"Анимированные 3D модели – бесплатно и премиум","format_page":"%{format_upper} 3D модели – скачать %{format_lower} файлы бесплатно","free_3d_models":"Бесплатные 3D модели – скачать","free_3d_print_models":"Бесплатные модели для 3D принтера – скачать STL","lowpoly_3d_models":"Low Poly 3D модели – бесплатно и премиум","pbr_3d_models":"PBR 3D модели – бесплатно и премиум","rigged_3d_models":"Rigged 3D модели (с ригом) – бесплатно и премиум","software_page":"3D модели для %{software} – скачать %{ext_file}","tag":{"3d_merged_premium":"%{tag_cap} – 3D модели скачать бесплатно и премиум","3d_merged_premium_print":"%{tag_cap} для 3D принтера – STL модели для печати","3d_models":"%{tag_cap} – 3D модели скачать бесплатно и премиум","3d_print_models":"%{tag_cap} для 3D принтера – STL модели для печати"}},"pbr":"PBR","per_page":"Количество на странице","polygons_options":{"custom":"Пользовательский диапазон","gt_250k":"250к+","lt_5k":"До 5к","prompt":"Количество полигонов","range_0_5000":"До 5К","range_100k_250k":"100к-250к","range_10k_50k":"10к-50к","range_50k_100k":"50к-100к","range_5k_10k":"5к-10к"},"price":"Цена: $%{from}-%{to}","price_range":"Цена","print_ready":"Готовые к 3д-печати","printable":"Модели для 3D-печати","reset_button":"Сбросить фильтры","rigged":"Риги","rounded_count":{"delimiter":" ","exact":"%{n}","million":"%{n}
16518 млн","separator":",","thousand":"%{n}+"},"sale_off":"Со скидкой","search_placeholder":"Ключевые слова для поиска","show_all_formats":"Показать все форматы","simulation_ready":"Готово для симуляции","software":{"meta":{"3d_models":"Скачайте %{rounded_count} 3D моделей для %{software}. Бесплатные и платные %{ext_file} файлы, готовые к импорту."}},"sort_by":"Сортировать по","sort_by_options":{"best_match":"Лучшее совпадение","highest_price":"Более высокая цена","lowest_price":"Более низкая цена","newest":"Самые новые","oldest":"Самые старые","sales":"Лучшие продажи","trending":"Тренды"},"subcategory":{"meta":{"3d_models":"%{count} %{category} 3д модели, доступные для скачивания в любом формате файла, включая FBX, OBJ, MAX, 3DS, C4D.%{page}","3d_print_models":"Скачать %{count} %{category} 3д модели печати, доступны для скачивания в STL, OBJ и другие форматы файлов, готовых к печати с FDM, SLS и других 3д принтеров.%{page}","animated_3d_models":"%{count} %{category} анимированные 3д модели, доступные для скачивания в любом формате файлов, в том числе FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Скачать %{count} %{category} бесплатные 3д модели, доступные в MAX, OBJ, FBX, 3DS, форматы файлов C4D, готовые к VR / AR, анимации, игр и других 3д-проектов.%{page}","free_3d_print_models":"Скачать %{count} %{category} бесплатные 3д модели печати готовые для FDM, ОАС, SLS и других 3д принтеры, доступные форматы STL, OBJ, 3дM и многое другое.%{page}","lowpoly_3d_models":"Скачать VR / AR / низкополигональная %{tag} 3д моделей, готовых к виртуальной реальности, игр и других приложений реального времени, доступны в FBX, OBJ, MAX, 3DS, форматы файлов C4D.%{page}","rigged_3d_models":"%{count} %{category} сфальсифицированы 3д модели, доступные для скачивания в любом формате файлов, в том числе FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"subtitle":{"aircraft":"%{count} самолета 3д модели доступны для скачивания на нашем рынке. CG %{airplanes_link} впишется в симуляции, визуализации, рекламы и видео. %{drone_link} органы и части будут радовать поклонников крошечных летательных аппаратов. А %{rigged_link} модель готова к ввозу в игровые движки и использовать для анимации или проектов VR / AR. Летающие игры, симуляторы и видео как обычного CG и низкий поли типов могут быть заполнены с 3д-моделями в большинстве популярных форматов, включая FBX, OBJ, MAX, 3DS, C4D.","animals":"%{count} из CG 3д животных -%{cats_link}, %{horses_link}, %{bears_link}, %{rhinos_link}, %{eagles_link}, %{lions_link}, вымышленный %{dragons_link} и больше - можно посмотреть здесь для загрузки. Коллекция 3д фауны будет удовлетворять потребности рекламы или статических сцен. Мы также имеем %{rigged_link} модели для игр, анимации, видео-продукции и VR / AR проектов. Используйте фильтры, чтобы найти сфальсифицированы, анимированный, низкополигональные или бесплатные 3д модели, доступной в большинстве отраслевых общепринятых форматов файлов, в том числе FBX, OBJ, MAX, 3DS, C4D, и других.","animated":"Скачайте %{rounded_count} анимированных 3D моделей с готовыми движениями — персонажи, животные и объекты для игр, кино и реального времени.","architectural":"%{count} архитектурных 3д моделей , доступных для загрузки содержит элементы и детали архитектуры, строительства и инженерного проектирования. Для того, чтобы купить или скачать бесплатно 3д модели, просматривать категории выше. Вы можете использовать модели для анимации или игр - некоторые из них сфальсифицированы и низкополигональных служить VR / AR потребности проекта. 3д модели доступны в форматах, таких как FBX, OBJ, MAX, 3DS, C4D.","car":"%{count} автомобиль 3д модели доступны для скачивания. Низкополигональных версии, которые будут привлекательны для игровых дизайнеров и разработчиков VR / AR приложений, другие - кинематографисты, аниматоров и рекламодателей. От классических и антикварных автомобилей на гоночные и концепцию из них видели в фильмах, есть большое разнообразие моделей 3д авто в практически любом формате (FBX, OBJ, MAX, 3DS, C4D и т.д.), вам могут понадобиться. Используйте анимированные и бесплатные 3д модели для прототипа и сократить время разработки.","character":"%{count} символов 3д модели доступны для загрузки, поэтому просматривают эту категорию вы найдете 3д модели %{human_link}, люди, мифические фигуры, научно-фантастические %{robots_link}, одежду и другие вещи, связанные с символами. С %{heart_link} и %{hand_link} к детальному реалистической %{male_link} или %{female_link}. Ряд моделей сфальсифицированы и может быть легко добавлен к вашей игре, VR / проект AR, видео или анимация. Оба высоких и низкое поли модели поли могут быть найдены в различных форматах, в том числе FBX, OBJ, MAX, 3DS, C4D.","custom"
16518:"Здесь вы можете купить или скачать бесплатно  %{tag} 3д-модели. %{tag_heading_subtitle}","electronics":"%{count} электроника 3д модели, доступные для скачивания. Это категория для архитекторов, декораторов, дизайнеров уровней, руководителей сцены и других объявлений. 3д модели %{smartphones_link}, %{laptops_link}, %{cameras_link}, %{tvs_link} и больше поможет точно захватить одержимость современной жизни с устройствами и технологиями. Есть 3д модели доступны в форматах файлов, включая FBX, OBJ, MAX, 3DS, C4D. Скачать бесплатно или купить премиум-модель и использовать для анимации, игр и проектов VR / AR.","exterior":"%{count} архитектурных внешних 3д-модели, доступных для загрузки диапазона от фасадов, городских зданий, ландшафтных объектов, жилых домов до промышленных сооружений, инфраструктуры зданий и%моделей полномасштабного %{city_link}. Для того, чтобы купить или скачать бесплатно 3д модели, просматривать категории выше. Используйте фильтры, чтобы найти сфальсифицированы, анимированные, низкополигональная, бесплатно 3д модели или сортировать по формату необходимо, в том числе и FBX, OBJ, MAX, 3DS, C4D. Некоторые из активов низкополигональные и могут помочь вам завершить окружение в вашей игре или приложению VR / AR.","food":"%{count} еда 3д модели доступны для download..Food хорошо служит для создания реалистичных сцен домашней жизни. 3д фрукты, овощи, напитки и питание все здесь, чтобы полные рекламы для ресторанов, AR-приложения для здорового питания, и визуализации архитекторы и дизайнеры интерьеров работают. Если вы ищете 3д %{pizza_link} и %{ketchup_link} или 3д модель обручального %{cake_link}, они здесь. Ряд низкополигональных версий доступны для тех, в игре, развитие VR / AR. 3д модели можно найти в FBX, OBJ, MAX, 3DS, форматы C4D.","formats":"Скачайте %{rounded_count} 3D моделей в формате %{format_upper} — бесплатно и премиум. Все файлы на этой странице доступны в нативном %{format_upper}, без конвертации.","free_general":"Скачайте %{rounded_count} бесплатных 3D моделей для личных и коммерческих проектов. 3д модели бесплатно и без скрытых платежей — в FBX, OBJ, BLEND и GLB.","free_no_tag":"%{model_count} бесплатно 3д  моделей готова к 3д печати. Скачать бесплатно 3д модели печати, доступных форматов файлов, включая STL, OBJ, 3дM, 3DS, MAX. 3д модели готовы к использованию с FDM, ОАС, SLS и других типов 3д принтеров.","free_printable":"Скачайте %{rounded_count} бесплатных моделей для 3D принтера: 3д модели для печати бесплатно, проверенные сообществом — фигурки, игрушки, декор и практичные вещи.","free_tag":"бесплатно 3д %{tag} моделей, доступных для загрузки. Доступные в различных форматах, включая MAX, OBJ, FBX, 3DS, STL, C4D, Blend, MA, MB. Найти профессиональную %{tag_link} для любых проектов 3д-проектирования как виртуальной реальности (VR), дополненной реальности (AR), игры, 3д визуализации или анимации.","furniture":"%{count} мебель 3д модели доступны для скачивания включают столы, стулья, диваны, кровати и другая мебель в различных текстур (дерево, металл и т.д.) и стили (%{retro_link}, %{modern_link} и т.д. ). Эта категория предназначена для всех, кто занимается с архитектурой и дизайном интерьера, а также рекламодателей и аниматоров. В режиме реального времени активы могут помочь игры и приложения VR / AR разработчиков комплексных проектов. Используйте фильтры, чтобы найти сфальсифицированы, анимированные, низкополигональные или бесплатные 3д моделей . Доступен в любом формате файла, включая FBX, OBJ, MAX, 3DS, C4D.","general":"Скачайте или купите %{rounded_count} профессиональных 3D моделей: 3д модели и ассеты для игр, кино, VFX, AR/VR и 3D печати. Бесплатно и премиум, в форматах FBX, OBJ, BLEND, GLB и ещё 100+.","high_poly":"Купить или скачать Анимированные Профессиональные 3д модели готовы к использованию в проектах CG, кино- и видеопроизводства, анимации, визуализации и другие. Активы доступны для загрузки во многих отраслевых принимаются форматы, включая MAX, OBJ, FBX, 3DS, UE4 и другие. Если вы находитесь в поисках высокого качества поли 3д активов, мы имеем ведущий цифровое искусство библиотеки для всех ваших потребностей.\n","household":"%{count} бытовых 3д модель, доступная для загрузки может быть использована, чтобы дать подробную информацию в номера. Вы можете поставить набор посуды на столе, есть перец и соль контейнеры для полного интерьера кухни, или поставить некоторые %{lights_link} и %{flowers_link} возле кровати, чтобы создать ощущение роскоши. Если вы дизайнер интерьера, аниматор, VR / AR приложение или создатель игры, эта категория для вас. Используйте фильтры, чтобы найти сфальсифицированы, анимированные, низкополигональные или бесплатные 3д моделей . Доступен в любом формате файла, включая FBX, OBJ, MAX, 3DS, C4D.","industrial":"%{count} промышленные модели 3д доступны для скачивания - строительных материалов, %{engineering_link} инструментов, машин и %{containers_link} - обеспечит как детали и интерактивные, сфальсифицированы элементы для создания живой среды. Это идеальная категория для моделирования / VR / AR или разработчиков, создающим %{construction_link} уровня сайта в игре. 3д модели будет служить профессионалам строят точные визуализации, работающие с различным программным обеспечением, как модели в таких форматах, Ð
16518ºÐ°Ðº FBX, OBJ, MAX, 3DS, C4D доступны.","interior":"%{count} интерьер 3д модели доступны для загрузки и включают в себя элементы и детали, используемые в архитектуре, строительстве и машиностроении, дизайн интерьера. Для того, чтобы купить премиум или скачать бесплатно 3д модели, просматривать категории, перечисленные выше. Вы можете использовать модели для анимации или игр - некоторые из них сфальсифицированы, анимационные и низкополигональные. Доступные в различных форматах, в том числе FBX, OBJ, MAX, 3DS, C4D.","low_poly":"Скачайте %{rounded_count} low poly 3D моделей — лёгкие низкополигональные ассеты для игр, AR/VR и мобильных проектов.","military":"%{count} военные 3д модель оружия, оружие, %{swords_link} и артиллерии как современных и исторических, используемая вооруженными силами и самооборона доступен для скачивания. Вы можете использовать модели для игр или контента VR / AR - некоторые из них являются многоугольниками Оптимизированные и готовые быть добавлены в игровой движок. Оснащенные и анимированные модели могут облегчить вашу работу дальше. Вы наверняка найдете соответствующие активы, поскольку есть много 3д моделей, доступных в любом формате файл, вам могут понадобиться (FBX, OBJ, MAX, 3DS, C4D и т.д.).","not_custom":"Бесплатные и платные 3D модели в категории %{tag_low} - реалистичные и стилизованные, в FBX, OBJ, BLEND и GLB.","optimized":"Модели, которые имеют меньше многоугольников лучше всего использовать в реальных приложениях, которые требуют времени быстрой обработки, как виртуальная реальность (VR), дополненная реальность (AR), смешанная реальность (MR), перекрестная реальность (XR) и игры, особенно игры для мобильных телефонов. Выберите из нашей коллекции сфальсифицированы и анимированные модели легко использовать их в приложениях реального времени. Получить 3д активов для сред, подобрать реквизит, объекты или купить полные 3д модели коллекции, пакеты и пакеты со всеми вашей игрой может понадобиться. Сохранить время разработки и затраты, делая опыт прототипов или использование 3д моделей  как заполнители в вашем проекте. Физически рендеринг, или PBR, обозначает модель, которые оказываемая точно и последовательно в любых условиях освещения благодаря текстурированию рабочих процессов, используемой в процессе создания.\n","pbr":"Скачайте %{rounded_count} 3D моделей с PBR текстурами (физически корректный рендеринг) — реалистичные материалы для Unity, Unreal и других движков реального времени.","plant":"%{count} завод 3д модель, доступная для скачивания содержит флор организмы, которые растут в домашних растения и в природе, такие как цветы, %{trees_link}, травы, лианы, зелень, травы, многолетники, саженцы, побеги, кустарники, промахи, капуста, виноград, сорняки. Для того, чтобы купить премиум или скачать бесплатно 3д модели, просматривать категории выше, чтобы найти наиболее подходящий вариант среди доступных моделей. Используйте фильтры, чтобы найти сфальсифицированы, анимированный, низкополигональные (для использования в играх и VR / AR-приложении) модели, или отфильтровать необходимые форматы (модели FBX, OBJ, MAX, 3DS, C4D и более доступны).","printable":"Скачайте %{rounded_count} моделей для 3D принтера: 3д модели для печати в STL, проверенные для FDM и фотополимерных принтеров. Фигурки, миниатюры, декор, полезные вещи и запчасти.","printable_merged_tag":"Скачайте %{rounded_count} моделей «%{tag_low}» для 3D печати: STL файлы бесплатно и премиум, для FDM и фотополимерных принтеров.","printable_tag":"Скачайте %{rounded_count} моделей «%{tag_low}» для 3D печати: STL файлы бесплатно и премиум, для FDM и фотополимерных принтеров.","rigged":"Скачайте %{rounded_count} rigged 3D моделей — персонажи, животные и существа со скелетом (ригом), готовые к анимации в Blender, Maya, 3ds Max, Unity и Unreal.","scanned":"%{count} отсканированные 3д модели доступны для приобретения или бесплатного скачивания. 3д модели Отсканированные готовы к анимации, игр и проектов VR / AR. Вы можете найти 3д модели объектов, встречающихся в природе, такие как камни и природные поверхности, люди и другие объекты реальной жизни здесь ... и он не может получить какую-либо более реалистичен, чем это. Используйте фильтры, чтобы найти сфальсифицированы, анимированные, низкополигональные или бесплатные 3д моделей . Доступен в любом формате файла, включая FBX, OBJ, MAX, 3DS, C4D.","science":"%{count} наука 3д модели доступны для скачивания в этой категории диапазоне от медицины, биологии, физики, химии и других областях объектов реального мира, связанных с наукой и техникой: лабораторное оборудование, микроскопических тел и субатомном мире. Нет необходимости искать дальше, %{cell_link}, %{skull_link}
16518 и %{skeleton_link} 3д модели можно найти здесь! Для того, чтобы купить или же скачать бесплатный 3д активов, просматривать категории выше. Вы можете использовать сфальсифицированы модели для анимации или низкополигональными из них для игр и проектов VR / AR. Оба свободных и премиум 3д-модели можно найти в ряде форматов файлов, включая FBX, OBJ, MAX, 3DS, C4D.","scripts_plugins":"%{count} 3д скрипты / плагины доступны для загрузки. 3д скрипты / плагины могут быть добавлены к различному программному обеспечению и использовать более эффективно сделать вашу работу трубопровод. Фильтр по форматам, чтобы найти инструменты Фит предпочитаемого программного обеспечения.\n","software_page":"Скачайте %{rounded_count} 3D моделей для %{software} — бесплатно и премиум. Нативные %{ext_file} файлы открываются сразу; остальные форматы легко импортируются.","space":"%{count} пространство 3д модели доступны для скачивания на CGTrader. Пространство категория полна низкополигонального, игровые готовые модели научно-фантастическом %{spaceships_link}. Есть также %{asteroids_link}, планет (включая Луну и Марс, для имитации колонизации) и реалистическое пространство %{shuttles_link} для более научно наклонена. Модели, безусловно, могут быть использованы в VR космический симулятор игры, AR-приложений и анимации, чтобы продемонстрировать миру за пределами планеты %{earth_link}. Фильтры здесь, чтобы помочь вам найти сфальсифицировано, анимированные, низкополигональные активы или модель в определенных форматах, в том числе FBX, OBJ, MAX, 3DS, C4D. ","sports":"%{count} спорт 3д модели доступно вместе оборудование гнезда, которое используется для активного отдыха, развлечений, физических упражнений, отдыха, приятного времяпрепровождения, забавы, а также игры, детские игрушки и полный спектр музыкальных инструментов. Для того, чтобы купить или скачать бесплатно 3д модели в любом формате файлов, необходимых, в том числе FBX, OBJ, MAX, 3DS, C4D, просматривать категории выше. Вы можете использовать модели для анимации, игр, проектов VR / AR - много из них сфальсифицированы, низкополигональных или анимированных уже.","test":{"airplane":"Любители авиации, слушайте, это никогда не было так легко получить идеальный 3д напечатанную модель самолета для настольной игры или коллекции модели. Поиск по моделей %{model_count} 3д печати самолет и найти модель, вам просто необходимо иметь на этой полке. модели реального самолета Discover или выдуманные из них. Поиск новейших конструкций или что-то, что рассчитывает обратно от времени братьев Райт. Это также прекрасный подарок для любого ребенка, который мечтает стать пилотом, когда он вырастет. Покупка и загрузка модели печати 3д, распечатать его самостоятельно или представить модель одного из наших поставщиков услуг печати 3д, чтобы оÐ
16518½ напечатан и отправлен к вам в кратчайшие сроки!\n","anatomy":"%{model_count} модель 3д анатомия, доступная для скачивания на нашем рынке. Исследуйте анатомии человека с высоким - поли кости, мышцы, модели органов. Идеально подходит для любого образовательного или научного проекта. Используйте для печати 3д модели для улучшения опыта образования. Создавайте потрясающие анимации и видео-игры с нашим\n обширная библиотека фальсифицированных моделей. Используйте нашу усовершенствованную систему фильтрации легко найти низкополигональный, анимированный, PBR,\n сфальсифицированные модели или что-нибудь, что соответствует вашим потребностям. Выберите из большой сделки форматов файлов, включая\n самые популярные из них, как FBX, OBJ, MAX, 3DS, C4D.\n","castle":"Ваш собственный удивительный 3д печататься замок на расстоянии одного клика! Поиск различных моделей 3д замок готов к 3д печати. Создание уникальной фантазии крепости для вашего D \u0026 D сессии или любой другой настольной игры средневекового рпга. Эти средневековые крепости удивительный подарок для любой настольной игры энтузиастов или прекрасным дополнением к вашей собственной коллекции. Найти идеальный 3д модели для рисования любителей или единственный в своем роде игрушка для детей. Покупка и загрузить выбранный дизайн 3д замка, распечатайте его с вашим собственным принтером или представить модель одного из наших поставщиков услуг печати 3д, чтобы он напечатан и отправлен к вам.\n","dice":"Удача не будет работать с 3д печатных кости! Если вы не хотите аутентичные игры в кости для вашей настольной игры ночью, смотреть не больше. Здесь вы можете выбрать между %{model_count} свободных 3д кости моделей, готовых для 3д печати. Когда игра закончена, использовать эти красивые модели в качестве аксессуара. Скачать бесплатно 3д для печати модели кости, форматы файлов доступны в том числе STL, OBJ, 3дM, 3DS, MAX. Наши 3д модели готовы к использованию с FDM, ОАС, SLS и других типов 3д принтеров. Сделайте вашу игру более интересной и получить 3д печатных кости!\n","drone":"3д печать и построить свой собственный беспилотный или найти части для замены. 3д печатных трутни легко собрать и ремонт после аварийной посадки. Откройте для себя модель с идеальной монтажной позицией камеры или запросом минимальных изменений. Идеально подходит для активного отдыха забавными, аэрофотосъемки, видеографии или отображения. Найти и приобрести уникальный дизайн 3д беспилотную модель, которая подходит для всех ваших потребностей. Распечатайте его самостоятельно или представить модель одного из наших поставщиков услуг печати 3д, чтобы оÐ
16518½ напечатан и отправлен к вам в кратчайшие сроки! Получите ваш собственный 3д печатного дрон и бродят по широкому голубому небу!\n","pokemon":"Пикачу, я выбираю тебя! Выберите из нагрузок карманных монстров, готовых для 3д печати. Великий расширить свой 3д печатное покемон коллекции. Поиск и наткнуться на самых лучших, самых подробных покемон конструкций для вашей следующей настольной игры с друзьями. Покупка и загрузить 3д покемон, распечатайте его с собственным 3д принтером, или представить модель одного из наших поставщиков услуг печати 3д, чтобы он напечатан и отправлен к вам. В конце концов, это одна из самых крутых вещей для 3д печати!\n","ring":"3д напечатаны ювелирные изделия новый черный в промышленности, как и 3д печатные кольца! Поиск огромного количества %{model_count} свободных 3д моделей для печати кольца. Просмотр с помощью уникальных дизайнов и найти что-то сделал для вас. Не могу найти что-нибудь для себя в магазине? Или, может быть, вы хотите аксессуар точно, как один ваш любимый персонаж фильма есть? Выберите из большой сделки форматов файлов, включая STL, OBJ, 3дM, 3DS, MAX. 3д модели готовы к использованию с FDM, ОАС, SLS и других типов 3д принтеров, поэтому просматривать наш выбор и попробовать на новом любимом кольце.\n","shoes":"Большие бренды на борту с 3д печатных обувь и теперь вы можете поделиться опытом, а также. Обзор и найти бесплатную печатные конструкции обуви - от кроссовок до модных каблуков есть много готовых к печати моделей просто ждем вас, чтобы попробовать их. Сделайте итоговый совершенным путем выбора материалов, регулируя мягкость и размер. Выберите один из 146 бесплатных 3д моделей обуви и загрузить их в доступных форматах файлов, включая STL, OBJ, 3дM, 3DS, MAX. Конструкции готовы к использованию с FDM, ОАС, SLS и других типов 3д принтеров. Получите ваши 3д печатной обувь сегодня!\n","textures":"Поиск по огромному количеству %{model_count} CG текстуры доступны для загрузки, включая красиво сделанные материалы. Просмотрите каtagории и найти бесшовную ткань, дерево, металл, пластик, кирпич текстуру, чтобы помочь закончить ваш проект быстрее. Используйте расширенный поиск, чтобы найти сфальсифицированные и анимированные текстуры для игры или видео создания. Наши материалы доступны для скачивания в различных форматах, включая TGA, SBSAR, JPG, PNG, TIFF и другие, так что не теряйте времени и найти готовые к использованию текстур CG практически для любого проекта можно себе представить.\n"},"various":"%{count} различные 3д-модели, которые не легко найти свое место в других категориях доступны для загрузки. %{rocks_link}, %{clouds_link}, %{logos_link}, %{cigarette_link} и другие не классифицированы 3д модели, которые вы, возможно, потребуется, чтобы завершить игру, анимацию или проект VR / AR здесь. Используйте фильтры, чтобы найти сфальсифицированы, анимированный, низкополигональные или бесплатно 3д модели, или перебирать доступными форматами, такие как FBX, OBJ, MAX, 3DS, C4D.","vehicle":"%{count} автомобиль 3д модели доступны для скачивания включают транспорт, %{ambulances_link}, автобусы, %{bikes_link} и Moto - это категория для людей, которые хотят, чтобы их городские улицы были заполнены с транспортными средствами, явл
16518яется ли реалистичной или научно-фантастической. Те, что делает имитационные игры или VR / AR-приложений, объявлений в производстве видео или даже разработки плакатов, скорее всего, найти наиболее популярные форматы файлов, такие как FBX, OBJ, MAX, 3DS, C4D годным к употреблению. Другие земли транспортные средства, такие как поезд или %{tractor_link} 3д модель также можно найти в этой обширной коллекции. Используйте фильтры, чтобы найти сфальсифицировано, анимированные, низкополигональную или бесплатный 3д модели быстрее.","watercraft":"%{count} ВОДНЫЙ 3д модели доступны для скачивания включают %{boats_link}, %{yachts_link}, %{ships_link} и другие водные транспортные средства собираются всех видов. Оснащенное 3д модель низкополигональной преуспеет в видеоиграх, VR / AR проектах, а также анимации, в то время как модели CG здесь для сцен и других применений, где визуальная точность важнее Polycount. Модели доступны в любом формате файла, включая, но не ограничиваясь FBX, OBJ, MAX, 3DS, C4D."},"tag":{"meta":{"3d_merged_premium":"Скачайте %{rounded_count} 3D моделей в категории %{tag_low} бесплатно и премиум. В форматах FBX, OBJ, BLEND, GLB — для игр, кино, AR/VR и 3D печати.","3d_merged_premium_print":"Скачайте %{rounded_count} STL моделей «%{tag_low}» для 3D принтера, бесплатно и премиум — проверено для FDM и фотополимерной печати.","3d_models":"Скачайте %{rounded_count} 3D моделей в категории %{tag_low} бесплатно и премиум. В форматах FBX, OBJ, BLEND, GLB — для игр, кино, AR/VR и 3D печати.","3d_print_models":"Скачайте %{rounded_count} STL моделей «%{tag_low}» для 3D принтера, бесплатно и премиум — проверено для FDM и фотополимерной печати.","animated_3d_models":"%{count} %{tag} анимированные 3д модели, доступные для скачивания в любом формате файлов, в том числе FBX, OBJ, MAX, 3DS, C4D.%{page}","free_3d_models":"Скачать %{count} %{tag} бесплатно 3д модели, доступные в MAX, OBJ, FBX, 3DS, форматы файлов C4D, готовые к VR / AR, анимации, игр и других 3д-проектов.%{page}","free_3d_print_models":"Скачать %{count} %{tag} бесплатно 3д модели печати готовые для FDM, ОАС, SLS и других 3д принтеры, доступные форматы STL, OBJ, 3дM и многое другое.%{page}","lowpoly_3d_models":"Скачать %{count} VR / AR / низкополигональная %{tag} 3д моделей, готовых к виртуальной реальности, игр и других приложений реального времени, доступны в FBX, OBJ, MAX, 3DS, форматы файлов C4D.%{page}","rigged_3d_models":"%{count} %{tag} сфальсифицированы 3д модели, доступные для скачивания в любом формате файлов, в том числе FBX, OBJ, MAX, 3DS, C4D.%{page}"}},"title":{"download":"| скачать","files":"3д файлы","formats":"%{format_upper} 3D модели","free":"Бесплатные 3D модели","free_printing":"Бесплатные модели для 3D принтера","free_tag":"Бесплатные %{selected_tag} 3д модели","general":"Профессиональные 3D модели","high_poly":"%{selected_filter} 3д модели.","landing":{"animated_3d_models":"Анимированные 3D модели","lowpoly_3d_models":"Низкополигональные 3D модели","pbr_3d_models":"PBR 3D модели","rigged_3d_models":"Rigged 3D модели"},"low_poly":"%{selected_filter} 3д модели","newest_cg":"Новые и последние 3д модели","newest_print":"Новые и последние 3д модели для печати","page_prefix":"Page %{page_number} ","pbr":"PBR 3д модели","printing":"Модели для 3D принтера","sales_cg":"Самые продаваемые 3д модели","sales_print":"Самые продаваемые 3д модели для печати","software_page":"3D модели для %{software}","tag_heading":{"cg":"Бесплатные  %{tag} 3д модели для печати","filter":"%{selected_filter} %{tag} 3д модели","general":"%{tag_cap}: %{rounded_count} 3D моделей","merged_premium_print_tag":"%{tag_cap}: модели для 3D принтера","merged_premium_tag":"%{tag_cap}: %{rounded_count} 3D моделей","printable":"%{tag_cap}: модели для 3D принтера"},"trending_cg":"Трендовые 3д модели","trending_print":"Трендовые 3д модели для печати"}},"cart":{"request_invoice_failed":"Упс. Что-то пошло Ð
16518½Ðµ так.","request_invoice_successful":"Запрос на счёт успешно отправлен.","shareable_cart_created":"Корзина успешно поделена.","shareable_cart_failed":"Упс. Что-то пошло не так.","subscription_covered_items_removed":"Удалено из корзины: %{titles}. Уже включено в вашу подписку.","subscription_covered_order_refused":"Теперь %{titles} входит в вашу подписку, поэтому оплата не была списана. Скачайте через подписку."},"categories":{"cg":{"header_title_html":"Профессиональные 3D модели","text":"Найдите нужный 3D-контент именно под ваши задачи, включая AR/VR, игры, рекламу, развлечения и другое."},"head-title":{"cg":"3D модели – скачать бесплатно или купить","cg-models":{"sort-by":{"newest":"Новые 3D-модели","top-selling":"Самые продаваемые 3D-модели","trending":"Популярные 3D-модели"}},"print":"Модели для 3D принтера – 3D модели для печати","print-models":{"sort-by":{"newest":"Новые модели для 3D-печати","top-selling":"Самые продаваемые модели для 3D-печати","trending":"Самые популярные модели для 3D-печати"}},"product-type":{"plural":{"capital":{"cg":"3д модели","cg-merge-premium":"3D-модели – бесплатные и премиум-загрузки","print":"Файлы и Модели для 3D-Печати","print-merge-premium":"%{tag} Файлы для 3D-печати — бесплатные и премиум-модели STL"},"lower":{"cg":"3д модели","print":"файлы и модели для 3D-печати"}},"singular":{"capital":{"cg":"3д модель","print":"Модель для 3д-печати"},"lower":{"cg":"3д модель","print":"Модель для 3д-печати"}}}},"index":{"browse_all":"Просмотреть все модели","info-section__description_1_html":"Покупайте или бесплатно скачивайте профессиональные 3D-модели для CG-проектов, видеопроизводства, анимации, визуализаций, игр, VR/AR и других нужд. Файлы доступны для загрузки в \u003ca href='https://www.cgtrader.com/blog/3d-file-formats' target='_blank' rel='noopener noreferrer'\u003eпопулярных форматах\u003c/a\u003e: MAX, OBJ, FBX, 3DS, STL, C4D, BLEND, MA, MB и других. Если вы ищете высокополигональные или real-time 3D-ассеты — у нас есть передовая библиотека цифрового исскуства под любые задачи.","info-section__description_1_printable_html":"Профессиональные 3D-файлы, модели и отпечатки, готовые для печати дома на настольных FDM или промышленных SLS 3D-принтерах, доступны в различных форматах, включая OBJ, \u003ca href=\"https://www.cgtrader.com/3d-print-models/stl-files\"\u003eSTL\u003c/a\u003e, WRL. Если вы любитель 3D-печати, мастер, педагог или компания, занимающаяся прототипированием с помощью 3D-печати, наши предметы декора для дома, стильные аксессуары для гаджетов, яркие модные изделия, уникальные украшения, механические детали и разнообразие миниатюр обязательно вдохновят вас.","info-section__heading_1_html":"Исследуйте \u003cstrong\u003eкатегории\u003c/strong\u003e 3D-моделей","info-section__heading_1_printable_html":"Посмотреть \u003cstrong\u003eкатегории\u003c/strong\u003e моделей для 3д-печати","info-section__heading_2_html":"\u003cstrong\u003eМаркетплейс\u003c/strong\u003e 3д моделей","info-section__heading_2_printable_html":"А что \u003cstrong\u003eещё\u003c/strong\u003e?"},"marketplace":{"3d-printing":{"link_text":"Показать модели для 3D-печати","text":"Найдите 3D-модели для своего 3D-принтера.","title":"Модели для 3D-печати"},"blender_text":"Откройте тысячи 3D-моделей Blender в формате .blend.","browse":"Показать","cinema-4d_text":"Откройте тысячи высококачественных моделей Cinema 4D в формате .c4d.","discounted":{"link_text":"Посмотреть 3д модели со скидкой","text":"Купить профессиональные 3д модели со скидкой.","title":"3д модели со скидкой"},"low-poly":{"link_text":"Показать low-poly 3D-модели","text":"Low-poly 3D-модели для видеоигр, VR и AR-приложений в реальном времени.","title":"Low-poly 3D-модели"},"pbr":{"link_text":"Показать PBR 3D-модели","text":"3D-модели с физически корректным рендерингом.","title":"PBR 3D-модели"},"scripts_apps":{"link_text":"Показать все","text":"Скрипты и плагины для повышения эффективности 3D-пайплайна.","title":"Скрипты и приложения"},"sketchup_text":"Изучите огромный выбор моделей Sketchup для архитектуры, интерьеров и дизайна видеоигр.","textures":{"link_text":"Показать текстуры","text":"Готовые текстуры для ускорения подготовки ваших 3D-моделей.","title":"Текстуры"},"zbrush_text":"Найдите 3D-модели в формате ZTL для цифровой лепки, моделирования, текстурирования и покраски."},"meta-description":{"cg":"Скачайте или купите %{rounded_count} профессиональных 3D моделей. Бесплатные и платные 3д модели для игр, кино, VFX, AR/VR и 3D печати. FBX, OBJ, BLEND, GLB.","print":"Скачайте %{rounded_count} моделей для 3д принтера. Бесплатные и платные STL модели для 3D печати: фигурки, миниатюры, декор, запчасти."},"more":{"designers":{"link_text":"Показать список 3D-дизайнеров","text":"Выбирайте из мÐ
16518½Ð¾Ð¶ÐµÑÑ‚ва портфолио 3D-дизайнеров и найдите именно то, что нужно для вашего проекта.","title":"Найдите лучших 3D-дизайнеров"},"projects":{"link_text":"Узнать больше о 3D-проектах","text":"Наймите опытных 3D-фрилансеров и решите любые задачи по 3D-моделированию.","title":"3D-проекты на CGTrader "},"visualization":{"link_text":"Узнать больше","text":"Превратите свои товары в фотореалистичные 3D-модели для e-commerce и других задач.","title":"3D-визуализация для e-Commerce"}},"navbar":{"aircraft":"Авиация","animal":"Животные","animals":"Животные","architectural":"Архитектурные","art":"Искусство","car":"Автомобиль","character":"Персонаж","exterior":"Экстерьер","fashion":"Мода","food":"Еда","furniture":"Мебель","gadgets":"Гаджеты","games-toys":"Игры и игрушки","hobby-diy":"Хобби и DIY","house":"Дом","household":"Домашнее хозяйство","industrial":"Промышленные","interior":"Интерьер","jewelry":"Ювелирные изделия","military":"Вооруженные силы","miniatures":"Миниатюры","plant":"Растения","science":"Наука","space":"Космос","vehicle":"Транспортное средство","watercraft":"Водный транспорт"},"print_models":{"hero_title_html":"Модели для 3D принтера","subtitle":"Найти точно подходящий контент для 3д-печати под ваши требования","title_html":"Купить профессиональные \u003cstrong\u003e3д модели для печати\u003c/strong\u003e"},"profile":{"collection":"3D-паки","free":"Бесплатные 3D модели","high_poly":"Высокополигональные 3D модели","optimized":"Низкополигональные/PBR 3D модели","printable":"Модели для 3D-печати"},"seo":{"cg":{"aircraft":"Эта каtagория охватывает 3д самолетов. CG { \"slug\": \"Самолет\", \"title\": \"самолеты\"} будет вписываться в симуляции, визуализацию, рекламу и видео. { \"slug\": \"дрон\", \"title\": \"Дрон\"} органы и части будут радовать поклонников крошечных летательных аппаратов. А { \"slug\": \"Самолет\", \"title\": \"сфальсифицированы модели\", \"active_filter\": \"сфальсифицировано\"} готовы быть импортировано в игровые системы.\n","animals":"Коллекция 3д фауны - { \"slug\": \"коты\", \"title\": \"Коты\"}, { \"slug\": \"лошадь\", \"title\": \"лошадей\"}, { \"slug\": \"медведь\" , \"title\": \"медведи\"}, { \"slug\": \"носорог\", \"title\": \"носороги\"}, { \"slug\": \"орел\", \"title\": \"орлы\"}, { \"slug\" : \"лев\", \"title\": \"львы\"} и больше - можно найти здесь. В CG 3д животные будут удовлетворять потребности рекламы или статических сцен. Мы также имеем { \"slug\": \"животное\", \"title\": \"анимированная животное\", \"active_filter\": \"сфальсифицировано\"} модель для игр.\n","architectural":"{ \"slug\": \"лестница\", \"title\": \"Лестница\" }, { \"slug\": \"камин\"}, 3д модели, а также украшения, как { \"slug\": \"кадр\"}, { \"slug\": \"подушку\"}, или { \"slug\": \"лампа\"} можно найти в обширной архитектурной каtagории детали автомобиля содержит автомобильные 3д модели, { \"slug\": \"машина\", \"title\": \"низкополигональное\", \"active_filter \": \"low_poly\"} модель библиотеки. Интерьер и экстерьер может быть использован для создания как цифровые, кухни и спальни, а также замок номера и подземелий ли вы архитектор, инженер или инди-разработчик компьютерных игр.\n","car":"Каtagория автомобилей содержит автомобильную 3д модели, \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e низкополигональной \u003c/a\u003e версии, которые будет привлекательна для дизайнеров игры, другой - кинематографисты или рекламодателей. От классических автомобилей до гоночных автомобилей и путевых к концепции тех, что есть большое разнообразие моделей 3д авто.\n","character":"В этой каtagории вы найдете 3д модели { \"slug\": \"человека\"}, люди, научной фантастики, одежды и других. С { \"slug\": \"сердце\"} и { \"slug\": \"рука\"} детальному реалистической { \"slug\": \"мужской\"} или { \"slug\": \"женский\"} моделей. Ряд моделей сфальсифицированы и может быть легко добавлен к вашей игре или анимации.\n","electronics":"Электроника каtagория для архитекторов, декораторов, дизайнеров уровней, руководителей сцены и других объявлений. 3д модель { \"slug\": \"смартфон\", \"названия\": \"смартфоны\"}, { \"slug\": \"ноутбук\", \"title\": \"ноутбуки\"}, { \"slug\": \"камера\", \"title\":\"камера\"}, {\"slug\":\"ТВ\",\"title\":\"телевизоры\"} и многим другим поможет точно захватить одержимость современной жизни с устройствами и технологиями.\n","engineering-parts":"Окна, трубопроводы, раздвижные механизмы двери и другие биты, которые необходимы для CAD проектов и даже DIY планы подпадают под эту каtagорию. Здесь вы можете найти 3д модели от мировых производителей, таких как Анвер, Dynatec и FCi.\n","exterior":"Обзор архитектурных экстерьеры от фасадов, городские здания до ландшафтных объектов, жилых домов, промышленных сооружений и полномасштабной 3д { \"slug\": \"город\"} моделей. Некоторые из активов низкополигональные и могут помочь вам завершить игру или приложение VR / AR.\n","food":"Еда помогает создавать реалистичные сцены домашней жизни. 3д фрукты, овощи, напитки и питание все здесь. Объявления для ресторанов, AR приложений для здорового питания. Если вы искали 3д { \"slug\": \"пицца\"} или свадебную { \"slug\": \"торт\"} модель, они здесь.\n","furniture":"3д столы, стулья, диваны, кровати в различных текстур (дерево, металл и т.д.) и стилей ({ \"slug\": \"ретро\"}, { \"slug\": \"современных\"} и т.д.), на- рука. Эта каtagория предназначена для всех, кто занимается с архитектурой и дизайном интерьера, а также рекламодателей.\n","furniture-set":"Если вам нужно, так глядя антикварный стул и диван, глядя, или полный набор кухонной посуды, это имеет место быть в этой каtagории собирать наборы 3д моделей мебели из кухни и гостиной в офис и столовой гарнитуров. Это помогает интерьеру или дизайну уровня специалиста сборка номеру с соответствующими элементами.\n","household":"Бытовая пункт 3д модели используются, чтобы дать подробную информацию в номера. Вы можете поставить обеденный набор 3д на столе, или перец и соль контейнеры полного интерьера кухни, может поставить некоторые { \"slug\": \"свет\", \"title\": \"огни\"} и { \"slug\": \"цветы\"} возле кровати, чтобы создать ощущение роскоши.\n","industrial":"Строительные материалы, { \"slug\": \"инжиниринг\"} инструменты, машина и { \"slug\": \"контейнер\", \"title\": \"контейнеры\"}, которые обеспечивают как экологические данные и интерактивные, сфальсифицированы элементы. Это идеально подходит для разработчиков моделирования игры или людей, строящих { \"slug\": \"строительство\"} уровня двора.\n","interior":"Эта каtagория содержит реалистичные 3д элементы и детали архитектуры, строительства и инженерного дизайна интерьера. Вы можете использовать эти 3д модели для анимации или игр - некоторые из них сфальсифицированы и низкополигональные.\n","military":"В этой каtagории вы можете купить 3д модели оружия, оружие, { \"title\": \"мечи\", \"slug\": \"меч\"} и артиллерии как современные и исторических используется вооруженными силами и самообороной. Вы можете использовать модели для игр - некоторые из них являются многоугольниками Оптимизированные и готовые для игровых движков.\n","plant":"Эта каtagория содержит 3д модели растений организмов, такие как цветы, { \"slug\": \"дерево\", \"title\": \"дерева\"}, трава, лианы, зелень, травы, многолетники, саженцы, побеги, кустарники, промахи, рассаду, лозы, сорняки. Для того, чтобы купить или скачать бесплатно 3д модели, просматривать каtagории ниже.\n","scanned":"Модельеры создают эти модели с помощью 3д сканирования технологий, таких как RealSense. Пни, статуи, даже мужчины и женщины пользуются поslugрностью здесь. Продукт готов к использованию в сценах и анимации, предлагая непревзойденный уровень реализма.\n","scie
16518nce":"Эта каtagория содержит 3д модели, относящиеся к медицине, биологии, физике, химии и технологии, такие как лабораторное оборудование, микроскопических тел и субатомном мире. Нет необходимости искать дальше, { \"slug\": \"ячейки\"}, { \"slug\": \"череп\"} и { \"slug\": \"скелет\"} 3д-модели можно найти здесь.\n","scripts-plugins":"Сценарии и плагины обеспечивают вам ярлыки программ. Моделирование, рендеринг, текстурирования - все это можно сделать проще с помощью плагинов, которые приходят для всех видов программного обеспечения, как Maya и V-Ray.\n","space":"Пространство каtagория полна низкополигонального, игровые готовые модели фантастики { \"slug\": \"космический корабль\", \"титул\": \"космические корабли\"}, { \"slug\": \"астероид\", \"титул\": \"астероиды \"}, планеты (в том числе {\"slug\":\"землей\",\"title\":\"земли\"} и реалистического пространства {\"slug\":\"челнока\",\"title\":\" челноки\"} для более с научной точки зрения наклонена 3д космические модели полезны для VR пространства симуляторов.\n","sport-hobby":"Эта каtagория охваты- ничего, что связано с { \"slug\": \"поле\"} события, спорт и хобби в целом. С { \"slug\": \"футбол\"} и лыжные склоны до { \"slug\": \"гитары\"} и { \"slug\": \"фортепиано\"}, эти полезные 3д модели для рекламы, сцен и анимации. Низкополигональных версии есть для спорта видеоигр.\n","textures":"Просмотр через библиотеку красиво сделанные 3д материалов и найти самые подходящие, готовые к использованию текстур подготовить \u003ca href=\"%{link_to_low_poly_items_url}\"\u003e игру готова \u003c/a\u003e или фотореалистичные модели, обновить библиотеку активов , а также улучшить свои 3д работы.\n","various":"Это 3д-модели, которые не легко вписываются в другие каtagории, поэтому мы создали один, который принял бы их всех. { \"title\": \"Rocks\", \"slug\": \"камни\"}, { \"slug\": \"облака\"}, { \"title\": \"логотипы\", \"slug\": \"логотип\"}, { \"title\" : \"зонтики\", \"slug\": \"зонтик\"} и другие не классифицирован 3д модель здесь.\n","vehicle":"Грузовые автомобили, { \"title\": \"скорая помощь\", \"slug\": \"скорой помощи\"}, автобусы, поезда, тракторы, { \"title\": \"велосипеды\", \"slug\": \"велосипед\"} и мотоциклов - это каtagория для люди, которые хотят, чтобы их городские улицы должны быть заполнены реалистичными и Ð
16518½Ð°ÑƒÑ‡Ð½Ð¾-фантастическими транспортными средствами, а также для тех, кто делает имитационные игры или форматирование плакаты.\n","watercraft":"Судовые покрытия { \"title\": \"лодка\", \"slug\": \"лодка\"}, { \"title\": \"яхта\", \"slug\": \"яхта\"}, { \"title\": \"корабли\", \"slug\" : \"корабль\"} и другие водные транспортные средства. Сфальсифицированы 3д модели будут делать хорошо в видеоиграх, анимации в то время как модели CG есть для ваших сцен или других приложений, где визуальная точность важнее Polycount.\n"},"forwarded":{"cg":{"aircraft":"Эта категория включает 3D-модели авиации. Самолёты подойдут для симуляций, визуализаций, рекламы и видео. Дроны и части к ним порадуют фанатов миниатюрной воздушной техники. Также есть модели с риггом, готовые для игровых движков.","animals":"Здесь вы найдёте 3D-модели фауны: кошек, лошадей, медведей, носорогов, орлов, львов и других. Подходят для рекламы и сцен. Здесь вы также найдете  модели животных с риггом, готовые для игровых движков.","architectural":"Лестницы, камины и другие 3D-модели, а также декоративные элементы — рамки, подушки, лампы — доступны в обширной библиотеке архитектурных деталей. Интерьерные и экстерьерные модели подойдут для создания цифровых кухонь, спален, замковых залов или подземелий — будь вы архитектором, инженером или инди-разработчиком игр.","car":"Категория включает 3D-модели автомобилей, включая низкополигональные. Подходит для игр, фильмов и рекламы — от классики до гоночных и концептов. Здесь вы сможете найти большое разнообразие автомобильных 3D-моделей!","character":"Здесь вы найдёте 3В этой категории представлены 3D-модели людей, персонажей, элементов научной фантастики, одежды и многого другого. От сердца и руки до детализированных реалистичных мужских и женских моделей. Включая версии с риггом которые с легкостью могут быть использованы в вашей игре или анимации.","electronics":"Электроника - категория для архитекторов, декораторов, дизайнеров, руководителей сцены и других специальностей. 3д модели { \"slug\": \"smartphone\", \"title\": \"смартфоны\"}, { \"slug\": \"laptop\", \"title\": \"ноутбуки\"}, { \"slug\": \"camera\", \"title\":\"камера\"}, {\"slug\": \"tv\",\"title\":\"телевизоры\"} и многие другие помогут точно показать одержимость современной жизни устройствами и технологиями.\n","engineering-parts":"Под эту категорию попадают окна, трубопроводы, механизмы раздвижных дверей и другие детали, необходимые для CAD-проектов и даже проектов DIY. Здесь вы можете найти 3д-модели от мировых производителей, таких как ANVER, Dynatec и FCi.","exterior":"Ознакомьтесь с архитектурными экстерьерами — от фасадов и городских зданий до ландшафтных объектов, жилых домов, промышленных построек и полноценных 3D-моделей городов. Некоторые ассеты имеют низкое число полигонов и отлично подойдут для завершения вашего игрового или VR/AR-проекта.","food":"Еда помогает создавать реалистичные сцены повседневной жизни. Здесь вы найдёте 3D-модели фруктов, овощей, напитков и готовых блюд. Ресурсы подойдут для ресторанной рекламы, AR-приложений, посвящённых здоровому питанию и Ð
16518½Ðµ только. Хотите 3D-пиццу или свадебный торт? Всё это — здесь.","furniture":"3д столы, стулья, диваны, кровати различных текстур (дерево, металл и т.д.) и стилей ({ \"slug\": \"retro\", \"title\": \"ретро\"}, { \"slug\": \"modern\", \"title\": \"современный\"} и т.д.) под рукой. Эта категория предназначена для всех, кто занимается архитектурой и дизайном интерьера, а также для рекламодателей.\n","furniture-set":"Если вам нужно одинаково выглядящее антикварное кресло и диван или полный набор кухонной утвари, это то место, где вы можете это всё найти, поскольку эта категория собирает наборы 3д-моделей мебели от кухни и гостиной до офисных и обеденных наборов мебели. Это помогает специалисту по дизайну интерьера или специалисту другого уровня построить комнаты с соответствующими элементами.","household":"3D-модели предметов быта помогают добавлять детализацию в интерьер. Вы можете поставить сервиз на стол, добавить баночки с солью и перцем на кухне или разместить светильники и цветы у кровати, чтобы создать ощущение уюта и роскоши.","industrial":"Строительные материалы, инженерные инструменты, техника и контейнеры — всё это добавляет как визуальные детали, так и интерактивные, риггированные элементы. Идеально подходит для разработчиков симуляторов и тех, кто создаёт уровни со строительными площадками.","interior":"В этой категории собраны реалистичные 3D-элементы и детали архитектуры, строительства и инженерного дизайна интерьеров. Эти модели можно использовать в анимации или играх — некоторые из них с риггоми оптимизированы по полигонам.","military":"В этой категории вы можете приобрести 3D-модели оружия, мечей, артиллерии и другого вооружения — как современного, так и исторического, использовавшегося армиями или для самообороны. Модели можно использовать в играх — многие из них оптимизированы по полигонам и готовы для игровых движков.","plant":"Включает модели флоры: цветы, деревья, трава, кусты, лианы, побеги и многое другое. Вы можете купить или бесплатно скачать 3D-модель.","scanned":"Дизайнеры создают эти модели с помощью технологий 3д сканирования, таких как RealSense. Пни, статуи, даже мужчины и женщины здесь пользуются популярностью. Продукт готов к использованию в сценах и анимации, предлагая непревзойденный уровень реализма.\n","science":"Эта категория содержит 3д-модели, связанные с медициной, биологией, физикой, химией и технологиями, такими как лабораторное оборудование, микроскопические тела и субатомный мир. Больше не ищите, {\"slug\": \"cell\"}, {\"slug\": \"skull\"} и {\"slug\": \"skeleton\"} 3д-модели можно найти здесь.","scripts-plugins":"Скрипты и плагины обеспечивают вас программными горячими клавишами. Моделирование, рендеринг, текстурирование - все это можно упростить с помощью плагинов, которые поставляются для всех видов программного обеспечения, таких как Maya и V-Ray.","space":"Космос — это категория, полная низкополигональных, готовых к использованию в играх 3D моделей научно-фантастических космических кораблей, астероидов, планет (включая Землю и реалистичные космические челноки для более научного применения). Также  полезны для использования в космических симуляторах виртуальной реальности.","sport-hobby":"Эта категория охватывает все, что связано с { \"slug\": \"field\",\"title\": \"поле\"} событиями, спортом и хобби в целом. От { \"slug\": \"football\",\"title\": \"футбол\"} и лыжных склонов до { \"slug\": \"guitar\",\"title\": \"гитары\"} и { \"slug\": \"piano\",\"title\": \"фортепиано\"}, это полезные 3д модели для рекламы, сцен и анимации. Низкополигональные версии есть для спорта и видеоигр.\n","textures":"Просматривайте библиотеку тщательно проработанных 3D-материалов и находите готовые к использованию текстуры, идеально подходящие для создания игровых или фотореалистичных моделей, расширения вашей библиотеки ассетов и улучшения 3D-проектов.","various":"Это 3д модели, которые не так легко вписываются в другие категории, поэтому мы создали одну, которая будет группировать их все. { \"title\": \"Горные породы\", \"slug\": \"rocks\"}
16518, { \"slug\": \"clouds\",\"title\": \"облака\"}, { \"title\": \"логотипы\", \"slug\": \"logo\"}, { \"title\" : \"зонтики\", \"slug\": \"umbrella\"} и другие не категоризированные 3д модели вы найдете здесь.\n","vehicle":"Грузовики, машины скорой помощи, автобусы, поезда, тракторы, велосипеды и мотоциклы — эта категория для тех, кто хочет наполнить городские улицы реалистичным или фантастическим транспортом, а также для разработчиков симуляторов и создателей постеров.","watercraft":"Категория «Водный транспорт» включает лодки, яхты, корабли и другие плавательные средства. Риггированные 3D-модели отлично подойдут для видеоигр и анимации, а CG-модели — для сцен и проектов, где визуальная достоверность важнее количества полигонов."},"printable":{"art":"Здесь находятся художественные 3д-принты. Ищите необычный скульптурный дизайн, воссоздающий классические произведения и статуи исторических личностей, или вдохновляющие статуэтки, может быть, даже логотипы для 3д-печати? Это и есть данная категория.","fashion":"Красивые и стильные вещи, которые вы можете носить, вы найдете в этой категории. Посмотрите, можете ли вы найти какое-либо применение для фантазийной космической {\"slug\": \"footwear\",\"title\": \"обуви\"}, инновационных очков и косплея {\"slug\": \"accessories\",\"title\": \"аксессуаров\"}, и ищите здесь.\n","gadgets":"Все { \"title\": \"iPhone\", \"slug\": \"iphone\"}, { \"title\": \"IPad\", \"slug\": \"ipad\"} (и другие интеллектуальные устройства) гаджеты могут быть найдены в категории гаджетов. От простого 3д дополнения до сложных деталей и \"хаков\" для ваших вещей, это категория для творческих людей и любителей техники.\n","games-toys":"Игры и игрушки - это категория, охватывающая печатные 3д НЛО или миниатюры монстров, крошечные { \"title\": \"танки\", \"slug\": \"tank\"}, прикольные{ \"slug\": \"chess\",\"title\": \"шахматы\"}, шлемы киногероев, принадлежности и аксессуары для хобби. Это категория подходит для всех вещей для гиков.\n","hobby-diy":"Если вы наш следующий великий изобретатель и хотите создать свой DIY проект или получить какие-то запасные части для { \"slug\": \"drone\",\"title\": \"дронов\"}, эта категория для вас. Кроме того, она имеет много вещей для людей, которые хотят сделать свои собственные фигурки или пластиковые реплики пушек.\n","house":"В
16518се виды безделушек можно найти здесь. { \"title\": \"Лампа\", \"slug\": \"lamp\"}, художественная { \"title\": \"чаша\", \"slug\": \"bowl\"} и все виды { \"title\": \"держатели\", \"slug\": \"holder\"}, если вы не можете найти правильное место для вашего телефона, сохранить порядок в зубочистках, куче ручек или украшений на вашем столе.\n","jewelry":"Ювелирные изделия - это та категория, где можно купить браслеты, кулоны или кольца с бриллиантами. Хотя 3д - принтеры недостаточно хороши для изготовления бриллиантов - пока вы все еще можете распечатать металлическое кольцо в качестве основы для вашего IT гиковского кольца для предложения.","miniatures":"3д печатные { \"title\": \"персонажи\", \"slug\": \"character\"}, известные люди, { \"title\": \"достопримечательности\", \"slug\": \"landmark\"}, животные, { \"title\": \"Мужчина \", \"slug\": \"male\"}, { \"slug\": \"female\",\"title\": \"женщина\"}, { \"title\": \"монстры\", \"slug\": \"monster\"}, { \"title\": \"инопланетяне\",\"slug\": \"alien\"} - все это здесь. Если вы печатаете коммунальные транспортные средства, чтобы поставить на стол или создать миниатюры для настольных игр, это ваша категория.\n","science":"Что может быть классифицировано как наука? Ну, модель скелета { \"slug\": \"bones\",\"title\": \"кость\"}, { \"slug\": \"tools\", \"title\": \"Инструменты\"} и связанные с астрофизикой 3д-печатные модели, которые могут быть использованы во многих исследовательских процессах.\n"}},"printable":{"art":"Это место, где художественные 3д печать идти. Глядя на дизайн фантазии скульптуры воссоздание классических произведений и статуй исторических личностей, или вдохновляющие фигурки, может быть, даже 3д-печатаемые логотипы? То есть каtagория.\n","fashion":"Красивые и стильные вещи, которые вы можете носить можно найти в этой каtagории. Смотрите, если вы можете найти любое использование для фантазии космической эры {\"slug\": \"обувь\"}, новизна очков и косплея {\"slug\": \"аксессуары\"} найти здесь.\n","gadgets":"Все { \"title\": \"iPhone\", \"slug\": \"iphone\"}, { \"title\": \"IPad\", \"slug\": \"Ipad\"} (и другие интеллектуальные устройства) случаи могут быть найдены в каtagории гаджетов. От простого 3д дополнений в сложные детали и \"хаки\" для ваших вещей, это каtagория для объявлений и любители техники.\n","games-toys":"Игры и игрушки это каtagория, охватывающая печатаемая 3д чужеродной или монстр миниатюру, крошечные { \"title\": \"танки\", \"slug\": \"танк\"}, фанк { \"slug\": \"шахматы\"} штуки, символьные кино шлемы, хобби принадлежности и аксессуары. Это каtagория к ходу для всех вещей вызывающих.\n","hobby-diy":"Если вы наш следующий великий изобретатель и хотите построить свой DIY проект или получить некоторые запасные части для { \"slug\": \"дронов\"}, это место быть. Кроме того, он имеет много вещей для людей, которые хотят сделать свои собственные фигурки или пластиковые пушки реплики.\n","house":"В
16518се виды безделушек можно найти здесь. { \"title\": \"Лампа\", \"slug\": \"лампа\"}, художественный { \"title\": \"чаша\", \"slug\": \"чаша\"}, и все виды { \"title\": \"держатели\", \"slug\": \"держатель\"} ли вы не можете найти правильное место для вашего телефона, сохранить порядок зубочистки, куча ручек или украшений на вашем столе.\n","jewelry":"Ювелирные изделия, где вы идете, чтобы получить браслеты и подвеску или кольцо с бриллиантами. В то время как 3д принтеры не достаточно хороши, чтобы сделать алмазы - еще - вы можете распечатать металлическое кольцо в качестве основы для вашего ИТ-выродка предложение кольца.\n","miniatures":"3д версия для печати { \"title\": \"символы\", \"slug\": \"символов\"}, известные люди, { \"title\": \"достопримечательности\", \"slug\": \"знаковые\"}, животные, { \"title\": \"Мужчина \", \"slug\": \"мужская\"}, { \"slug\": \"женская\"}, { \"title\": \"монстры\", \"slug\": \"монстр\"}, { \"title\": \"инопланетяне\",\"slug\" :\"иностранец\"} - все это здесь. Если вы печатаете коммунальные транспортные средства, чтобы поставить на стол или создание миниатюр для настольных игр, это ваша каtagория.\n","science":"Что может быть классифицирован как наука? Ну, модель скелета { \"slug\": \"кость\"}, { \"slug\": \"Инструменты\"} и астрофизики, связанная с 3д моделей для печати, которые могут быть использованы во многих процессах обучения.\n"},"titles":{"aircraft":"3D-модели авиации","animal":"3D-модели животных","animals":"3D-модели животных","architectural":"Архитектурные 3D-модели","car":"3D-модели автомобилей","character":"3D-модели персонажей","exterior":"3D-модели экстерьеров","food":"3D-модели еды","furniture":"3D-модели мебели","household":"Бытовые 3D-модели","industrial":"Промышленные 3D-модели","interior":"3D-модели интерьеров","military":"Военные 3D-модели","plant":"3D-модели растений","space":"Космические 3D-модели","textures":"Текстуры","vehicle":"Транспортные 3D-модели","watercraft":"3D-модели водного транспорта"}},"title":{"cg":"3д модели","print":"Файлы для 3D-печати"}}
16518,"competition":{"digital-art-competition":{"categories":{"page_description":"Принять участие в Конкурсе CGTrader Digital Art, представив свои работы Character Иллюстрация, характер концепции дизайна, окружающей среды Иллюстрация, Окружающая среда Концепция дизайна, объектов дизайна и объектов каtagории Concept Design. Если у вас есть несколько больших работ, которые подходят для различных каtagорий, не стесняйтесь представить их в соответствующие каtagории. Выберите каtagорию, чтобы увидеть призы для этой каtagории."}}},"conversations":{"action_button_confirmation":"Вы действительно хотите снова открыть этот запрос?","reply_posted":"Ответ был опубликован.","with_yourself":"не может совпадать с отправителем."},"date":{"abbr_day_names":{},"abbr_month_names":{},"common_abbr_day_names":["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],"common_abbr_month_names":[null,"янв.","февр.","марта","апр.","мая","июня","июля","авг.","сент.","окт.","нояб.","дек."],"common_day_names":["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],"common_month_names":[null,"января","февраля","марта","апреля","мая","июня","июля","августа","сентября","октября","ноября","декабря"],"day_names":{},"formats":{"default":"%d.%m.%Y","long":"%d %B %Y","short":"%d %b"},"month_names":[null,"Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],"order":["day","month","year"],"standalone_abbr_day_names":["вс","пн","вт","ср","чт","пт","сб"],"standalone_abbr_month_names":[null,"янв.","февр.","март","апр.","май","июнь","июль","авг.","сент.","окт.","нояб.","дек."],"standalone_day_names":["Воскресенье","Понедельник","Вторник","Среда","Четверг","Пятница","Суббота"],"standalone_month_names":[null,"Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"]},"datetime":{"distance_in_words":{"about_x_hours":{"few":"около %{count} часов","many":"около %{count} часов","one":"около %{count} часа","other":"около %{count} часов"},"about_x_months":{"few":"около %{count} месяцев","many":"около %{count} месяцев","one":"около %{count} месяца","other":"около %{count} месяцев"},"about_x_years":{"few":"около %{count} лет","many":"около %{count} лет","one":"около %{count} года","other":"около %{count} лет"},"almost_x_years":{"few":"почти %{count} года","many":"почти %{count} лет","one":"почти %{count} год","other":"почти %{count} лет"},"half_a_minute":"меньше минуты","less_than_x_minutes":{"few":"меньше %{count} минут","many":"меньше %{count} минут","one":"меньше %{count} минуты","other":"меньше %{count} минут"},"less_than_x_seconds":{"few":"меньше %{count} секунд","many":"меньше %{count} секунд","one":"меньше %{count} секунды","other":"меньше %{count} секунд"},"over_x_years":{"few":"больше %{count} лет","many":"больше %{count} лет","one":"больше %{count} года","other":"больше %{count} лет"},"x_days":{"few":"%{count} дня","many":"%{count} дней","one":"%{count} день","other":"%{count} дней"},"x_minutes":{"few":"%{count} минуты","many":"%{count} минут","one":"%{count} минута","other":"%{count} минут"},"x_months":{"few":"%{count} месяца","many":"%{count} месяцев","one":"%{count} месяц","other":"%{count} месяцев"},"x_seconds":{"few":"%{count} секунды","many":"%{count} секунд","one":"%{count} секунда","other":"%{count} секунд"},"x_years":{"few":"%{count} года","many":"%{count} лет","one":"%{count} год","other":"%{count} лет"}},"prompts":{"day":"День","hour":"Часов","minute":"Минут","month":"Месяц","second":"Секунд","year":"Год"}}
16518,"design_partnership":{"benefits":{"benefits_arr":[{"title":"Начинайте работать сейчас","info":"Не нужно искать клиента – работайте над сотнями доступных задач по моделированию, которые соответствуют вашим интересам и финансовым ожиданиям.","icon":"rabbit-fast"},{"title":"Гибкое рабочее время","info":"Подходит и для сов и для жаворонков - вы единственный, кто выбирает сколько поработать.","icon":"clock"},{"title":"Работайте комфортно на дому","info":"Все фриланс-задачи могут быть выполнены дома на вашем домашнем компьютере или в любом другом места, где вы предпочитаете работать.","icon":"couch"},{"title":"Получайте настоящие деньги","info":"Зарабатывайте настоящие деньги. Перечисления через Paypal, Payoneer, Webmoney или прямо на ваш банковский счет.","icon":"money-check-alt"},{"title":"Выделенная 3D поддержка","info":"Наша выделенная команда готова поделиться 3D ноу-хау, ответить на технические вопросы и помочь вам выполнить задачу.","icon":"user-friends"},{"title":"Работайте с топ заказчиками","info":"Создавайте пользовательские модели для глобального бизнеса (включая компании из списка Fortune 500).","icon":"hands-helping"}],"section_title":"Что в этом для вас?"},"cta_button":"Присоеденяйтесь к Wildcat сегодня","explanation_steps":{"section_title":"Присоединяйтесь сегодня","step_1":{"info":"Создайте аккаунт в CGTrader, если у вас его еще нет. Как только войдете в систему, предоставьте примеры Ваших работ или пройдите небольшой тест. Если Ваших навыки подойдут, Вы получите полный доступ к нашим оплачиваемым задачам.","title":"Присоединяйтесь"},"step_2":{"info":"Получили подтверждение? Немедля просмотрите доступные задачи по 3D моделированию в нашей системе Wildcat, работайте над темы, которые Вам понравятся более всего либо возвращайтесь за другими задачи попозже.","title":"Начинайте"},"step_3":{"info":"Выберите предпочитаемый метод оплаты и получайте свой заработок на фрилансе еженедельно или ежемесячно.","title":"Получайте оплату"}},"header":{"benefits":[{"text":"Зарабатывайте до $200 за каждую изготовленную модель"},{"text":"Выбирайте задачи согласно Вашим предпочтениям, никаких торгов"},{"text":"Наслаждайтесь бонусами множителя прибыли и топовыми ценами"},{"text":"Постоянный поток работы и дохода"}],"page_title":"Фриланс платформа без торгов для 3D дизайнеров","subtitle":"Присоеденяйтесь к эксклюзивному сообществу 3D дизайнеров и получите доступ к сотням курированых заданий по моделированию. Работайте в любом месте, в любое время!","switcher_html":"\u003ca href='/freelance-with-us'\u003eEN\u003c/a\u003e\n\u003cspan class='u-mr10 u-ml10'\u003e|\u003c/span\u003e\n\u003ca href='/ru/Zarabatyvayte-s-CGTrader' class='is-active'\u003eRU\u003c/a\u003e\n"},"help_section":{"contact_btn":"Связаться с нами","help_center_btn":"Посетите центр помощи","section_title":"Есть вопросы?"},"meta":{"description":"Получите доступ к сотням задач по 3д-моделированию и зарабатывайте деньги в удобном режиме.","title":"Зарабатывайте с CGTrader"},"positions":{"open_positions":[{"type":"pbr","title":"PBR (низкополигональное) моделирование","info_html":"\u003cp\u003eДоступно 3D-дизайнерам, имеющим опыт создания PBR моделей.\u003c/p\u003e\n\u003cp\u003e\n \u003cspan class=\"text-semibold\"\u003eТребования к программному обеспечению: отсутствуют\u003c/span\u003e \u003cbr /\u003e\n Формат доставки: \u003cspan class=\"text-semibold\"\u003e.fbx\u003c/span\u003e\n\u003c/p\u003e\n","badge":"В тренде!"},{"type":"archviz","title":"Aрх. виз. (высокополигональное) моделирование","info_html":"\u003cp\u003eДоступно 3D-дизайнерам, имеющим опыт 3D моделирования и текстурирования.\u003c/p\u003e\n\u003cp\u003e\n Требования к программному обеспечению: \u003cspan class=\"text-semibold\"\u003e3ds Max 2020\u003c/span\u003e (другие версии\n допускаются, но не рекомендуются) \u003cbr /\u003e\n Рендерер: \u003cspan class=\"text-semibold\"\u003eV-ray Next, обновление 3.2\u003c/span\u003e (другие версии\n допускаются, но не рекомендуются)\n\u003c/p\u003e\n","badge":""}],"section_title":"Доступные задачи на данный момент"}},"devise":{"failure":{"too_many_requests":"Вы уже запрашивали письмо для сброса пароля. Пожалуйста, подождите несколько минут, прежде чем попробовать снова."}},"disabled_cookies_modal":{"button":"отменить","text_1":"Вы не сможете исследовать миллионы больших 3D моделей, добавить их в корзин
16518у или Купить.","text_2":"Если вы хотите, чтобы в полной мере испытать CGTrader, мы рекомендуем позволяя кук на этом сайте.","title":"Кажется, вы отключили куки вашего браузера"},"emails":{"error_notification":"Подписка не найдена.","success_subscribed_notification":"Вы успешно подписались.","success_unsubscribed_notification":"Вы успешно отписались."},"errors":{"format":"%{attribute} %{message}","messages":{"accepted":"нужно подтвердить","blank":"не может быть пустым","confirmation":"не совпадает со значением поля %{attribute}","empty":"не может быть пустым","equal_to":"может иметь лишь значение, равное %{count}","even":"может иметь лишь четное значение","exclusion":"имеет зарезервированное значение","greater_than":"может иметь значение больше %{count}","greater_than_or_equal_to":"может иметь значение не меньше %{count}","in":"должно быть в диапазоне %{count}","inclusion":"имеет непредусмотренное значение","invalid":"имеет неверное значение","less_than":"может иметь значение меньше %{count}","less_than_or_equal_to":"может иметь значение не больше %{count}","model_invalid":"Возникли ошибки: %{errors}","not_a_number":"не является числом","not_an_integer":"не является целым числом","odd":"может иметь лишь нечетное значение","other_than":"должно отличаться от %{count}","present":"нужно оставить пустым","record_invalid":"Возникли ошибки: %{errors}","required":"не может отсутствовать","taken":"уже существует","too_long":{"few":"слишком большой длины (не может быть больше чем %{count} символа)","many":"слишком большой длины (не может быть больше чем %{count} символов)","one":"слишком большой длины (не может быть больше чем %{count} символ)","other":"слишком большой длины (не может быть больше чем %{count} символа)"},"too_short":{"few":"недостаточной длины (не может быть меньше %{count} символов)","many":"недостаточной длины (не может быть меньше %{count} символов)","one":"недостаточной длины (не может быть меньше %{count} символа)","other":"недостаточной длины (не может быть меньше %{count} символа)"},"wrong_length":{"few":"неверной длины (может быть длиной ровно %{count} символа)","many":"неверной длины (может быть длиной ровно %{count} символов)","one":"неверной длины (может быть длиной ровно %{count} символ)","other":"неверной длины (может быть длиной ровно %{count} символа)"}},"template":{"body":"Проблемы возникли со следующими полями:","header":{"few":"%{model}: сохранение не удалось из-за %{count} ошибок","many":"%{model}: сохранение не удалось из-за %{count} ошибок","one":"%{model}: сохранение не удалось из-за %{count} ошибки","other":"%{model}: сохранение не удалось из-за %{count} ошибки"}}},"file_size_validator":{"errors":{"invalid_carrierwave":"Ожидался объект CarrierWave::Uploader::Base","invali
16518d_key":":%{key} должно быть неотрицательным целым числом","range_format":":in и :within должны быть диапазоном (Range)","range_unspecified":"Диапазон не указан. Укажите параметр :within, :maximum, :minimum или :is."}},"file_type":{"default_error":"Путь к файлу не указан."},"footer":{"bottom-row":{"privacy":"Политика конфиденциальности","terms-link":"Правила и условия"},"links":{"buyLinks":[{"text":"Купить 3D-модели","link":"/ru/3d-modeli"},{"text":"Для бизнеса","link":"/discover/business-account"},{"text":"Бесплатные 3D-датасеты","link":"/discover/datasets"},{"text":"Нанять 3D-дизайнера","link":"/3d-modeling-jobs"},{"text":"Бесплатные 3D-модели","link":"/ru/besplatnye-3d-modeli"},{"text":"Бесплатные модели для 3D-печати","link":"/ru/besplatnye-3d-pechat-modeli"}],"communityLinks":[{"text":"Сообщество","link":""},{"text":"Лучшие дизайнеры","link":"/designers"},{"text":"Челленджи","link":"/challenges"},{"text":"Форум","link":"/forum"}],"companyLinks":[{"text":"Компания","link":"/discover/about-us"},{"text":"Блог","link":"/blog"},{"text":"Вакансии","link":"/pages/careers"},{"text":"Центр поддержки","link":"https://help.cgtrader.com"}],"sellLinks":[{"text":"Продать 3D-модели","link":"/discover/sell-3d-models"},{"text":"Увеличьте продажи","link":"/discover/sell-more"},{"text":"Обзор рынка 3D","link":"/discover/3d-market-trends"},{"text":"Топ поисков 3D-моделей","link":"/discover/3d-models-top-searches"},{"text":"Топ поисков для 3D-печати","link":"/discover/3d-print-models-top-searches"}]},"subscribe":{"button-active":"Подписаться","button-disabled":"Подписка оформлена","header":"Подписаться на новости:","placeholder":"Введите адрес электронной почты","terms-link":"Условия пользования","text":"Подписавшись, вы подтверждаете, что прочитали и принимаете наши"}},"general":{"description":"CGTrader представляет собой интернет-рынок, где 3D-художники и дизайнеры могут купить, продать или поделиться 3д-модели. Он был основан и разрабатывается командой 3D-дизайнеров и инженеров. Наша цель состоит в том, чтобы демократизировать 3D фондовый рынок и изменить способ, которым мы продавать 3д модели онлайн. Позволяя авторы получают максимальные гонорары, мы стремимся повысить их мотивацию и повысить качество 3д-модели.","navigation":{"buy_3d_models":"Купить 3д модели","sell_3d_models":"Продать 3д модели"},"search_placeholder":"Поиск среди более чем 2 миллионов 3D-моделей","site-search__field":"Поиск среди %{model_count} 3D-моделей","site_title":"3д модели магазина","site_title_postfix":" | CGTrader"},"generator":{"landing_pages":{"three_d_model":{"defaults":{"closing_cta":{"heading":"Пусть ИИ воплотит ваши мечты","subtitle":"Превратите подсказку или референсное изображение в готовую к производству 3D-модель, затем доработайте, перетекстурируйте и экспортируйте её в нужном формате."},"faq":{"heading":"Часто задаваемые вопросы","items":{"commercial":{"answer":"Коммерческое использование вашей 3D-модели %{object} доступно на платном плане CGTrader AI. Ознакомьтесь с Условиями использования CGTrader для получения информации о лицензионных условиях для ИИ-сгенерированных моделей.","question":"Можно ли использовать сгенерированную модель %{object} в коммерческих целях?"},"formats":{"answer":"3D-модели %{object} доступны в форматах GLB, FBX, OBJ, STL или USDZ.","question":"В каком формате файла я получу модель %{object}?"},"no_skills":{"answer":"Знания 3D-моделирования не требуются. Вся работа выполняется через генератор моделей %{object}. Система самостоятельно обрабатывает геометрию, пропорции и структуру по вашему запросу или загруженному изображению. Даже новички могут создать полноценную 3D-модель %{object} за несколько минут.","question":"Нужны ли знания 3D-моделирования для использования генератора моделей %{object}?"},"what_is":{"answer":"Генератор 3D-моделей CGTrader %{object}, основанный на ИИ, создаёт трёхмерную модель %{object} из текстовых инструкций или референсных изображений. Он анализирует вашу идею и создаёт загружаемую модель для дизайна, прототипирования и визуализации. Наши платные подписки предлагают более высокие ежемесячные лимиты.","question":"Что такое генератор 3D-моделей %{object}?"},"why_cgtrader":{"answer":"CGTrader обеспечивает плавный опыт работы, быструю обработку и высококачественные 3D-результаты. Этот инструмент подходит как для профессионалов, так и для начинающих, которые ищут надёжное решение для создания 3D-моделей %{object}.","question":"Почему стоит использовать ИИ-генератор %{object} от CGTrader?"}}},"h1":"ИИ-генератор 3D-моделей %{object}","hero_alt":"ИИ-генератор 3D-моделей %{object}","hero_height":617,"hero_width":1080,"how_to":{"heading":"Генерация %{object} из изображений или текстовых подсказок","paragraphs":["Создать трёхмерную модель %{object} очень просто. На левой панели в верхнем левом углу нажмите кнопку, чтобы загрузить изображение %{object} и создать 3D-модель. Также можно ввести подсказку в поле «Опишите, что вы хотите создать» внизу. CGTrader AI автоматически уточняет вашу подсказку, поэтому достаточно краткого описания.","Правая панель даёт широкие возможности управления 3D-моделью %{object}. Выберите параметры: тип сетки, материал, управление подсказками и формат файла. Можно также настроить текстуру модели %{object}. После ввода текстовых инструкций или изображения нажмите «Генерировать». Инструмент обработает ваш запрос за несколько минут в зависимости от нагрузки системы, материала, типа сетки, форм, значений seed и качества."],"steps":["Введите текстовое описание и/или прикрепите референсное изображение для повышения точности вашей 3D-модели %{object}.","Выберите один из наших пресетов или выберите движок ИИ, задайте количество полигонов и выберите тип материала для 3D-модели %{object}.","Нажмите «Генерировать», чтобы создать 3D-модель %{object} по вашей подсказке и/или референсному изображению.","Скачайте созданную 3D-модель %{object} в различных форматах (GLB, FBX, OBJ, STL или USDZ)."]},"intro":"Ищете простой и быстрый способ создать высококачественную 3D-модель %{object} за несколько минут? Наш передовой генератор 3D-моделей %{object}, CGTrader AI, преобразует текстовые команды или референсные изображения в полностью готовые модели. Он работает на ведущих движках генерации 3D: Meshy, Hunyuan и Tripo3D. Это помогает быстро создавать 3D-модели как начинающим, так и опытным специалистам в области дизайна для визуализации, экспериментов и прототипирования. Поддерживается преобразование текста в 3D и изображения в 3D, совместимо с 3D-принтерами. Загрузите изображение в формате JPG или PNG или введите описание текстом — CGTrader AI сделает всё остальное.","meta_description":"ИИ-генератор 3D-моделей %{object} от CGTrader ☑️ Создавайте детализированные 3D-модели %{object} по тексту или изображению. Начните бесплатно.","primary_cta":"Начать генерацию бесплатно","title":"ИИ-генератор 3D-моделей %{object} (бесплатно) | CGTrader"}}},"rules":{"errors":{"front_view_required":"Требуется изображение вида спереди."}}},"helpers":{"select":{"prompt":"Выберите: "},"submit":{"create":"Создать %{model}","submit":"Сохранить %{model}","update":"Сохранить %{model}"}},"home":{"index":{"3D-projects":{"info-section__cta":"Учить больше","info-section__description":"Там, где люди с захватывающими 3д проектами отвечают талантливым 3д дизайнер","info-section__heading_html":"\u003cstrong\u003e 3д проекты \u003c/strong\u003e Фриланс Хаб"},"categories-conatainer":{"browse_all":"Просмотреть все модели","browse_all_tooltip":"Просмотреть все 3д модели","discounts":"Скидки"},"cgdream":{"cta":"Начните бесплатно!","heading_html":"\u003cstrong\u003eИсследуйте CGDream,\u003c/strong\u003e\u003c/br\u003e где ИИ встречается с 3D-моделями.","text_html":"\u003cstrong\u003e CGDream \u003c/strong\u003e-это передовое приложение, которое использует мощность генеративных моделей ИИ и 3D для создания потрясающих 2D-изображений. \u003cstrong\u003e ваше творческое путешествие начинается сейчас. \u003c/strong\u003e"},"community":{"info-section__cta":"Присоединиться к сообществу","info-section__heading_html":"Регистрация CGTrader \u003cstrong\u003e Основной \u003c/strong\u003e","info-section__icon-list_1":{"de
16518scription":"Витрина ваш лучший делает, получить обратную связь и признание","heading":"галереи"},"info-section__icon-list_2":{"description":"Учите новые навыки и научить других","heading":"Учебники"},"info-section__icon-list_3":{"description":"Найти учебные пособия и обсудить другие связанные с 3д вопросов","heading":"Форум"}},"covid":{"timer-strip__item":"Бесплатные 3д ресурсы печати для COVID-19 Защита","timer-strip__item--button":"Просмотр 3д модели"},"enterprise":{"info-section__cta":"Учить больше","info-section__heading_html":"3д Услуги для \u003cstrong\u003e Предприятие \u003c/strong\u003e","info-section__icon-list_1":"Мы можем сделать реализацию AR для вас","info-section__icon-list_2":"Больше конверсий продаж, меньше продукт возвращается","info-section__icon-list_3":"Качество обеспечивается строгим QA"},"header":"3д модели для VR / AR, 3д печати и компьютерной графики","marketplace":{"info-section__description":"Выберите из %{models_count} 3д модели для приложений, таких как игровые, AR / VR, рекламы, анимации, фильмов и 3д печати","info-section__heading_html":"3д модель \u003cstrong\u003e Торговая площадка \u003c/strong\u003e","info-section__image-list_1":{"description":"Купить профессиональные 3д модели для вашего проекта","heading":"3д модели"},"info-section__image-list_2":{"description":"Низкополигональных PBR 3д Модель готова к видеоиграм, VR \u0026 AR приложения в режиме реального времени","heading":"Низкополигональных PBR 3д модели"},"info-section__image-list_3":{"description":"Найти 3д печать Дизайн для 3д принтеров","heading":"3д печать моделей"},"view_all":"Просмотреть все"},"search_dropdown":{"ai_cards":{"image_to_3d":{"description":"Превратите изображение в 3D-модель","title":"Изображение в 3D"},"text_to_3d":{"description":"Опишите свою идею, чтобы создать 3D-модель","title":"Текст в 3D"}},"ai_section_title":"Создать с помощью ИИ","aria_label":"Поисковые подсказки","columns":{"cg":{"search_label":"Искать в 3D-моделях","title":"3D-модели"},"print":{"search_label":"Искать в 3D-печати","title":"Модели для 3D-печати"}}},"sell":{"info-section__heading_html":"\u003cstrong\u003e Продам \u003c/strong\u003e 3д модели и проекты Найти Внештатный","info-section__icon-list_1":{"description":"Продать 3д модели и заработать до 85% роялти","link":"Продать 3д модели"},"info-section__icon-list_2":{"description":"Работа с корпоративными клиентами","link":"Получить Квалифицированные"},"info-section__icon-list_3":{"description":"Произведите свои навыки и получить до 95% роялти","link":"Просмотр 3д моделирование Работа"},"info-section__icon-list_4":{"description":"Создайте свой портфель и быть видимым","link":"Просмотр 3д Конструкторы"}},"seo":{"description":"Исследуйте более 2 миллионов 3D моделей онлайн для проектов печати, игр, VR/AR и дизайна. Скачивайте, настраивайте или нанимайте 3D экспертов - всё на CGTrader.","title":"CGTrader - 3D-модели и маркетплейс 3D-печати"}}},"homepage":{"budget_counter":{"range_0_10":"До $10","range_10_20":"$10 - $20","range_20_15000":"Более $20"},"quotes":{"quote_0":{"author":{"name":"Хуан Карлос Хименес","username":"CGShape"},"text":"Понизить комиссии, принятые от продавцов означает более высокие цены для покупателей. Проверьте и сравните цены на CGTrader и другие магазины."},"quote_1":{"author":{"name":"Хуан Карлос Хименес","username":"CGShape"},"text":"Наиболее яркое преимущество CGTrader плоско плата система активации, которая дает нам высокую отдачу. Вы не берете комиссию процент наших продаж. После продажи, мы получаем 100% от стоимости. Только если мы хотим продолжать продавать этот продукт, мы можем повторно активировать его еще раз, заплатив $ 5. Элегантное решение."},"quote_10":{"author":{"name":"ThreeD Ken","username":"3DKen"},"text":"Плоская система активации платы гениальна. Мне было интересно, почему никто не подумал об этом раньше. После продажи, заплатить небольшую плату, чтобы активировать продукт и это все. Продавец находится в полном контроле."},"quote_11":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"Есть две вещи, которые мне нравятся больше всего о CGTrader. Во-первых нет, там нет скрытый смысл что-либо здесь. Вам не нужно быть осторожным с CGTrader. Во-вторых, этот магазин художниками и для художников. Они понимают вас лучше. Что-то беспокоит вас здесь? ... пусть они знают, и они будут убедиться, что вы чувствовали себя здесь комфортно."},"quote_12":{"author":{"name":"Sid Naique","username":"sidnaique"},"text":"Мини-плата система активации является лучшим! (По крайней мере, я не встречал ничего лучше, чем это). Его очень реалистичный и справедливый по отношению к продавцам. Они не заботятся, сколько прибыли вы делаете, просто платите фиксированную арендную плату. НО если вы не сделаете какие-либо продажи, вы не должны платить арендную плату либо. Ярмарка CG торговли здесь!"},"quote_13":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"CGTrader привлекает клиентов даже из развивающихся стран, поскольку цены дешевле, чем в других магазинах. В то же время авторы могут заработать больше, потому что нет такой вещи, как высокие ставки комиссионного вознаграждения. Это правильный путь, чтобы оценить работу 3D художников."},"quote_14":{"author":{"name":"Leandro Salerno","username":"leansaler"},"text":"Интуитивный интерфейс издателя и инновационных инструментов, такие как рекомендации tagов и гибкие описания формы делает процесс публикации легко и быстро."},"quote_15":{"author":{"name":"Хавьер Almazor","username":"jvr3D"},"text":"Подход CGTrader о продаже 3D отличается, и не имеет ничего общего с другими магазинами, которые я использовал, чтобы продать. Обратите низкую плату в размере 5 $ после продажи это отличная идея, и позвольте мне предложить самые низкие цены здесь. Заказ
16518чик и исполнитель приносят пользу этим способом работы."},"quote_16":{"author":{"name":"Хавьер Almazor","username":"jvr3D"},"text":"Система загрузки проста и понятна. Это хорошо, чтобы знать, что за этим проектом люди с многолетним опытом в вопросах компьютерной графики. В любое время у меня была проблема, команда CGTrader фиксированной быстро."},"quote_17":{"author":{"name":"Хавьер Almazor","username":"jvr3D"},"text":"CGTrader позволяет мне связать свой профиль с моей личной веб-страницы. Клиенты могут связаться со мной, если они не уверены в характеристике предмета они хотят купить или просто хотите его модификацию. Это как иметь персональный 3D-магазин в большом рынке!"},"quote_2":{"author":{"name":"Хуан Карлос Хименес","username":"CGShape"},"text":"Мы пользовались большой и отзывчивой поддержкой со стороны команды CGTrader. Персонал любезно предложил сделать публикацию всех моделей для нас, который является большим подспорьем для профессиональных продавцов."},"quote_3":{"author":{"name":"Хуан Карлос Хименес","username":"CGShape"},"text":"На этой платформе мы можем предложить гораздо более выгодные цены для наших клиентов. Например, все наши продукты по цене на 20% ниже, чем где-либо еще, и мы по-прежнему получать больше прибыли по сравнению с другими магазинами!"},"quote_4":{"author":{"name":"Хесус Педроса Vidal","username":"NoneCG"},"text":"Это ЛУЧШИЙ на сегодняшний день система. Все другие интернет-магазины 3D взять% от нашей прибыли. Это была особенность, которая привлекла наше внимание в CGTrader с самого начала. Клиенты, покупка на CGTrader, знает, что большая часть их деньги идут к автору, который в конце концов будет делать больше и лучше модели, если их прибыль улучшить."},"quote_5":{"author":{"name":"Хесус Педроса Vidal","username":"NoneCG"},"text":"CGTrader является интернет-магазин 3D, который сделал самый быстрый рост продаж и прибыли для нас. Система активации плоско-плата является лучшей вы можете найти, как это вознаграждает создатель контента, а не посредники."},"quote_6":{"author":{"name":"Ričardas Marozas","username":"рабочий насосная станция"},"text":"Когда я получил $ 160 на мой счет Paypal, не знал, что происходит, как это никогда не случалось раньше, но потом я подумал, что это может быть продажа CGTrader и я был прав. Продан тот же пакет на Turbosquid в прошлом месяце за $ 189, но после того, как 40% ставка роялти и 30% налога, все, что я получил $ 52,92 - гроши по сравнению с $ 160 от CGTrader. Плюс клиент заплатил $ 29 меньше - теперь это правильное дело."},"quote_7":{"author":{"name":"ThreeD Ken","username":"3DKen"},"text":"Сначала я скептически относился к такой хорошей сделки для продавцов, которая не явÐ
16518»ÑÐµÑ‚ся нормой для крупных интернет-магазинов 3D. Когда я узнал о системе активации плоско-плате, я был поражен таким творческим способом продажи цифровых активов. Продажа, затем активировать, заплатив взнос в размере $ 5. Все это имеет смысл для меня. Я загрузил несколько, чтобы попробовать и действительно, когда я получил свою первую продажу, это 100% роялти!"},"quote_8":{"author":{"name":"ThreeD Ken","username":"3DKen"},"text":"Я так привык многие другие сайты, заряд от 30% - 60% комиссии, пока я не обнаружил CGTrader. Как продавец, я определенно как высокий процент отчислений прибыли."},"quote_9":{"author":{"name":"ThreeD Ken","username":"3DKen"},"text":"Опыт продавец является фантастическим. Представление продукта является ветер. И да, система позволила мне сохранить черновик моего представления продукта на каждом этапе пути."}}},"hyperwallet":{"failed_payment_agreement":"Произошла непредвиденная ошибка","success_notification":"Платёжное соглашение было успешно обновлено.","success_payment_agreement":"Платёжное соглашение было успешно отправлено."},"i18n":{"plural":{"rule":{}},"transliterate":{"rule":{}}},"item-search-filters":{"3d-models-url":"3d-modeli","3d-print-models-url":"3d-pechat-modeli","dedicated-search-page-url":{"animated":"/animirovannaya-","free":"/besplatnye-","low_poly":"/nizkopoligonalnyy-","pbr":"/pbr-","rigged":"/sfalsifitsirovano-"}},"item_use_case":{"not_valid":"Недопустимый файл","pundit":{"not_authorized":"У вас нет прав для выполнения этого действия."},"title":"Случай использования для %{item_name}"},"items":{"already_purchased":"Вы уже приобрели этот товар.","filters":{"attributes":"Атрибуты","free":"Бесплатно","max_value":"$ 500 +","min_value":"$ 2","reset":"Сбросить фильтры"},"footer":{"next":"Следующая","per_page":"Количество на странице:","previous":"Предыдущая"},"geometry_type_forbidden":"Geometry type forbidden!","last_card":{"button_text":"Заказать пользовательскую 3д модель","text":"Не удалось найти то, что вы ищете?"},"migrations":{"errors":{"invalid_owner":"Новый владелец не может принять предметы.","unpublished_items":"Все предметы должны быть опубликованы."}},"new_price_applied":"Новые цены были успешно применены ко всем товарам.","new_price_applied_to_selected_items":"Новые цены были успешно применены к выбранным товарам.","new_prices_to_filtered_models":"Новые цены были успешно применены ко всем отфильтрованным товарам.","order_processed":"Заказ обработан.","order_processing":"Заказ обрабатывается.","something_went_wrong":"Что-то пошло не так","title_error":"Title should not contain non-latin symbols","uniq_user":"уже отправил вариант использования для этого товара","upload_error":"Ошибка загрузки.","validate_license_for_zero_price":"is not valid for free item","validate_minimum_price":"Предложение по цене не должно быть ниже минимальных 50% от исходной цены","verify_printable_formats_error":"Пожалуйста, загрузите хотя бы один файл в формате .stl, .obj или .wrl.","you_can_not_delete_models":"Вы не можете удалить модели"},"jobs":{"about":{"hire_designers_page":{"hero":{"description":"3D Projects упрощает найм квалифицированных 3D-фрилансеров и помогает решить все задачи моделирования.","heading_html":"Познакомьтесь с лучшей платформой для фрилансеров для ваших \u003cstrong\u003e3D-задач по моделированию\u003c/strong\u003e"},"sections":{"call_to_action":{"description":"Опубликуйте свой проект бесплатно и убедитесь, как легко решить следующую задачу моделирования с помощью 3D-проектов.","title":"Ð
16518Ð°Ð¹Ð¼Ð¸Ñ‚е лучших специалистов по 3D уже сегодня!"},"freelancer_selection":{"title":"Сделайте свой следующий 3D-проект успешным","widest_selection":{"description":"Не соглашайтесь на меньшее. Здесь работают 40 000 фриланс-3D-дизайнеров. CGTrader — крупнейшая платформа, которая помогает вам найти квалифицированных специалистов: моделлеров, аниматоров, риггеров, экспертов по 3D-печати и всех, кто между ними.","title":"Самый широкий выбор 3D-фрилансеров"}},"how_it_works":{"steps":{"describe":{"description":"Расскажите нам, что вам нужно. Используйте простую форму, добавьте изображения и опубликуйте своё фриланс-предложение за несколько минут.","title":"Опишите свой проект"},"pay":{"description":"Безопасно внесите предоплату в CGTrader и дайте знать фрилансеру, что вы готовы начать. Общайтесь, делитесь файлами и отслеживайте ход проекта в удобном рабочем пространстве. Переведите оплату только тогда, когда будете довольны итоговым результатом.","title":"Оплатите результат"},"select":{"bullets":["Получайте предложения от квалифицированных дизайнеров прямо на свою почту","Общайтесь с дизайнерами в реальном времени","Просматривайте их предыдущие проекты","Вы также можете искать, фильтровать и приглашать фрилансеров по их 3D-навыкам, рейтингу, а также по используемому ПО или форматам"],"title":"Выберите лучшего фрилансера"}},"title":"Выполните свою задачу в 3 простых шага"},"payment_quality":{"description":"CGTrader гарантирует, что услуги фрилансеров будут соответствовать вашим требованиям и будут выполнены в срок. Наша команда контроля качества всегда доступна, когда она вам нужна.","title":"Гарантия качества"},"secure_payments":{"description":"Наша эскроу-система гарантирует защиту ваших средств, и вы платите только тогда, когда довольны конечным результатом.","title":"Безопасные платежи"},"workspace_convenience":{"description":"Забудьте о неудобстве постоянного переключения между вкладками и разными инструментами. С помощью выделенного рабочего пространства вы сможете легко управлять всем в одном месте: несколькими проектами, счетами, перепиской, обменом файлами и многим другим.","title":"Удобство единого экрана"}},"shared":{"post_project_cta":"Опубликовать проект"}},"navigation":{"find_jobs_cta":"Найти 3D-вакансии","hire_freelancers_cta":"Наймите 3D-фрилансеров","how_it_works":"Как это работает","why_projects":"Почему 3D-проекты"}},"agreed_to_terms_of_use":"Вы должны согласиться с Условиями и положениями 3D Projects, чтобы продолжить.","budget_error_message":"слишком высок","check_designer_uniqueness_error":"Дизайнера можно пригласить только один раз","check_ownership_error":"Приглашающий не явÐ
16518»ÑÐµÑ‚ся владельцем проекта","check_status_error":"Дизайнеров можно приглашать только в одобренные проекты.","deadline_error_message":"Крайний срок должен быть в будущем","discount_code_error":"Извините, код скидки не существует или срок его действия истёк.","email_subject":"Возможность 3D проекта: %{subject}","error_check_application_uniqueness":"Вы можете подать заявку только один раз","error_check_applier":"Вы не можете подать заявку на свой собственный 3D проект","error_check_job_availability":"Пользователь не имеет права подавать заявку","error_check_job_status":"Подача заявок на проекты приостановлена до завершения нового процесса модерации, обычно в течение 3 рабочих дней. Спасибо за понимание!","modeling_request_failed":"Запрос на моделирование не выполнен!","offer_has_been_sent":"Спасибо, ваше предложение было отправлено!","offer_is_closed":"Предложение закрыто.","payment_successful":"Оплата успешно произведена.","payment_under_review":"Платёж находится на проверке.","share_with_yourself_error":"Нельзя делиться шаблонами «Нанять меня» с самим собой","system_messages":{"buyer_cancels_job":"Проект был отменён и перемещён во вкладку %{archive_url}.","buyer_edits_hire_me_template":"Детали проекта были обновлены клиентом.","buyer_edits_job_details":"Детали проекта были обновлены.","buyer_marks_job_as_finished":"Проект был отмечен как завершённый и перемещён во вкладку %{archive_url}.","buyer_marks_job_as_finished_with_resell":"Клиент отметил проект как завершённый и разрешил вам продавать результаты на CGTrader. Не забудьте \u003cb\u003e\u003ca href=\"/profile/upload/model\"\u003eзагрузить\u003c/a\u003e\u003c/b\u003e созданную модель!","buyer_negotiates":"Клиент обсудил ваше ценовое предложение.","buyer_pays_for_the_job":"Проект был предоплачен и перемещён во вкладку %{in_progress_url}.","designer_accepts_changes":"Дизайнер проверил обновлённые детали.","designer_accepts_offer":"Дизайнер принял ваше предложение.","designer_edits_offer":"Дизайнер отредактировал своё предложение.","designer_negotiates":"Дизайнер обсудил ваше предложение.","designer_withdraws_offer":"Дизайнер отозвал своё предложение.","job_expires":"Срок действия проекта истёк, и он был перемещён во вкладку %{archive_url}."},"template_error":"Проекты не были созданы!","user_not_exists":"Пользователь с таким именем не существует.","want_that_job":"Я хочу эту работу!"},"kodama":{"invalid_request":"Неверный запрос!"},"landing":{"consent_not_saved":"Согласие не было сохранено","failure_notification":"Все обязательные поля запроса на доступ к API должны быть заполнены корректно.","success_notification":"Спасибо за ваш запрос. Мы свяжемся с вами в ближайшее время."},"layout":{"chunk_load_notice":"При загрузке страницы произошла ошибка. Пожалуйста, обновите страницу.","cookies_policy":{"accept":"принимать","learn_more":"Выучить больше","text":"Наш сайт использует куки-файлы для сбора статистических данных для посетителей и взаимодействие дорожки с прямой маркетинговой коммуникацией / улучшить наш сайт и улучшить свой опыт просмотра. Пожалуйста, смотрите наши Cookie Примечание Для получения дополнительной информации о печенье, данные, которые они собирают, которые могут получить доступ к ним, и вашим правам."},"translations_notice":{"cta":"Переключиться на English","text":"Здравствуйте! Мы переводим CGTrader. Идет перевод, и некоторые разделы могут быть недоступны на русском языке."}},"model":{"already_purchased":"Вы уже купили этот продукт. Вы можете скачать его из \u003ca href='/profile/cart/purchased'\u003eкорзин
16518ы покупок\u003c/a\u003e. Если хотите, вы можете купить его снова.","animated":"Анимированный","artist_profile":"Профиль художника","author":"Автор","author_get_your_payment":"Автор получит 100% вашей оплаты. CGTrader позволяет художникам получать максимальный доход и мотивирует продавать высококачественный контент.","author_products":"Другие продукты от \u003ca href=\"%{author_url}\"\u003e%{username}\u003c/a\u003e","authorised_seller":"Авторизованный продавец CGTrader","authority":"Авторитет","breadcrumb":{"3d_models":"3D модели","home":"Главная"},"bulk_download":{"download_all":"Скачать все файлы","files_included":{"one":"Включён %{count} файл","other":"Включено файлов: %{count}"},"too_large":"Эта модель слишком велика для загрузки одним архивом (более %{limit}). Пожалуйста, скачайте файлы по отдельности."},"buy":"Купить","cannot_download":"Только пользователи с репутацией %{experience_needed} могут скачивать более %{models} бесплатных моделей. Узнайте больше об уровнях и репутации, \u003ca href='%{reputation_url}' target='_blank'\u003eнажав здесь\u003c/a\u003e.","collection":"3D-пак","contact_author":"Связаться с автором","description":"Описание","download":"Скачать","download_limit_exceeded":"Вы превысили свой дневной лимит загрузок. Попробуйте снова завтра. Этот лимит не распространяется на пользователей, совершивших покупку за последние 12 месяцев.","download_modal":{"chat_with_us":"Написать в чат","contact_support":"Пожалуйста, свяжитесь с нами через чат.","download_file":"Скачать %{name}","empty":"Файлы не найдены","generic_error":"Что-то пошло не так, попробуйте ещё раз","link_error":"Приносим извинения, но с вашей ссылкой для скачивания возникла небольшая проблема.","pbr_ready":"Поддержка PBR","preparing":"Подготовка загрузки...","title":"Скачать файлы"},"format_downloads":"Загрузки 3D-форматов","game_ready":"Готово для игр (lowpoly)","geometry":"Геометрия","license":"Лицензия","low_poly":"Низкополигональные","make_offer":"Сделать предложение","materials":"Материалы","messages":{"no_files":"Нет загруженных файлов","no_rating":"Рейтинг для этой модели пока отсутствует"},"model_details":"Детали 3D модели","pbr_textures":"PBR-текстуры","plugin_used":"Используемый плагин","polygons":"Полигоны","price":"Цена","print_ready":"Готово для 3D-печати","product_id":"Идентификатор продукта","product_support":"Поддержка продукта","product_type":"Тип продукта","published":"Опубликовано","rated":{"error":"Вы уже оценили эту модель","success":"Спасибо за вашу оценку этой модели"},"rating":"Рейтинг 3D модели","ratings":{"desc":{"negative":"Пользователи, купившие эту 3D модель, считают её ниже ожидаемого качества","positive":"Пользователи, купившие эту 3D модель, считают её качественной"},"negative":"отрицательный","positive":"положительный"},"recently_viewed_models":"Недавно просмотренные модели","renderer":"Рендерер","report":"Пожаловаться","rigged":"С риггингом","scanned":"Сканированные","scripts-plugins":"Скрипты и плагины","similar_models":"Похожие модели","statistics":"Статистика","subscription":{"limit_reached":"Вы достигли лимита загрузок за этот расчетный период.","limit_reached_detailed":"Вы достигли лимита в %{limit} загрузок в этом месяце. Ваш лимит обновится %{reset_date}. Чтобы скачать больше моделей сейчас, вы можете приобрести их по отдельности.","limit_reached_detailed_no_date":"Вы достигли лимита в %{limit} загрузок в этом месяце. Чтобы скачать больше моделей сейчас, вы можете приобрести их по отдельности."},"tags":"Теги","textures":"Текстуры","title":"%{item_title} 3D модель","unwrapped_uvs":"Развернутые UV","uv_mapping":"UV-развертка","vat_notice":"Все цены указаны без НДС","version":"Версия","vertices":"Вершины","vr":"VR модели"},"models":{"error":{"cannot_be_blank":" не может быть пустым."}},"navigation":{"3d-models":{"3d-printing":"Модели для 3д печати","discounts":"Скидки","find_designers":"Найти лучших дизайнеров","free":"Бесплатные 3д модели","pbr":"PBR 3д модели","scanned_3d-models":"Сканированные 3д модели","scripts_apps":"Скрипты и приложения","show-all":"Показать все","textures":"CG текстуры","title":"3д модели","vr_low-poly":"Низкополигональные 3д модели"},"already_added_message":"уже был добавлен в корзин
16518у","already_purchased_message":"Вы уже приобрели этот товар.","aria":{"account":"Аккаунт","cart":"Корзина","close_menu":"Закрыть меню","logo":"CGTrader - 3D-модели / 3D-дизайнеры","messages":"Сообщения","notifications":"Уведомления","open_menu":"Открыть меню","search":"Поиск","search_by_image":"Поиск по изображению"},"budgets":{"below_10":"До $10","collections":"Наборы","free":"Бесплатно","more_than_20":"Более $20","more_than_50":"Более $50","sale":"В продаже","subscription":"Подписка"},"cart_limit_reached":"Вы достигли предела в 500 товаров в вашей корзине. Чтобы продолжить покупки, пожалуйста, завершите текущий заказ и начните новый для любых дополнительных товаров.","cart_widget":{"continue_shopping":"Продолжить покупки","discount":"Скидка с корпоративным купоном:","discount_applied":"Примененные скидки:","final":"Окончательная цена:","have":"У вас","items":"наименований в вашей корзине.","left_to_unlock_discounts":"Осталось {{leftToUnlock}} чтобы разблокировать скидки","potential_discount":"Возможная скидка:","proceed":"Перейти к оформлению заказа","recently_viewed_empty_text":"Начните исследовать и ваши недавно просмотренные товары появятся здесь.","recently_viewed_empty_title":"Здесь пока ничего нет","recently_viewed_tab":"Недавно просмотренные","remainer":"Чтобы активировать CGTrader, добавьте: ","sale_discounts":"Примененные скидки: ","sale_time_left":"Осталось времени на распродажу","sale_tooltip":"Скидки от наших распродажных кампаний применяются только когда ваша корзина достигает ${{threshold}} или более.","sale_tooltip_text":"Этот товар на распродаже, но скидка будет применена только когда общая стоимость вашей корзины достигает ${{threshold}} или более.","text":"Ваша корзина пуста.","title":"Корзина покупателя","total":"Общее количество:","used":"Использовано кредитов:","view":"Посмотреть корзину","wishlist_empty_text":"Начните исследовать и сохраните свои любимые находки для будущего — они появятся здесь.","wishlist_empty_title":"Ваш список желаний пуст","wishlist_tab":"Список желаний"},"community":{"awards":"Награды дизайнеров 🏆","blog":"Блог","challenges":"Задачи","community_ratings":"Рейтинги сообщества","designers":"3D дизайнеры","digital_art":"Конкурс цифрового искусства","forum":"Форум","galleries":"Галереи","invite":"Пригласить друга","printlist":"Список 3д-печати","sell_3d-models":"Продать 3д модели","title":"Сообщество","tutorials":"Учебные пособия"},"conversations_widget":{"conversations":"Диалоги","messages":{"loading":"Загрузка входящих сообщений"},"types":{"cgtrader_support":"CGTrader Поддержка","file_conversion_request":"Запрос преобразования файлов","job_application":"3д проект","model_feedback":"Обратная связь по продукту","model_support":"Поддержка по модели","offer":"Предложение цены","personal":"Личное сообщение","promotions":"Акции"}},"create_ai":{"community_gallery":"Галерея сообщества","free_trial":"Бесплатный период","generate_3d_model":"Сгенерировать 3D-модель","generation":{"dismiss":"Закрыть уведомление","error_title":"Проблема генерации","generating_title":"Ваша 3D-модель генерируется","open_model":"Открыть модель","ready_title":"Ваша 3D-модель готова!"}},"create_with_ai":"Создать с ИИ","credits_guest_widget":{"create":"Создайте аккаунт сейчас!","discount":"скидка","for":"на вашу","get":"и получи","learn_more":"Узнайте больше о кредитах лояльности CGTrader","link":"здесь","purchase":"первую покупку!","sign-up":"Регистрируйся","text_below_1":"Кредиты могут быть использованы при покупке \"","text_below_2":"\"отмеченных моделей и покрывать до 30% скидки от цены."},"credits_user_widget":{"condition_1":"Баллы CGTrader можно использовать для покупки моделей, отмеченных значком \u003ci class=\"custom-font-icon custom-font-icon--small icon-wallet\"\u003e\u003c/i\u003e, предоставляя скидку до 30% от их цены.","condition_2":"Баллы можно использовать только если общая стоимость вашей корзин
16518ы составляет \u003cstrong\u003e$%{minimum}\u003c/strong\u003e или больше.","condition_3":"Во время активных распродаж или акций баллы использовать нельзя.","condition_4":"Имейте в виду, что у баллов есть срок действия - ваши баллы истекут через \u003cstrong\u003e%{days}\u003c/strong\u003e дней.","earn_rate":"Количество заработанных кредитов:","first_text_1":"Считайте это подарком за вашу первую покупку - вы можете использовать кредиты при покупке \"","first_text_2":"\"отмеченных моделей и получить до 30% скидки от цены. Кредиты могут быть использованы только, если общая стоимость вашей корзины равна или больше, чем","important":"ВАЖНО:","learn_more":"Узнать больше","lifetime_spending":"Долгосрочные расходы:","list_item":"Увеличивая ваши долгосрочные расходы на CGTrader, ваша кредитная ставка также увеличивается.","message":"Кредиты не могут быть использованы во время распродажи, но вы можете повысить свой уровень лицензии и собирать больше кредитов для вашей следующей покупки.","more_credits":"Как я могу получить больше кредитов?","next_level":"Следующий уровень:","notice":"ВНИМАНИЕ: Баллы CGTrader нельзя использовать во время текущей распродажи, но они снова станут доступны после её завершения. А пока посмотрите отличные предложения на 3D-модели \u003ca href=\"https://www.cgtrader.com/3d-models?sale_off=1\"\u003eздесь\u003c/a\u003e.","par_1":"У вас","par_2":"CGTrader кредитов.","par_2_variation":" в баллах, которые вы можете потратить, если распродажа невозможна.","regular_text_1":"Кредиты могут быть использованы при покупке \"","regular_text_2":"\"отмеченных моделей и покрывать до 30% скидки от цены.","title":"Программа лояльности CGTrader"},"enterprise":{"management":"3D управление активами","title":"Производство 3D-контента"},"hire_a_freelancer":"Нанять фрилансера","item_add_message":"успешно добавлен в корзину","item_restricted_message":"Этот товар нельзя купить из-за санкций, наложенных на страну дизайнера (RU/BY).","navbar":{"actions":{"new":"Новые","top_selling":"Лидеры продаж","trending":"В тренде"},"budgets":{"below_10":"До $10","free":"Бесплатно","more_than_20":"Более $20","more_than_50":"Более $50","sale":"В продаже","up_to_10":"До $10"}},"projects":{"3d_customs":"Индивидуальное 3D","3d_printing":"3D печать","about":"О 3д проектах","browse":"Посмотреть проекты","buyer_perspective":"Нанять 3д дизайнеров","for_designers":"Для дизайнеров","my_projects":"Мои проекты","seller_perspective":"Фриланс 3д проекты","start":"Начать проект","work":"Работа с Cgtrader"},"sell_3d-models":"Продать 3д модели","sellers_hub":"Центр продавца","subscription_includes_item_message":"Этот товар уже включен в вашу подписку.","tooltips":{"cart":"Корзина покупателя","conversations":"Мои сообщения","discounts":"Программа лояльности CGTrader","notifications":"Уведомления","publisher":"Издатель"},"user":{"ban_notification":"Эта учетная запись заблокирована. Пожалуйста, свяжитесь с нашей поддержкой для разъяснений.","cannot_refer_yourself":"нельзя пригласить самого себя","email_confirmation":"Ваш адрес электронной почты был подтверждён.","email_reconfirmation":"уже был подтверждён, пожалуйста, попробуйте войти","ends_at":"Окончание: %{date}","handle_error_tfa_code_missmatch":"Введённый вами код безопасности неверен. Пожалуйста, попробуйте снова.","handle_error_tfa_code_missmatch_last":"Срок действия вашего кода безопасности истёк. Чтобы продолжить, нажмите «Отправить письмо повторно» и введите новый полученный код.","handle_error_tfa_default":"Что-то пошло Ð
16518½Ðµ так, пожалуйста, попробуйте позже.","handle_error_tfa_expired":"Срок действия вашего кода безопасности истёк.","hundred_percent_royalties_popover":"Вы участвуете во временной кампании 100% заработка: все доходы от продаж новых моделей будут идти вам до окончания кампании","invalid_email_message":"Пожалуйста, введите действительный адрес электронной почты, чтобы продолжить.","login":"Авторизоваться","meta_description":"Посмотрите профиль 3D дизайнера %{username} на CGTrader, его портфолио и доступные 3D модели. Ознакомьтесь с лучшими 3D моделями от ведущих художников индустрии.","meta_title_suffix":"3D дизайнер","not_valid_birthday":"День рождения недействителен.","offline":"Не в сети","online":"В сети","page_title":"Профиль дизайнера","rate_models_limit":"Сегодня вы больше не можете оценивать модели.","semantic_description":"3D модели, работы и портфолио дизайнера. Ознакомьтесь с его творчеством на CGTrader","semantic_title":"Профиль дизайнера","session_failure":"Указаны неверное имя пользователя/электронная почта или пароль","session_password_expired":"Ваш пароль истёк из-за бездействия. Пожалуйста, сбросьте пароль через ссылку, отправленную вам по электронной почте.","session_two_fa_failure":"Ошибка проверки кода","session_unconfirmed":"Ваш адрес электронной почты ещё не подтверждён.","token_not_recognized":"Токен не был распознан","twitter_shareable_text":"#3dartist, за которым стоит следить","username_error":"не должно содержать пробелов или нелатинских символов"},"user_menu":{"buyer":{"freelancers":"Найти фрилансеров","purchases":"Мои покупки","sell":"Продать 3д модели","wishlist":"Список желаний 3д моделей"},"dashboard":"Панель администратора","logout":"Выйти","public_profile":"Публичный профиль","seller":{"analytics":"Аналитика","find_new_projects":"Найти новые 3D проекты","freelance":"Фриланс 3д проекты","freelance_cgtrader":"Фриланс с CGTrader","models":"Мои модели","publish_project":"Опубликовать проект","publishing":"Издательский","sales":"Мои продажи","subscription":"Подписка","wishlist":"Список желаний 3д моделей"},"settings":"Настройки"}},"number":{"currency":{"format":{"delimiter":" ","format":"%n %u","negative_format":"-%n %u","precision":2,"separator":",","significant":false,"strip_insignificant_zeros":false,"unit":"руб."}},"format":{"delimiter":" ","precision":3,"round_mode":"default","separator":",","significant":false,"strip_insignificant_zeros":false},"human":{"decimal_units":{"format":"%n %u","units":{"billion":{"few":"миллиардов","many":"миллиардов","one":"миллиард","other":"миллиардов"},"million":{"few":"миллионов","many":"миллионов","one":"миллион","other":"миллионов"},"quadrillion":{"few":"квадриллионов","many":"квадриллионов","one":"квадриллион","other":"квадриллионов"},"thousand":{"few":"тысяч","many":"тысяч","one":"тысяча","other":"тысяч"},"trillion":{"few":"триллионов","many":"триллионов","one":"триллион","other":"триллионов"},"unit":""}},"format":{"delimiter":"","precision":1,"significant":false,"strip_insignificant_zeros":false},"storage_units":{"format":"%n %u","units":{"byte":{"few":"байта","many":"байт","one":"байт","other":"байта"},"eb":"ЭБ","gb":"ГБ","kb":"КБ","mb":"МБ","pb":"ПБ","tb":"ТБ"}}},"percentage":{"format":{"delimiter":"","format":"%n%"}},"precision":{"format":{"delimiter":""}}}
16518,"owly":{"failed_set_pending":"Не удалось установить статус «в ожидании».","failed_to_approve":"Не удалось одобрить.","failed_to_suspend":"Не удалось приостановить.","failed_to_update_license":"Не удалось обновить лицензию.","failed_to_update_status":"Не удалось обновить статус одобрения."},"print_models":{"browse_all_makes":"Смотреть все примеры 3D-печати","browse_all_models":"Смотреть все модели","description":"Откройте для себя популярные модели, готовые к 3D-печати: игровые, художественные, для дома и многое другое.","makes_description":"Изучите последние примеры 3D-печати, загруженные сообществом - реальные напечатанные образцы, показывающие, как 3D-модели выглядят в физическом мире.","makes_title":"Новые примеры 3D-печати","new_description":"Ознакомьтесь с новыми моделями в нашей коллекции — свежие дизайны, готовые к загрузке и печати для вашего следующего проекта.","new_title":"Новые модели для 3D-печати","title":"Популярные модели для 3D-печати"},"profile":{"account_settings":{"account":{"email":"Электронная почта","submit":"Сохранить","title":"Профиль","update":{"updated":"Профиль обновлен"},"username":"Имя пользователя"},"email_settings":{"descriptions":{"ai_studio_notifications":"Уведомления о ваших генерациях CGTrader AI, в том числе когда готова модель, которую вы ещё не открывали","communication":"Информация о вашем общении с другими пользователями CGTrader, включая личные сообщения, новые предложения по ценам и индивидуальным заказам, отзывы и запросы на поддержку продуктов","news":"Информация о продуктах CGTrader, объявления о новых функциях и новости сообщества","onboarding_and_edu":"Информация о процессе введения и советы, которые помогут вам максимально эффективно использовать CGTrader","push_notifications":"Уведомления о важной активности и обновлениях аккаунта","sales_and_offers":"Информация о специальных предложениях, распродажах 3D-моделей и бонусных кредитах CGTrader","seller_sold_item":"Информация о проданных вами 3D-моделях и полученных платежах"},"privacy_policy_html":"Пожалуйста, ознакомьтесь с нашей \u003ca href='https://www.cgtrader.com/pages/privacy-policy' 'target='_blank'\u003eПолитикой конфиденциальности\u003c/a\u003e для получения дополнительной информации. ","titles":{"ai_studio_notifications":"Уведомления CGTrader AI","communication":"Общение (Личные сообщения, отзывы о 3D-моделях, активность сообщества, предложения)","news":"Новости 3D / обновления CGTrader","onboarding_and_edu":"Введение и обучение","push_notifications":"Уведомления о важной активности и обновлениях аккаунта","sales_and_offers":"Продажи и специальные предложения","seller_sold_item":"Информация о продажах и платежах"},"tracking_attributes":{"ai_studio_notifications":"Уведомления CGTrader AI","communication":"Общение","news":"Новости 3D / обновления CGTrader","onboarding_and_edu":"Введение и обучение","sales_and_offers":"Продажи и специальные предложения","seller_sold_item":"Информация о продажах и платежах"},"update":{"updated":"Ваша электронная почта была успешно обновлена."}},"gdpr_success_notification":{"opt_in":"Спасибо! Вы успешно подписались на наши электронные письма.","opt_out":"Вы успешно отписались от наших новостных рассылок. Вы можете изменить это предпочтение в любое время, посетив настройки вашего профиля.\n"},"location_tooltip_text":"\u003cdiv\u003eЭтот флажок позволяет отображать местоположение на странице профиля.\u003c/div\u003e \u003cdiv\u003eОбратите внимание, что в любом случае, по причинам соблюдения требований, ваше местоположение будет отображаться вашим покупателям (если вы продавец) и продавцам, у которых вы покупали.\u003c/div\u003e","moneybookers":{"account":"Аккаунт Skrill"},"password":{"current_password":"Текущий пароль","new_password":"Новый пароль","submit":"Изменить пароль","title":"Пароль","updated":"Ваш пароль был успешно обновлен. Теперь вы можете использовать новый пароль для входа.","verify_password":"Подтвердите пароль"},"paypal":{"account":"Аккаунт PayPal","submit":"Сохранить","title":"Ð
16518Ð°ÑÑ‚ройки PayPal","update":{"updated":"Настройки PayPal были обновлены"}},"personal_info":{"areas_of_expertise":"Области экспертизы","country":"Страна","description":"Несколько предложений о себе","full_name":"Полное имя","languages":"Языки","preffered_tools":"Предпочитаемые инструменты","submit":"Сохранить","title":"Личная и профессиональная информация","update":{"updated":"Ваша информация была успешно обновлена"},"website":"Веб-сайт"},"personalization":{"no_muted_title":"Нет заблокированных дизайнеров","title":"Заблокированные дизайнеры","tooltip":"3D-модели перечисленных дизайнеров исключены из ваших результатов поиска.","unmute_designer":{"confirm":"Вы уверены?"}},"professional_info":{"allow_private_job_requests":"Покупатели смогут отправлять вам индивидуальные запросы на 3D-работы.","buyer_engagement_program_html":"Привлекайте больше клиентов, позволяя им покупать ваши модели с помощью CGTrader Points. \u003ca href='https://www.cgtrader.com/pages/cgtrader-loyalty-discounts-explained' target='_blank'\u003eПодробнее\u003c/a\u003e\n","pbr_program":"Более низкая ставка заработка применяется только при загрузке преобразованного файла","pbr_program_agreement":"Вы согласились участвовать в программе PBR на 24 месяца с %{date}. Подробнее см. Условия и Положения. Если у вас есть вопросы, свяжитесь с нашей поддержкой.","product_analytics_participation_html":"Я участвую в \u003ca href='https://www.cgtrader.com/profile/analytics'target='_blank'\u003eАналитике\u003c/a\u003e и соглашаюсь с их Условиями и Положениями\n","product_analytics_participation_tooltip_html":"Вы согласились участвовать в Аналитике и приняли Условия и Положения Аналитики (\u003ca href='https://www.cgtrader.com/pages/cgtrader-analytics-terms-of-service'target='_blank'\u003eссылка\u003c/a\u003e) %{accepted_at}. Если вы хотите прекратить участие, пожалуйста, свяжитесь с поддержкой CGTrader\n"},"security":{"allow_mfa":"Включите двухэтапную проверку для защиты своего аккаунта","use_authenticator_app":"Использовать приложение-аутентификатор для двухэтапной проверки","use_authenticator_app_tooltip":"Отсканируйте QR-код с помощью вашего приложения-аутентификатора (Google Authenticator / Authy) для генерации кодов проверки.","use_email":"Использовать электронную почту для двухэтапной проверки"},"sidebar":{"general_settigns":"Общие настройки","member_since":"Участник с","paypal":"PayPal","title":"Информация об аккаунте"},"title":"Мой аккаунт"},"conversations":{"conversations":"Диалоги","messages":{"loading":"Загрузка Входящие"},"types":{"cgtrader_support":"CGTrader Поддержка","file_conversion_request":"запрос преобразования файлов","job_application":"3D проект","model_feedback":"Продукт Обратная связь","model_support":"Модель поддержки","offer":"Предложение цены","personal":"Личное сообщение","promotions":"Акции"}},"meta":{"description":"Ознакомьтесь с высококачественными 3D‑моделями от %{username} на CGTrader. Просматривайте уникальные ресурсы для игр, VR/AR и визуализации.","title":"Создатель 3D‑моделей: %{username}"},"my_products":{"suspension_appeal":{"reasons":{"not_available":"N/A"}}},"settings_updated":"Ваши настройки были успешно обновлены."},"publishing":{"banned_error":"Ваш аккаунт заблокирован за публикацию спам-контента. Если вы считаете, что это ошибка, пожалуйста, свяжитесь с supp
16518[email protected].","files_not_finalized":"Один или несколько файлов не были загружены полностью и не могут быть опубликованы. Удалите их и загрузите снова перед публикацией.","spam_suspect":"Ваш аккаунт не авторизован для публикации этой модели. Если вы считаете, что это ошибка, пожалуйста, свяжитесь с [email protected]."},"rating":{"anonymous_buyer":"Анонимный Покупатель","errors":{"not_verified_buyer":"Только проверенные покупатели могут оставить отзыв"}},"routes":{"3d-models":"3d-модели","3d-print-models":"3d-печать-модели","animated-3d-models":"анимированная-3d-модели","controllers":{"three_d_studio":{"3d-models":"3d-models","images":"images"}},"discover":"otkrytie","free-3d-models":"бесплатные-3d-модели","free-3d-print-models":"бесплатные-3d-печать-модели","freelance-with-us":"Зарабатывайте-с-CGTrader","low-poly-3d-models":"низкополигональный-3d-модели","pbr-3d-models":"pbr-3d-модели","pbr-textures":"pbr-textures","rigged-3d-models":"сфальсифицировано-3d-модели","search":"search"},"sale_off":{"no_overlapping_campaigns":"Даты кампании пересекаются с другой существующей акцией распродажи.","parse_discount_rules_text":"Строка #{index} не содержит обязательного параметра 'discount:'"},"search":{"not_found":"Подписка на поиск не найдена","subscription_created":"Подписка на поиск создана","successfully_unsubscribed":"Вы успешно отписались от подписки на поиск"},"subscription_opt_ins":{"lock_period_error":"Some items cannot be opted out before the six-month lock period ends.","missing_items_error":"Items not found: %{items}."},"subscriptions":{"flash":{"active_subscription":"У вас уже есть активная подписка.","premium_upgrade_unavailable":"Переход на Premium временно недоступен. Пожалуйста, попробуйте позже.","update_payment_method":"С оплатой вашей подписки возникла проблема. Пожалуйста, обновите способ оплаты вместо покупки новой подписки."},"payment_form":{"current_price":"%{price}","features":{"first":"%{downloads} ассетов / месяц — скачивайте модели или генерируйте с ИИ","second":"Мгновенный доступ к %{models_count}+ ключевым 3D-моделям","third":"Без отчислений, без ИИ"},"from_price_label":"от","original_price":24.99,"per_model_label":"за модель","plan_name":"Подписка CGTrader Essentials — годовой план, ежемесячная оплата","premium":{"features":{"first":"%{downloads} ассетов / месяц — скачивайте модели или генерируйте с ИИ","fourth":"Без отчислений, без ИИ","second":"Мгновенный доступ к %{models_count}+ моделям в общем доступе","third":"Модели профессионального уровня, не входящие в Essentials"},"plan_name":"Подписка CGTrader Premium — годовой план, ежемесячная оплата"},"price_unit":"/месяц","vat_amount":"%{vat_amount}"}},"support":{"array":{"last_word_connector":" и ","sentence_connector":"и","skip_last_comma":true,"two_words_connector":" и ","words_connector":", "},"select":{"prompt":"Выберите: "}},"tag":{"description":{"bottle":"Ученые говорят, что бутылка вина в день не плоха для вас, но когда дело доходит до более сильных напитков, как виски или бурбон, мы должны реально смотреть наше потребление алкоголя. Очевидно, бокал шампанского в баре внешность круче, чем пластиковая бутылка воды, но думать о возможности жить дольше и пиво будет казаться разумной жертвой. Эта категория предлагает широкий выбор бутылок 3D моделей, начиная от связанных с алкоголем объектов, таких как винные шкафы и штопоры для всех типов бутылки 3D моделей (от шампуня до оливкового масла).","brain":"Мозг является одним из самых крупных и сложных органов в организме человека. Из-за свою сложную структуру и функцию, анатомия головного мозга также очень сложна. Тем не менее, ученые углубимся в архитектуру человеческого мозга, раскрывая ошеломляющие тайны. Широкий спектр реалистичных и подробные мозга 3D-моделей, которые готовы к компьютерной графики приложений, таких как реклама, 3D визуализация, 3D-игр, веб и любой другой области, связанной с 3D дизайн доступен для загрузки.","building":"Неясно, где современные архитекторы берут свое вдохновение из, но некоторые из всемирно известных зданий настолько передний край, что они похожи на видения из будущего. И мы не просто означает внешнюю часть ... В умелых руках стандартное домостроение становится захватывающий архитектурное творение с инновационным дизайном интерьера, независимо от того, явл
16518яется ли это офис или здание городской. Наш выбор построения 3D моделей готов для загрузки и может быть применен в различных областях - 3D анимации, графический дизайн, реклама, 3D игры или веб.","camera":"Фотография и киносъемка в настоящее время один из самых популярных полой карьеры среди молодых людей, но не все с профессиональной камерой, художник или фотограф. С качеством телефонных камер, улучшающих из года в год, мы видим людей, фотографировать на каждом шагу, и тех, кто имеет навыки редактирования вновь проявленное изображение действительно знаменитый. Жизнь действительно выглядит немного по-другому, когда вы находитесь на другой стороне линзы, но давайте не увлекайтесь ... Широкий спектр детальной камеры 3D моделей доступен для загрузки для использования графических дизайнеров, анимацией и разработчики игр и специалисты, работающие в других 3D-смежных областях.","car":"Глядя на эти очень подробно и очень реалистичных 3D моделей автомобилей, вы можете почти услышать рев двигателя! Широкий выбор автомобиля 3D модели могут сделать вашу жизнь намного проще, если вы боретесь с компьютерной графикой, анимацией или любой другой 3D-связанной областью.","cell":"Ученые подсчитали, что человеческое тело содержит от 5 млрд до 200 миллионов триллионов клеток. Растения и животные клетки, тоже. Мы не хотим утомлять вас с этим биологии вещи, но широкий спектр клеточных 3D моделей, которые у нас есть охватывает все, от крови, вирусов и ДНК в костном мозге и клеточных стенок. Разве это не ... нервы?","chair":"Мы принимаем, сидя в креслах как само собой разумеющееся и не всегда замечают, как много обаяния они могут добавить к нашему дому или офисного интерьера. Председатель фактически является одним из центральных предметов мебели в любом доме. Здесь вы можете найти высокое качество и привлекательный дизайн кресла 3д-модели, которые могут быть применены в различных областях - от рекламы и 3D визуализации в 3D-играх.","city":"Городская жизнь эксцентричная игра: улицы распирают гоночные такси, современные зданиями, пешеходы, идеально соответствующей офисную одежду, спальные мосты и парки ... Из Шанхая, Нью-Йорк (! Manhattan), в Лондон или Париж, - каждый город имеет свой собственная магия. Вы можете выбрать из этого богатого выбора детальных городских 3D моделей, которые могут быть использованы в ряде областей - рекламы, компьютерной графики, анимации, 3D визуализации или игр. Исследуйте городскую жизнь.","dragon":"Дракон является, вероятно, самым популярным существо живет в мире фантазий. Он показал во многих мифах различных культур, анимации, фильмов и книг, которые она стала легендой. Это особенно любят разработчиков игр, как это добавляет, что магическое очарование темных пещер и подземелий. Широкий спектр реалистичных, сфальсифицированы дракона 3д-модели, которые готовы к компьютерной графики приложений, таких как реклама, 3D визуализация, 3D-игр, веб и любой другой области, связанной с 3D дизайн доступен для скачивания.","earth":"Уровень детализации в недавно добавленном зрении земли в Google картах потряс ряд людей. Вы можете исследовать всю планету - океаны, горы и пустыни, города и обнаружить скрытые дороги, не выходя из дома. Вот некоторые невероятные вещи Мать-Земля не испытывала раньше. Подборка реалистичных земных 3D-моделей, которые готовы к компьютерной графики приложений, таких как реклама, 3D визуализация, 3D-игр, веб и любой другой области, связанной с 3D дизайн доступен для загрузки.","engine":"Некоторые люди могут назвать двигатель, просто слушая звук он делает, и это действительно удивительно. Независимо от того, явл
16518яется ли это реактивный двигатель или двигатель автомобиля, они очень сложные машины, которые мы используем так часто, но не тратить слишком много времени, думая об этом. Двигатель является двигателем (пока он не перестанет работать). Эта категория имеет целый ряд различных типов двигателей 3D-моделей, а также включает в себя деталь двигателя, как пропеллер или цилиндр, поэтому мы уверены, что вы найдете то, что вам нужно.","female":"Это не шутка, что существуют значительные различия между мужским и женским мозгом, а это значит, что это нормально, что средний мужчина и женщина ведут себя, чувствовать и реагировать совершенно по-разному. Но это еще не все об анатомии. Нет история любви, фильм или игра никогда не снял его без сильной девушки характера, потому что сила леди шарм неоспоримо. Богатый выбор реалистичных женских 3D моделей может предложить вам все, что вам нужно - от обнаженных 3D моделей для женской дыхательной системы.","hand":"Поэтому мы используем наши руки так мÐ
16518½Ð¾Ð³Ð¾ вещей, их необыкновенная анатомия. Один только большой палец находится под контролем девяти отдельных мышц. Трудно поверить, но первые руки были более экзотическими, чем любая сторона сегодня - некоторые виды были семь пальцев. Человеческое тело значительно изменилось. Рука или рука, как мы видим это сегодня выглядит лучше, чем его ранние версии. Широкий спектр реалистичных, сфальсифицированы ручные 3д-модели, которые готовы к компьютерной графики приложений, таких как реклама, 3D визуализация, 3D-игр, веб и любой другой области, связанной с 3D дизайн доступен для скачивания.","head":"В анатомии человека, голова верхняя часть тела человека. Моделирование человеческой головы 3D может оказаться немного сложным, потому что (несмотря на то, что есть очень много различных форм и размеров), мы оцениваем симметричные, реалистично выглядящие головы - не смешные глаза, уши, нос, губы или линии челюсти, пожалуйста. Лицо является чувствительной областью, и это будет просто обычная грубым, чтобы весело провести время с этим. Наш выбор включает в себя реалистичный мужчина, женщина, ребенок и даже персонаж рассказа, как Гарри Поттер и Бэтмен или знаменитость, как Том Круз и Скарлетт Йоханссон руководители, которые готовы для загрузки.","helmet":"Всегда помните, что приходит здоровье и безопасность первого, поэтому защита ваша голова здравая идея. Средневековые рыцари, Daft Punk и даже Ironman носить шлемы, так как обычные супергерой велосипедисты или строители не должны беспокоиться о поиске распущены. Эта коллекция шлемов 3D моделей, которые доступны для загрузки окажется полезным, если вы работаете в области графического дизайна, 3D-анимации, 3D-игры или визуализации.","house":"Это старая истина, что дом человека есть свой замок, но когда дело доходит до строительства, что идеальный дом или купить идеальную квартиру, мы, как правило, теряют голова над ним. К счастью, этот широкий спектр дом 3D моделей, которые включают в себя все, начиная от архитектурных деталей интерьера и экстерьера, мебель, поможет создать современную 3D визуализации, чтобы облегчить эту боль.","human":"Когда вы начинаете изучать анатомию человека, вы понимаете, что люди, такие запутанные и сложные машины, что захватывает дух. Каждая часть тела, мышц, суставов, органов или костей сочетание соединяется в одну систему, ничего не работает в изоляции! Эта супер реалистичные 3D модель человека позволит исследовать анатомические структуры с любым углом.","lamp":"Если вы посмотрите вокруг вашего дома и подсчитывать типы ламп, которыми вы владеете, вы будете удивлены: чтение, настольные, настенные, потолочные, письменный стол, штатив лампы ... Это достаточно, чтобы сделать вашу голову спина! Не говоря уже о том, что стили они приходят - от экстравагантных современных ламп в стиле ретро или стильный ф
16518утуристический. В этой категории у нас есть все виды ламп 3D моделей, так что просматривать и загружать для использования в области графического дизайна, 3D визуализации или любой другой 3D-связанной области.","light":"Вы когда-нибудь задавались вопросом, почему лампочка является символом блестящей идеей? Конечно, идея яркая может просветить архитектуру ума, но это не так. Уличный свет, люстра или любой другой тип лампы никогда не будет стоять шанс занять это место. Если в следующий раз, когда вы посмотрите на потолок и смотреть на какой-то архитектурный свет идея гений не ударил вас, это может быть потому, что вы смотрели на неправильную систему молнии ... Наш выбор высокого качества света 3D модели окажется под рукой, если вы работаете в области архитектуры, компьютерной графики, 3D-анимации и дизайна.","machine":"Представьте себе, если все машины вдруг остановились не работают: нет кофе, нет удобной для стирки, не ПВЕН на угле улицы, нет времени тренажерного зала, не завышены автоматов ... Это звук удручает, не так ли? Мы так привыкли к этим бытовой техники и электронных устройств, что буквально остановить мировой поворот! К счастью, наш выбор подробные машины 3D моделей охватывает все из пулеметов электрогенераторов, которые окажутся полезными для дизайнеров, работающих с компьютерной графикой, рекламой, 3D-анимации, 3D-игры или Интернет.","man":"Обширный набор искусственных 3D моделей доступен для скачивания. Сидеть человек, простой человек, летающий человек или средний человек, у нас есть все - кроме того, что совершенный человек, что все после. Он тусклый характер и мы ненавидим, чтобы довести это, но это может быть мифом ... Но эй, люди рождены, чтобы быть удивительным, не является совершенным.","robot":"R2D2, вероятно, самый известный робот в мире, но это, конечно, не только один. От индустриального к игрушке человеческих роботов, мы видим все более широкий выбор этих механических машин, который является одновременно прекрасным и немного тревожным. Это не так уж трудно понять людей, которые настаивают, что компьютеры, технологии, роботы делают слишком значительное влияние на человека. Широкий спектр реалистичных, сфальсифицированы роботов 3д-модели, которые готовы к компьютерной графики приложений, таких как реклама, 3D визуализация, 3D-игр, веб и любой другой области, связанной с 3D дизайн доступен для скачивания.","ship":"Суда явл
16518яются мощными суда и бывают всех размеров, цветов и форм. Они могут быть классифицированы как грузовых судов, военных кораблей и пассажирских судов, но все они работают в глубоких морских водах. Если вы испытали дискомфорт от укачивания, она не может быть ваши любимые транспортные средства, но многие люди считают морские прогулки, как довольно стильный и приятный. Наш выбор судовых 3D моделей готова для загрузки и будет соответствовать стандартам даже самого требовательного капитана.","skeleton":"Скелет человека или скелетная система (анатомия) является внутренней структурой тела, которая состоит из 270 костей при рождении. Она обеспечивает структуру тела, защиту и движение и является одним из самых сложных каркасных систем. Богатый выбор анатомически правильный, реалистичный и масштабируется скелет 3D-моделей, которые идеально подходят для использования в 3D-анимации, графического дизайна, рекламы, 3D-игр и веб доступен для загрузки.","skull":"Старейший наиболее полный, генетически нетронутый человеческий череп был обнаружен в окружении множества вымерших животных более чем на 40 метров ниже уровня моря. Это один из самых старых скелетов, найденных в Новом Свете, и это также включает в себя все основные кости тела и неповрежденного черепа и набор зубов. Человеческие черепа в последнее время стали вещью в моде и дизайне, но когда вы на самом деле дать ему некоторые мысли, они вроде делают нас все похожи по анатомии уродам ... Эта категория предлагает широкий спектр череп 3D моделей для использования в графическом дизайне , 3D-анимация, 3D-игры или рекламы.","sofa":"Роскошь или простой, современный или античный, кожа или минималистский дизайн? Как вам нравится ваш диван? Диапазон диван 3D моделей, которые мы должны удовлетворить даже самый худший перфекционист. Когда вы задумаетесь об этом, диван является центром каждой гостиной, так что вы должны быть творческими при выборе дизайна!","table":"Новейшие мебельные тенденции настолько разнообразны, что выбор современный, модный стол становится слишком напряженным. Там раньше было время, когда у вас был простой обеденный стол для скромных праздников и этого было достаточно, но сегодня - таблица должна быть заявление или, по крайней мере, выделяющийся интерьер акцент. Этот выбор дает широкий спектр настольных 3D-моделей, чтобы удовлетворить любой каприз.","tower":"Каждый город имеет специальную башню, что все на самом деле гордиться. Подумайте Эйфелева башня или Пизанская башня, или в Лондоне Тауэрский мост, не говоря уже все современные небоскребы, футуристические здания и городской пейзаж, Ð
16518ºÐ¾Ñ‚орый туристы восхищаются. Однако, когда речь идет о промышленных районах города, вода или электрические башни, все стремятся превратить свои глаза в сторону ... Но мы должны научиться не ценить каждое архитектурное творение, независимо от того, древний, промышленный или современный. Детальные башни 3D моделей доступны для скачивания и готовы к компьютерным графикам приложений, таким как реклама, 3D визуализация, 3D-игры, веб и любой другой область, связанной с 3D-дизайном.","tree":"Если вы находитесь на миссии, чтобы посадить деревья или экзотические растения, вы пришли в нужное место. Эта категория предлагает широкий выбор простой в использовании, очень реалистичные 3D модели деревьев к CG художников и архитекторов, которые стремятся добавить зелень в свои проекты. Если вы цените хорошее качество, мы уверены, что этот выбор 3D моделей деревьев растений будет соответствовать вашим стандартам!","truck":"Вождение грузовика трудная работа, особенно если вам нужно найти свой путь через вареньем город движения. Независимо от того, является ли это пожарный, мусоровоз, грузовик пищи или родовой один, он принимает большое умение маневрировать эти гигантские транспортные средства. Мы уверены, что этот выбор реалистичных, высокое качество грузовиков 3D модели идеально подходят для использования в 3D-анимации, графического дизайна, рекламы или любой другой 3D-связанной области.","tv":"Там раньше было время, когда камин был центральной каждого респектабельного дома, теперь телевизор с игроками и динамиками король гостиной (и даже спальни). В последние годы телевидение догнали новейшими технологиями. В результате, у нас есть умные 3D-телевизор, плоские и плазменные экраны, миллион новых функций и дизайн сенсорного экрана. Не удивительно, что телевидение не толкает киÐ
16518½Ð¾ из рынка. Широкий спектр реалистичных, подробных моделей телевизоров 3D, которые готовы к компьютерной графики приложений, таких как реклама, 3D визуализация, 3D-игр, веб и любой другой области, связанной с 3D дизайн доступен для загрузки."}},"tag_promotion":{"days_left_error":"должно быть от 1 до 30","no_other_pending_promotions_for_same_item_tag":"уже имеет ожидающую промоакцию"},"time":{"am":"утра","formats":{"default":"%a, %d %b %Y, %H:%M:%S %z","long":"%d %B %Y, %H:%M","short":"%d %b, %H:%M"},"pm":"вечера"},"tooltips":{"buyer_loyalty_program_current_level_html":"В настоящее время вы потратили %{total_price_paid} на CGTrader и вашей кредитной заработать скорость составляет %{buyer_loyalty_rate}. \u003cbr/\u003e Это означает, что вы получите %{buyer_loyalty_rate} кредитов от каждой покупки.\n","buyer_loyalty_program_html":"Это процент от того, сколько вы получите от ваших покупок в качестве новых кредитов. \u003cbr/\u003e Это увеличит до 30% в качестве пожизненных расходов на CGTrader увеличении. \u003cbr/\u003e В настоящее время ваша кредитная ставка заработать %{buyer_loyalty_rate}.\n","buyer_loyalty_program_next_level_html":"Следующий уровень %{next_lvl_expenses} увеличит ваш Кредит заработать ставку до %{next_lvl_expenses_rate}. \u003cbr/\u003e Вы должны купить за $ %{left_until_next_lvl} больше, чтобы достичь следующего уровня.\n"},"trending_headers":{"models":"Посмотрите другие популярные 3D модели","print":"Посмотрите другие популярные 3D модели для печати"},"tutorial":{"destroy":"Урок был успешно удалён.","submit_for_review_error":"Пожалуйста, заполните все обязательные поля.","submitted":"Спасибо за отправку вашего урока. Он будет проверен и вскоре опубликован."}},"zh-CN":{"activerecord":{"attributes":{"content_report":{"categories":{"adult":"成人或暴力内容","copyright":"侵犯版权"}}},"errors":{"messages":{"embedded_script":"此文件不受支持,无法上传。请尝试其他文件。"}}},"api":{"profile":{"actions":{"contact":"联系","follow":"关注","follow_tooltip":"关注此设计师以获取其内容更新,并在搜索结果中轻松筛选喜爱的设计师。","hide":"隐藏","hire_me":"雇佣我","invite_client":"启动项目","mute_tooltip":"在搜索结果中隐藏此设计师的模型。","take_over":"接管","unfollow":"取消关注","unhide":"显示","view_all_models":"查看所有设计师的模型"},"private_project_review":"私人项目"}},"browser":{"3d_models":"3D模型","3d_print":"3D打印","active_filters":{"polygons":"%{range}多边形"},"adult_content":"显示年龄+","animated":"动画","breadcrumb":{"browse_by_type":"按类型浏览","collection":"3D资源包","free":"自由","free_models":"免费的3D模型","free_print_models":"免费的3D打印模型","low_poly":"低多边形","low_poly_models":"VR / AR /低多边形的3D模型","low_poly_print_models":"低多边形 3D 打印模型","model_3d":"3D模型","model_3dp":"3D打印模型","models":"3D模型","models_3d":"3D模型","models_3dp":"3D打印模型","models_variant_0":"3D模型","models_variant_1":"3D资产","models_variant_2":"VR模型","models_variant_3":"CG模型","models_variant_4":"3D模型商店","models_variant_5":"3D物体","print_models":"3D打印文件","print_models_variant_0":"3D打印模型","print_models_variant_1":"3D打印机模型","print_models_variant_10":"用于打印的3D模型","print_models_variant_11":"可供打印的3D模型","print_models_variant_12":"用于打印的3D文件","print_models_variant_13":"适用于3D打印机的模型","print_models_variant_14":"用于3D打印的模型","print_models_variant_15":"3D打印的模型","print_models_variant_16":"可打印的3D文件","print_models_variant_2":"用于打印的3D模型","print_models_variant_3":"3D打印模型","print_models_variant_4":"3D打印文件","print_models_variant_5":"3D打印文件","print_models_variant_6":"3D打印机文件","print_models_variant_7":"可打印3D模型","print_models_variant_8":"可打印的3D模型","print_models_variant_9":"已打印的3D模型"},"buyer_engagement_program":"折扣","categories":"分类","category":{"meta":{"3d_models":"%{count} %{category}的3D模型可供下载的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D%{page}","3d_print_models":"下载 %{count} %{category}3D打印模型,可供下载的STL,OBJ等格式的文件,准备与FDM,SLS等3D打印机上打印。%{page}","animated_3d_models":"%{count} %{category}动画可供下载的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D 3D模型%{page}","free_3d_models":"下载 %{count} %{category}免费的3D模型,在MAX,OBJ,FBX,3DS,C4D文件格式提供,准备VR / AR,动漫,游戏等3D项目。%{page}","free_3d_print_models":"下载 %{count} %{category}免费的3D打印模型准备FDM,SLA,SLS等3D打印机,可用的格式STL,OBJ,3DM多。%{page}","lowpoly_3d_models":"下载 %{count} VR / AR /低多边形%{category}的3D模型准备虚拟现实,游戏和其他实时应用程序,提供FBX,OBJ,MAX,3DS,C4D文件格式%{page}","rigged_3d_models":"%{count} %{category}操纵可供下载的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D 3D模型%{page}"}},"collection":"3D资源包","discounts":"折扣","ext":{"meta":{"3d_models":"下载%{rounded_count}个%{format_upper}格式3D模型。免费与付费%{format_lower}文件。","3d_print_models":"下载 %{count} %{ext}文件 - %{software_name} 3D打印模型准备VR / AR,3D设计,动画,游戏和实时应用程序%{page}。"}},"filter_button":"应用过滤器","filters":{"adult_content_only":"仅限成人内容","cgt_standard_only":"CGT标准","collections_only":"仅限组合包","content_filters":"内容筛选","designer":"设计师","exclude_3d_print":"排除3D打印","exclude_adult_content":"排除成人内容","exclude_ai_generated":"排除AI生成内容","exclude_editorial_license":"排除编辑许可","exclude_free":"排除免费","file_format":"文件格式","following":"关注中","free":"免费","has_marmoset":"带有3D查看器","has_video":"带有视频","include_ai_generated":"包含AI生成内容","label":"过滤器","poly_count":"多边形数量","print_ready":"3D打印","stl_files":"STL 文件","subscription":"订阅","type":"类型"},"formats":"格式","free":"自由","free_models":"只显示免费模式","free_to_premium":{"main":"%{category} 3D模型","postfix":"在优质列表中。","prefix":"你需要特别的东西吗?你可以找到更高质量的"},"gallery":{"preview":"预览"},"game_ready":"游戏准备","high_poly":"高聚","items_page_title":"3D模型","license":{"exclude_branded":"排除品牌","exclude_editorial":"编辑排除","prompt":"执照","tooltip_more":"阅读更多","tooltip_text":" 不要显示,只能在非商业,教育目的而使用的机型。","tooltip_title":"排除编辑观点:"},"low-poly":"低聚","low_poly":"低聚","meta":{"3d_models":"下载或购买%{rounded_count}个专业3D模型。免费与付费3D模型,适用于游戏、影视、VFX、AR/VR和3D打印。支持FBX、OBJ、BLEND、GLB格式。","3d_print_models":"下载%{rounded_count}个3D打印模型。免费与付费STL文件:手办、模型、实用件等。","animated_3d_models":"下载%{rounded_count}个带动画的3D模型。FBX、BLEND、GLB格式的游戏就绪角色与物体。","free_3d_models":"下载%{rounded_count}个免费3D模型。免费3D素材,支持FBX、OBJ、BLEND、GLB格式,可用于游戏和CG项目。","free_3d_print_models":"%{rounded_count}个免费3D打印模型。免费下载STL文件,适用于各类3D打印机。","lowpoly_3d_models":"下载%{rounded_count}个低多边形3D模型,适用于游戏、AR/VR与实时渲染。","metaverse":"输入我们的高和低poly 3D型号的收集,并使用任何文件格式。","omniverse":"使用我们的Omniverse 3D模型来构建虚拟世界。从具有高和低的多边形计数的动画,操纵和PBR模型中进行选择。","page_prefix":"page %{page_number} with","pbr_3d_models":"下载%{rounded_count}个带PBR贴图的3D模型,适用于Unity、Unreal与实时渲染引擎。","rigged_3d_models":"下载%{rounded_count}个已绑定骨骼的3D模型,可直接用于动画。支持Blender、Maya、3ds Ma
16518x、Unity和Unreal。","snap-ar":"借助我们的3D型号,您可以使用Snap AR加入数百万个Snapchatters,以创造有趣的增强现实体验。","spark-ar":"使用各种Spark AR 3D型号来使您的Facebook和Instagram亮起,这些模型被操纵,动画和准备使用。","stream_3d_print_models":"浏览通过各种流的3D打印模型类别来发现一些从专业的3D制作者最不寻常的3D打印设计。","textures":"%{count} 个纹理的三维资源和材料可供下载的各种格式,包括.TGA .sbsar .JPEG .PNG .TIFF等。 %{页}","tiktok":"Tiktok 3D型号使您的视频效果更令人印象深刻。我们的图书馆可以满足您的所有创造性需求,并不断更新。","unity":"以任何文件格式和任何poly计数查找您的Unity项目的3D模型。创建独特的虚拟环境所需的一切。","unreal":"使用任何文件格式兼容的3D模型创建令人印象深刻的可视化。我们的图书馆都有来自每个类别的低模型。"},"optimized":"低多边形/ PBR","page_title":{"animated_3d_models":"动画3D模型 – 免费与付费","format_page":"%{format_upper} 3D模型 – %{format_lower}文件下载","free_3d_models":"免费3D模型 – 免费下载","free_3d_print_models":"免费3D打印模型 – 免费STL文件下载","lowpoly_3d_models":"低多边形3D模型(Low Poly) – 免费与付费","pbr_3d_models":"PBR 3D模型 – 免费与付费","rigged_3d_models":"绑定骨骼3D模型(Rigged) – 免费与付费","software_page":"%{software} 3D模型 – %{ext_file}下载","tag":{"3d_merged_premium":"%{tag_cap}3D模型 – 免费下载与购买","3d_merged_premium_print":"%{tag_cap}3D打印模型 – STL文件","3d_models":"%{tag_cap}3D模型 – 免费下载与购买","3d_print_models":"%{tag_cap}3D打印模型 – STL文件"}},"pbr":"PBR","per_page":"每页","polygons_options":{"custom":"自定义范围","gt_250k":"250K +","lt_5k":"高达5K","more_than_250000":"250K以上","prompt":"保利计","range_0_5000":"最多5K","range_100000_250000":"100 - 250K","range_10000_50000":"10 - 50K","range_100k_250k":"100k发出250K","range_10k_50k":"10K到50K","range_50000_100000":"50 - 100K","range_5000_10000":"5 - 10K","range_50k_100k":"50K到100K","range_5k_10k":"5K到10K"},"price":"价格:$%{from}-%{to}","price_range":"价钱","print_ready":"三维打印就绪","printable":"3D打印模型","reset_button":"复位过滤器","rigged":"非法操纵的","rounded_count":{"delimiter":",","exact":"%{n}","separator":".","ten_thousand":"%{n}万","thousand":"%{n}"},"sale_off":"特价促销","search_placeholder":"搜索关键词","show_all_formats":"显示所有格式","simulation_ready":"仿真就绪","software":{"meta":{"3d_models":"下载%{rounded_count}个%{software}用3D模型。免费与付费%{ext_file}文件,可直接导入。"}},"sort_by":"排序方式","sort_by_options":{"best_match":"最佳匹配","highest_price":"较高的价格","lowest_price":"更低的价格","newest":"最新","oldest":"最老的","sales":"最畅销","trending":"趋势"},"subcategory":{"meta":{"3d_models":"%{count} %{category}的3D模型可供下载的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D%{page}","3d_print_models":"下载 %{count} %{category}3D打印模型,可供下载的STL,OBJ等格式的文件,准备与FDM,SLS等3D打印机上打印。%{page}","animated_3d_models":"%{count} %{category}动画可供下载的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D 3D模型%{page}","free_3d_models":"下载 %{count} %{category}免费的3D模型,在MAX,OBJ,FBX,3DS,C4D文件格式提供,准备VR / AR,动漫,游戏等3D项目。%{page}","free_3d_print_models":"下载 %{count} %{category}免费的3D打印模型准备FDM,SLA,SLS等3D打印机,可用的格式STL,OBJ,3DM多。%{page}","lowpoly_3d_models":"下载VR / AR /低聚%{tag}的3D模型准备虚拟现实,游戏和其他实时应用程序,提供FBX,OBJ,MAX,3DS,C4D文件格式%{page}","rigged_3d_models":"%{count} %{category}操纵可供下载的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D 3D模型%{page}"}}
16518,"subtitle":{"aircraft":"%{count} 算机的3D模型可供下载我们的市场。CG %{airplanes_link} 将融入模拟,可视化,广告和视频。%{drone_link} 车身和部件将令细小飞车辆的球迷。并且%{rigged_link}模型准备就绪,要导入到游戏引擎和用于动画或VR / AR项目。飞行游戏,模拟器和定期两种CG和低聚类型的影片,可以在最流行的格式,包括FBX,OBJ,MAX,3DS,C4D充满3D模型。","animals":"CG三维动物 %{count} - %{cats_link} %{horses_link} %{bears_link} %{rhinos_link} %{eagles_link} %{lions_link},虚构 %{dragons_link}等等 - 都可以在这里下载。 3D动物的集合将满足广告或静态场景的需求。我们也有%{rigged_link}款的游戏,动画,影视制作和VR / AR项目。使用过滤器查找操纵,动画,低聚或在大多数行业公认的文件格式,包括FBX,OBJ,MAX,3DS,C4D,和其他人提供免费的3D模型。","animated":"下载%{rounded_count}个带预置动画的3D模型:角色、动物与道具,适用于游戏、影视与实时项目。","architectural":"可供下载建筑的3D模型 %{count} 计数包含元素与建筑,建筑与工程设计的细节。购买或下载免费的3D模型,浏览上述的类别中。您可以使用模型动画或游戏 - 其中一些是操纵和低聚担任VR / AR项目的需要。 3D模型文件格式如FBX,OBJ,MAX,3DS,C4D可用。","car":"%{count} 个汽车3D模型可供下载。其中低聚的版本将是游戏设计者或VR / AR应用的开发者,他人的吸引力 - 以电影制作,动画或广告商。从经典古董车到赛车和概念的人在电影中看到的,有基本上任何格式(FBX,OBJ,MAX,3DS,C4D等),你可能需要一个伟大的各种3D汽车模型。使用动画和免费的3D模型,原型和缩短开发时间。","character":"%{count} 个字符的3D模型可供下载,所以这一类浏览,你会发现3D模型%{human_link},人物,神话人物,科幻%{robots_link},服装及相关文字其他的事情。从%{heart_link}和%{hand_link}以细致逼真%{male_link}或%{female_link}车型。许多机型都操纵并可以很容易地添加到您的游戏,VR / AR项目,视频或动画。这两个高聚和低多边形模型可以以多种格式,包括FBX,OBJ,MAX,3DS,C4D被发现。","custom":"在这里,您可以购买或免费下载%{tag}标签的3D模型。 %{tag_heading_subtitle}","electronics":"%{count} 个电子可供下载的3D模型。这是建筑师,室内设计师,关卡设计师,现场制定者和其他广告类别。 %的3D模型%{smartphones_link},%{laptops_link},%{cameras_link},%{tvs_link},更有助于准确地捕捉现代生活与设备和技术的痴迷。有文件格式,包括FBX,OBJ,MAX,3DS,C4D可用的3D模型。免费下载或购买高档车型和使用动画,游戏和VR / AR项目。","exterior":"可从立面,城市建筑,景观对象,住宅产业建设,基础设施建设的建筑物和满量程%{city_link}模型下载范围建筑外墙3D模型 %{count} 个。购买或下载免费的3D模型,浏览上述的类别中。使用过滤器查找操纵,动画,低多,免费的3D模型或排序格式需要,包括FBX,OBJ,MAX,3DS,C4D。有些资产是低多边形并且可以帮助你完成你的游戏或VR / AR应用程序的环境。","food":"%{count} 计数食品的3D模型可用于download..Food提供良好服务,以打造国内生活的逼真的场景。 3D水果,蔬菜,饮料和餐点都在这里餐馆完整的广告,对于健康的饮食AR应用和可视化建筑师和室内设计师的工作。无论您是在寻找3D %{pizza_link}和%{ketchup_link}模型或的婚礼%{cake_link}3D模型,他们在这里。一些低多边形的版本是为那些在游戏中,VR / AR发展。三维模型可以在FBX,OBJ,MAX,3DS,C4D格式被发现。","formats":"下载%{rounded_count}个%{format_upper}格式的3D模型,免费与付费。本页所有文件均为原生%{format_upper},无需转换。","free_general":"下载%{rounded_count}个免费3D模型:免版税3D素材,支持FBX、OBJ、BLEND和GLB格式,适用于个人和商业项目。","free_no_tag":"%{model_count} 免费的3D标签的机型准备三维打印。下载标签免费的3D打印模型,文件格式提供,包括STL,OBJ,3DM,3DS,MAX。 3D模型愿与FDM,SLA,SLS和其他类型的3D打印机的使用。","free_printable":"免费下载%{rounded_count}个3D打印模型:社区验证的免费STL文件——手办、摆件、实用小物等。","free_tag":"免费的3D %{tag}标签的机型准备三维打印。下载 %{tag}标签免费的3D打印模型,文件格式提供,包括STL,OBJ,3DM,3DS,MAX。 3D模型愿与FDM,SLA,SLS和其他类型的3D打印机的使用。","furniture":"可供下载 %{count} 计数家具3D模型包括桌子,椅子,沙发,床等家具中的各种纹理(木材,金属等)和样式(%{retro_link},%{modern_link}等)。这个类别是为参与建筑和室内设计,以及广告和动画制作人。实时资产可以帮助游戏和VR / AR应用开发商完成项目。使用过滤器查找操纵,动画,低聚或免费的3D模型。可在包括FBX,OBJ,MAX,3DS,C4D任何文件格式。","general":"浏览%{rounded_count}个专业3D模型:免费下载或购买,适用于游戏、影视、VFX、AR/VR与3D打印。支持FBX、OBJ、BLEND、GLB等100多种格式。","high_poly":"购买或下载动画专业的3D模型准备在CG项目,影视制作,动画制作,可视化,和其他人使用。资产可供下载在许多行业公认的格式,包括MAX,OBJ,FBX,3DS,UE4等。如果你在寻找高品质的聚3D资源的是,我们有各种需求的领先的数字艺术图书馆。\n","household":"可供下载 %{count} 个家庭3D模型可以用来给细节的房间。您可能希望把餐具放在桌子上,有胡椒和盐集装箱齐全的厨房内,或放些%{lights_link}和%{flowers_link}
16518床边营造豪华的感觉。如果你是一位室内设计师,动画师,VR / AR应用程序或游戏的创造者,这个类别是为您。使用过滤器查找操纵,动画,低聚或免费的3D模型。可在包括FBX,OBJ,MAX,3DS,C4D任何文件格式。","industrial":"%{count} 个工业3D模型可供下载 - 建筑材料,%{engineering_link}工具,机械和%{containers_link} - 将同时提供详细信息和互动,操纵元素,打造活泼的环境。这是模拟/ VR / AR或开发建设中的游戏%{construction_link}站点级别完美的类别。 3D模型将成为专业建设有不同的软件工作作为格式如FBX,OBJ,MAX,3DS,C4D模型可精确的可视化。","interior":"%{count} 个室内3D模型可供下载,包括建筑,建筑和工程,室内设计使用的元素和细节。购买溢价或下载一个免费的3D模型,浏览上面列出的类别。您可以使用模型动画或游戏 - 它们中的一些操纵,动画和低聚。可在多种文件格式,包括FBX,OBJ,MAX,3DS,C4D。","low_poly":"下载%{rounded_count}个低多边形(low poly)3D模型:轻量、游戏就绪,适用于游戏、AR/VR和移动项目。","military":"%{count} 个武器,武器,%{swords_link}和火炮现代和历史的军事3D模型由军队和自卫使用可供下载。您可以使用型号为游戏或VR / AR内容 - 他们中的一些优化,并准备将其添加到游戏引擎的多边形。操纵和动画模型可以进一步简化您的工作。你一定会找到相关的资产,因为有你可能需要(FBX,OBJ,MAX,3DS,C4D等)的任何文件格式提供的3D模型。","not_custom":"下载%{rounded_count}个%{tag_cap}3D模型:免费与付费、写实与风格化,支持FBX、OBJ、BLEND和GLB格式。","optimized":"其中有较少的多边形模型是最好的,需要快速处理,如虚拟现实(VR),增强现实(AR),混合现实(MR),交叉现实(XR)和游戏,尤其是手机游戏的实时应用。从我们操纵和动画模型的集合,轻松地在实时应用程序中使用它们。获取三维资产环境,挑道具,物品或者购买完整的三维模型集合,捆和包的一切你的游戏可能需要。节省开发时间和成本,制定原型经验或使用3D模型作为您的项目占位符。基于物理的渲染,或者PBR,表示其准确一致地呈现在任何照明条件下,由于纹理化的模型工作流在创建过程中使用。\n","pbr":"下载%{rounded_count}个带PBR(基于物理渲染)贴图的3D模型:在任何光照下都真实一致的材质,适用于Unity、Unreal等引擎。","plant":"可供下载%{计数}工厂的3D模型包含成长为国内植物和自然植物有机体,如花卉,%{trees_link},草,攀缘植物,绿化,草本,多年生植物,苗,芽,灌木,单,豆芽,藤蔓,杂草。购买溢价或下载一个免费的3D模型,浏览的类别上面找到可用的模型之间的最佳匹配。使用过滤器查找操纵,动画,低聚(用于游戏和VR / AR应用程序)模式,或过滤掉所需的格式(在FBX,OBJ,MAX,3DS,C4D模型和更多可用)。","printable":"下载%{rounded_count}个3D打印模型:经社区测试的免费与付费STL文件,适用于FDM和光固化打印机。手办、模型、实用件与零件。","printable_merged_tag":"下载%{rounded_count}个%{tag_cap}3D打印模型:免费与付费STL文件,FDM与光固化打印机均适用。","printable_tag":"下载%{rounded_count}个%{tag_cap}3D打印模型:免费与付费STL文件,FDM与光固化打印机均适用。","rigged":"下载%{rounded_count}个rigged(已绑定骨骼)3D模型:角色、动物与生物,随时可制作动画。支持Blender、Maya、3ds Max、Unity和Unreal。","scanned":"%{count} 个扫描3D模型可用于购买或免费下载。 3D扫描模型准备就绪,动画,游戏和VR / AR项目。你可以找到的3D模型,如岩石和自然表面,人类和其他在这里现实生活中物体在自然界中发现的对象...它不能得到任何比这更切合实际。使用过滤器查找操纵,动画,低聚或免费的3D模型。可在包括FBX,OBJ,MAX,3DS,C4D任何文件格式。","science":"%{count} 个科学可供下载,从医学,生物学,物理学,化学领域和其他现实世界中这一类范围内的3D模型对象相关科技:实验室设备,显微组织和亚原子世界。不要再观望,%{cell_link},%{skull_link} 和 %{skeleton_link} 3D模型可以在这里找到!购买或下载免费的3D资产,浏览上述的类别中。您可以使用动画或小聚的人操纵的模型为游戏和VR / AR项目。免费和付费的3D模型可以在许多文件格式,包括FBX,OBJ,MAX,3DS,C4D被发现。","scripts_plugins":"%{count} 个3D脚本/插件可供下载。 3D脚本/插件可以添加到各种软件和用于更有效地使你的工作流水线。通过格式过滤器来查找工具配件您首选的软件。\n","software_page":"下载%{rounded_count}个%{software}
16518用3D模型:原生%{ext_file}文件可直接打开,其他格式也可轻松导入。","space":"%{count} 个空间的3D模型可供下载的CGTrader。空间是一个完整的低聚,科幻%{spaceships_link}游戏准备的车型类别。也有%{asteroids_link},行星(包括月球和火星,以模拟殖民化)和现实空间%{shuttles_link}为更科学地倾斜。该车型绝对可以在VR空间模拟器游戏,AR应用程序和动画来演示这个星球%{earth_link}以外的世界。过滤器在这里你会发现操纵,动画,低多边形资产或模型特定格式,包括FBX,OBJ,MAX,3DS,C4D帮助。","sports":"%{count} 算运动的3D模型可嵌套在一起的设备是用于休闲活动,娱乐,运动,休闲,愉快的消遣,乐趣以及游戏,儿童玩具和全方位的乐器。购买或下载所需的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D一个免费的3D模型,浏览上述的类别中。您可以使用模型动画,游戏,VR / AR项目 - 他们很多都是七拼八凑,低聚或已动画。","test":{"airplane":"航空爱好者,听好了,它从来没有那么容易得到完美的3D打印飞机模型为您的棋盘游戏或模型集合。搜索%{model_count} 3D打印飞机模型,找到你只需要有一个货架上的模型。发现真正的飞机模型或捏造的。搜索最新设计什么才是最重要的,从莱特兄弟的时候能回来。它也是谁的梦想,当他长大后成为一名飞行员的任何孩子一个完美的礼物。购买和下载的3D打印模型,自行打印或提交模型,以我们的3D打印服务提供商之一,有它打印并运到你在任何时间!\n","anatomy":"%{model_count} 3D解剖模型可供下载我们的市场。探索具有较高的人体解剖 - 聚骨骼,肌肉,器官模型。适合任何教育或科学项目。使用可打印三维模型,以提高教育的经验。创建令人惊叹的动画和视频游戏与我们\n 操纵模型的大量藏书。使用我们先进的过滤系统,可以轻松找到低聚,动画,PBR,\n 受操纵的模型或任何适合您的需求最好的。选择的文件格式很大,包括\n 最流行的像FBX,OBJ,MAX,3DS,C4D。\n","castle":"印刷城堡你自己的惊人3D是一个点击即可!搜索各种3D模型城堡准备好3D打印。为您的d&d会话或任何其他中世纪RPG桌面游戏独特的梦幻城堡。这些中世纪的堡垒是任何棋盘游戏爱好者或者一个伟大的除了自己的收藏一个惊人的礼物。查找绘画爱好者或孩子们的这种玩具的一个完美的3D模型。订购和下载你所选择的3D城堡设计风格,用自己的打印机打印出来,或提交模型,以我们的3D打印服务提供商之一,有它的印刷和装运。\n","dice":"运气不会用完用3D打印骰子!如果你想为你的棋盘游戏夜晚真实的游戏骰子,看看没了。在这里,您可以 %{model_count} 免费的3D模型骰子准备3D打印之间进行选择。当比赛结束时,将这些美丽的模特作为附件。免费下载的3D打印模型骰子,文件格式提供,包括STL,OBJ,3DM,3DS,MAX。我们的3D模型愿与FDM,SLA,SLS和其他类型的3D打印机的使用。使你的游戏更有趣,让你的3D打印的骰子!\n","drone":"3D打印,并建立自己的无人机或找到零件进行更换。 3D印刷无人驾驶飞机很容易迫降后组装和维修。发现与最小的改动完美的摄像机安装位置或要求的模型。完美的好玩的休闲活动,航拍摄影,录像或映射。查找并购买一个设计独特的3D模型,无人驾驶飞机适合您的所有需求。自行打印或提交模型给我们的三维打印服务提供商之一,有它打印并运到你在任何时间!获取自己的3D打印无人机和漫游广泛的蓝天!\n","pokemon":"皮卡丘,我选择了你!选择从口袋妖怪准备3D打印的负荷。大扩展您的3D打印的神奇宝贝收藏。搜索和在最好的,最详细的宠物小精灵设计绊倒你的下一个桌面游戏的朋友。订购和下载你的3D宠物小精灵,用自己的三维打印机打印出来,或提交模型,以我们的3D打印服务提供商之一,有它的印刷和装运。毕竟,这是最酷的事情3D打印一个!\n","ring":"3D打印珠宝是在同行业中新的黑色,因为是3D打印的戒指!搜寻 %{model_count} 免费的3D打印模型环的巨大数额。通过独特的设计浏览,找到刚才为你做什么。无法找到在店里自己做任何事或者,也许你想要一个附件酷似一个你最喜欢的电影角色了?选择的文件格式很大,包括STL,OBJ,3DM,3DS,MAX。 3D模型愿与FDM,SLA,SLS和其他类型的3D打印机的使用,通过我们的选择,以便浏览和尝试新的最喜欢的戒指。\n","shoes":"大品牌板载的3D打印的鞋子,现在你可以分享经验,以及。浏览并找到免费打印鞋的设计 - 从运动鞋到高跟鞋的时尚也有很多现成的打印模式,在等待着你去尝试它们。通过选择材料,调整柔软性和尺寸使结果完美。选择从146免费3D靴款和下载他们在可用的文件格式,包括STL,OBJ,3DM,3DS,MAX。设计愿与FDM,SLA,SLS和其他类型的3D打印机的使用。让您的3D打印的鞋子吧!\n","textures":"中搜索%了大量 %{model_count} CG纹理可供下载,包括精美的制作材料。浏览类别,并找到无缝织物,木材,金属,塑料,砖纹理来帮助完成您的项目更快。使用我们的高级搜索找到游戏或视频创作作弊的和动画纹理。我们的材料可供下载的各种格式,包括TGA,SBSAR,JPG,PNG,TIFF和其他人,所以不要浪费时间,找到准备使用的CG贴图几乎所有项目可想而知。\n"},"various":"%{count} 个不同的3D模型不容易找到自己的位置在其他类别可供下载。 %{rocks_link},%{clouds_link},%{logos_link},%{cigarette_link}和其他非分类的3D模型,你可能需要完成你的游戏,动画或VR / AR项目都在这里。使用过滤器查找操纵,动画,低聚或免费的3D模型,或可用的格式如FBX,OBJ,MAX,3DS,C4D理清。","vehicle":"可供下载 %{count} 车辆3D模型包括卡车,%{ambulances_link},公共汽车%{bikes_link}和摩托 - 这个类别是为谁希望自己的城市街道充满车辆是否现实或科幻的人。那些制作模拟游戏或VR / AR应用,创意的视频制作,甚至海报设计很可能会发现最流行的文件格式,如FBX,OBJ,MAX,3DS,C4D使用。其他陆地交通工具像火车或%{tractor_link} 3D模型也可以这个庞大集合中找到。使用过滤器查找操纵,动画,低聚或免费的3D模型更快。","watercraft":"%{count} 个船舶可供下载的3D模型包含%{boats_link},%{yachts_link},%{ships_link}和各种其他水上行驶车辆。着装低多边形的模型将在视频游戏,VR / AR项目以及动画做的很好,而CG模型在这里为您的场景和其他应用中的视觉保真度比更重要。模型是包æ
16518‹¬ä»»ä½•文件格式提供,包括但不限于FBX,OBJ,MAX,3DS,C4D。"},"tag":{"meta":{"3d_merged_premium":"下载%{rounded_count}个%{tag_cap}3D模型,免费与付费。支持FBX、OBJ、BLEND、GLB格式,适用于游戏、影视、AR/VR和3D打印。","3d_merged_premium_print":"下载%{rounded_count}个%{tag_cap}3D打印模型。免费与付费STL文件,适用于FDM和光固化打印机。","3d_models":"下载%{rounded_count}个%{tag_cap}3D模型,免费与付费。支持FBX、OBJ、BLEND、GLB格式,适用于游戏、影视、AR/VR和3D打印。","3d_print_models":"下载%{rounded_count}个%{tag_cap}3D打印模型。免费与付费STL文件,适用于FDM和光固化打印机。","animated_3d_models":" %{count} %{tag}动画可供下载的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D 3D模型%{page}","free_3d_models":"下载 %{count} %{tag}免费的3D模型,在MAX,OBJ,FBX,3DS,C4D文件格式提供,准备VR / AR,动漫,游戏等3D项目。%{page}","free_3d_print_models":"下载 %{count} %{tag}免费的3D打印模型准备FDM,SLA,SLS等3D打印机,可用的格式STL,OBJ,3DM多。%{page}","lowpoly_3d_models":"下载 %{count} VR / AR /低多边形%{tag}的3D模型准备虚拟现实,游戏和其他实时应用程序,提供FBX,OBJ,MAX,3DS,C4D文件格式%{page}","rigged_3d_models":"%{count} %{tag}的操纵可供下载的任何文件格式,包括FBX,OBJ,MAX,3DS,C4D 3D模型%{page}"}},"title":{"download":"|下载","files":"3D文件","formats":"%{format_upper} 3D模型","free":"免费3D模型","free_printing":"免费3D打印模型","free_tag":"免费 %{selected_tag} 3D模型","general":"购买专业3D模型","high_poly":"%{selected_filter} 3D 模型","landing":{"animated_3d_models":"动画3D模型","lowpoly_3d_models":"低多边形3D模型","pbr_3d_models":"PBR 3D模型","rigged_3d_models":"绑定骨骼3D模型"},"low_poly":"%{selected_filter} 3D 模型","newest_cg":"新的和最新的3D模型","newest_print":"新的和最新的3D打印模型","page_prefix":"page %{page_number}","pbr":"基于物理的渲染 3D 模型","printing":"3D打印模型 – STL文件","sales_cg":"最畅销的3D模型","sales_print":"最畅销的3D打印模型","software_page":"%{software} 3D模型","tag_heading":{"cg":"免费%{tag} 3D 打印模型","filter":"%{selected_filter}%{tag} 的 3D 模型","general":"%{tag_cap}3D模型","merged_premium_print_tag":"%{tag_cap}3D打印模型","merged_premium_tag":"%{tag_cap}3D模型","printable":"%{tag_cap}3D打印模型"},"trending_cg":"趋势3D模型","trending_print":"趋势3D打印模型"}},"cart":{"request_invoice_failed":"哎呀,出错了。","request_invoice_successful":"发票请求已成功发送。","shareable_cart_created":"购物车已成功分享。","shareable_cart_failed":"哎呀,出错了。","subscription_covered_items_removed":"已从购物车中移除:%{titles}。已包含在您的订阅中。","subscription_covered_order_refused":"您的订阅现已包含 %{titles},因此我们未向您收费。请通过订阅下载。"},"categories":{"cg":{"header_title_html":"购买专业3D模型","text":"找到完全符合您需求的3D内容,包括AR/VR、游戏、广告、娱乐等\n"},"head-title":{"cg":"3D模型 – 免费下载与购买","cg-models":{"sort-by":{"newest":"最新3D模型","top-selling":"最畅销3D模型","trending":"热门3D模型"}},"print":"3D打印模型 – STL文件下载","print-models":{"sort-by":{"newest":"最新3D打印模型","top-selling":"最畅销3D打印模型","trending":"最热门3D打印项目"}},"product-type":{"plural":{"capital":{"cg":"3D模型","cg-merge-premium":"3D 模型 – 免费和付费下载","print":"3D打印文件和模型","print-merge-premium":"%{tag} 3D 打印文件 - 免费和高级 STL 模型"},"lower":{"cg":"3D模型","print":"3D打印文件和模型"}},"singular":{"capital":{"cg":"三维模型","print":"3D打印模型"},"lower":{"cg":"3D模型","print":"3D打印模型"}}}},"index":{"browse_all":"浏览所有模型","info-section__description_1_html":"购买或免费下载专业3D模型,可立即用于CG项目、影视制作、动画、可视化、游戏、VR/AR等。资源提供多种\u003ca href='https://www.cgtrader.com/blog/3d-file-formats' target='_blank' rel='noopener noreferrer'\u003e行业认可的格式\u003c/a\u003e,包括MAX、OBJ、FBX、3DS、STL、C4D、
16518BLEND、MA、MB等。如果您在寻找高多边形或实时3D资源,我们拥有领先的数字艺术库来满足您的所有需求。\n","info-section__description_1_printable_html":"专业的3D文件、打印件和模型,适用于使用桌面FDM或工业级SLS 3D打印机在家中打印,支持多种行业格式,包括OBJ、\u003ca href=\"https://www.cgtrader.com/3d-print-models/stl-files\"\u003eSTL\u003c/a\u003e、WRL。如果您是3D打印爱好者、创客、教育工作者或通过3D打印进行原型制作的企业,我们的家居装饰、时尚小工具配件、引人注目的时尚单品、独特的珠宝、机械部件以及各种微型模型,一定会给您带来灵感。","info-section__heading_1_html":"探索3D模型\u003cstrong\u003e类别\u003c/strong\u003e","info-section__heading_1_printable_html":"探索3D打印模型的\u003cstrong\u003e类别\u003c/strong\u003e","info-section__heading_2_html":"3D模型\u003cstrong\u003e市场\u003c/strong\u003e","info-section__heading_2_printable_html":"还有\u003cstrong\u003e什么?\u003c/strong\u003e"},"marketplace":{"3d-printing":{"link_text":"浏览3D打印模型","text":"查找适用于您的3D打印机的3D模型。","title":"3D打印模型"},"blender_text":"发现数千个可用于blend格式的Blender 3D模型。","browse":"浏览","cinema-4d_text":"发现数千个高质量Cinema 4D模型,可用于c4d文件格式。","discounted":{"link_text":"浏览贴现3D模型","text":"购买专业的3D模型打折。","title":"特价3D模型"},"low-poly":{"link_text":"浏览低多边形3D模型","text":"低多边形3D模型,适用于视频游戏、VR和AR实时应用。","title":"低多边形3D模型"}
16518,"pbr":{"link_text":"浏览PBR 3D模型","text":"基于物理渲染的3D模型。","title":"PBR 3D模型"},"scripts_apps":{"link_text":"浏览全部","text":"3D脚本和插件,让您的3D建模流程更高效。","title":"脚本与应用"},"sketchup_text":"浏览大量Sketchup 3D模型,适用于建筑、室内和视频游戏设计。","textures":{"link_text":"浏览纹理","text":"即用型纹理,帮您节省准备3D模型的时间。","title":"纹理"},"zbrush_text":"查找ZTL文件格式的3D资源,用于数字雕刻、3D建模、贴图和绘画项目。"},"meta-description":{"cg":"下载或购买%{rounded_count}个专业3D模型。免费与付费3D模型,适用于游戏、影视、VFX、AR/VR和3D打印。支持FBX、OBJ、BLEND、GLB格式。","print":"下载%{rounded_count}个3D打印模型。免费与付费STL文件:手办、模型、实用件等。"},"more":{"designers":{"link_text":"浏览3D设计师列表","text":"浏览数千个3D设计师作品集,找到完全符合您需求的内容。","title":"寻找最佳3D设计师"},"projects":{"link_text":"了解更多关于3D项目","text":"雇佣优质3D自由艺术家,解决您所有的定制建模挑战。","title":"CGTrader 3D项目"},"visualization":{"link_text":"了解更多","text":"将您的产品转换为照片级逼真的3D模型,用于电商和其他业务应用。","title":"电商3D可视化"}},"navbar":{"aircraft":"飞机","animal":"动物","animals":"动物","architectural":"建筑的","art":"艺术","car":"汽车","character":"字符","exterior":"外观","fashion":"时尚","food":"餐饮","furniture":"家具","gadgets":"小工具","games-toys":"游戏和玩具","hobby-diy":"嗜好DIY","house":"房屋","household":"家庭","industrial":"产业","interior":"室内","jewelry":"首饰","military":"军事","miniatures":"袖珍","plant":"厂","science":"科学","space":"空间","vehicle":"车辆","watercraft":"船舶"},"print_models":{"hero_title_html":"3D打印模型 – STL文件","subtitle":"找到完全符合您需求的3D打印内容","title_html":"购买专业的\u003cstrong\u003e3D打印模型\u003c/strong\u003e"},"profile":{"collection":"3D资源包","free":"免费的3D模型","high_poly":"高多边形3D模型","optimized":"低多边形/ Pbr 3D模型","printable":"3D打印模型"},"seo":{"cg":{"aircraft":"该类别涵盖3D飞机。CG飞机适用于模拟、可视化、广告和视频。无人机机身和零件将让小型飞行器爱好者满意。绑定模型可直接导入游戏引擎。\n","animals":"此处可找到3D动物合集——猫、马、熊、犀牛、鹰、狮子等。CG动物可满足广告或静态场景的需求ã€
16518‚我们也有用于游戏的绑定动物模型。\n","architectural":"楼梯、壁炉、3D模型以及装饰品(如画框、枕头或灯具)可在庞大的建筑细节模型库中找到。可利用室内外构建数字厨房、卧室、城堡房间或地牢,无论您是建筑师、工程师还是独立游戏开发者。\n","car":"汽车类别包含3D汽车模型,其中低多边形版本吸引游戏设计师,其余则适合法影像制作人或广告商。从经典汽车到赛车、概念车,各类3D汽车模型应有尽有。\n","character":"在此类别中,您将找到人类、人物、科幻、服装等3D模型。从心脏和手到详细逼真的男女模型。许多模型已绑定,可轻松添加到您的游戏或动画中。\n","electronics":"电子是建筑师,室内设计师,关卡设计师,现场制定者和其他广告类别。 { \"slug\": \"笔记本电脑\", \"title\": \"笔记本电脑\"},{ \"slug\": \"相机\",\"title\": \"智能手机\"}, {\"slug\": \"智能手机\", \"title\":\"相机\"},{\"slug\":\"TV\",\"title\":\"电视\"},更有助于准确地捕捉现代生活与设备和技术的痴迷。\n","engineering-parts":"窗,管道,所必需的CAD项目,甚至DIY计划推拉门机制和其他位属于这一类。在这里,您可以从全球制造商,如ANVER,纳特和FCI找到的3D模型。\n","exterior":"浏览建筑外观,从立面、城市建筑到景观对象、住宅、工业建筑以及全尺度3D城市模型。一些资源为低多边形,可帮助您完成游戏或VR/AR应用。\n","food":"食物有助于营造家庭生活的真实场景。3D水果、蔬菜、饮料和餐点都在这里,可用于餐厅广告或健康饮食的AR应用。无论您是在找3D披萨还是婚礼蛋糕模型,这里都有。\n","furniture":"3D桌子、椅子、沙发、床提供多种材质(木材、金属等)和风格(复古、现代等)。该类别适合建筑、室内设计及广告相关人员。\n","furniture-set":"如果你需要看起来都差不多的古董椅子和沙发看,或全套厨具,这是从厨房和客厅到办公室和食堂成套家具这一类收藏一系列的3D模型家具的地方。这有助于匹配的元素内部或级设计专家打造的房间。\n","household":"家居3D模型用于为房间增添细节。您可以在桌上放置3D餐具套装,或使用胡椒和盐罐来完善厨房,还可以在床边放置灯光和鲜花以营造奢华氛围。\n","industrial":"建筑材料、工程工具、机械和集装箱,既提供环境细节,也包含交互式绑定元素。非常适合模拟游戏开发者或制作施工场景的人。\n","interior":"本类别包含建筑、施工和室内设计的逼真3D元素与细节。您可以将这些3D模型用于动画或游戏,其中一些是绑定的且为低多边形。\n","military":"在此类别中,您可以购买武器、盔甲、剑和火炮的3D模型——包括现代和历史版本,用于军队或自卫。模型可用于游戏,其中一些已进行多边形优化并可直接用于游戏引擎。\n","plant":"此类别包含植物类3D模型,如花、树、草、藤蔓、绿植、草药、多年生植物、幼苗、嫩芽、灌木、插枝、芽、葡萄藤、杂草。要购买或免费下载3D模型,请浏览以下类别。\n","scanned":"建模通过使用​​3D扫描技术,如RealSense创建这些模型。树桩,雕像,甚至男性和女性在这里受欢迎。该产品是准备在场景和动画中使用,提供了现实主义的无与伦比的水平。\n","scie
16518nce":"这个类别包含涉及医药,生物,物理,化学和技术的3D模型,如实验室设备,显微组织和亚原子世界。不要再观望,{\"slug\":\"细胞\"},{\"slug\":\"骷髅\"}和{\"slug\":\"骨架\"} 3D模型可以在这里找到。\n","scripts-plugins":"脚本和插件为您提供软件的快捷方式。建模,渲染,纹理 - 这些都可以通过插件,来像Maya和的V-Ray各种软件变得更容易。\n","space":"太空类别充满了低多边形、游戏就绪的科幻飞船、小行星、行星(包括地球和为科学爱好者准备的逼真航天飞机)。3D太空模型对VR太空模拟游戏很有用。\n","sport-hobby":"此类别encompases任何与{\"slug\":\"田\"}一般事件,体育和爱好。从{\"slug\":\"足球\"}和滑雪斜坡{\"slug\":\"吉他\"}和{\"slug\":\"钢琴\"},这些都是有用的3D模型的广告,场景和动画。低多边形版本有体育视频游戏。\n","textures":"浏览一个精美制作的3D材质库,找到最合适的即用型纹理,用于准备游戏就绪或照片级逼真的模型,升级您的资源库并改进您的3D作品。\n","various":"这些都是3D模型不容易融入其他类别,所以我们创建了一个会接受他们。 { \"title\": \"岩石\", \"slug\": \"岩石\"},{ \"slug\": \"云\"},{ \"title\": \"标志\", \"slug\": \"标志\"},{ \"title\":\"伞\",\"slug\":\"保护伞\"}和其他非分类3D模型都在这里。\n","vehicle":"卡车、救护车、公交车、火车、拖拉机、自行车和摩托车——本类别适合希望让城市街道充满真实与科幻车辆的人,以及制作模拟游戏或海报的用户。\n","watercraft":"水上交通工具类别包括船只、游艇、舰船等。绑定的3D模型适合用于视频游戏和动画,而CG模型适合用于对视觉逼真度要求更高的场景或其他应用。\n"},"forwarded":{"cg":{"aircraft":"此类别包括3D飞机。 CG {\"slug\": \"aircraft\",\"title\":\"飞机\"}将适用于模拟,可视化,广告和视频。 {\"slug\": \"drone\",\"title\":\"雄蜂\"}机构和部分将满足微小空中运输工具的球迷。和{\"slug\": \"aircraft\",\"title\":\"造模型\",\"active_filter\":\"rigged\"}
16518准备导入到游戏引擎。\n","animals":"3D动物的集合 -  {\"slug\": \"cat\",\"title\": \"猫\"},{\"slug\": \"horse\",\"title\": \"马\"},{\"slug\": \"bear\",\"title\":\"熊\"},{\"slug\": \"rhino\",\"title\":\"犀牛\"},{\"slug\": \"eagle\",\"title\":\"鹰\"},{\"slug\": \"lion\",\"title\":\"狮子\"}多 - 可以在这里找到。该CG 3D动物将满足广告或静态场景的需求。我们也有{\"slug\": \"animal\",\"title\":\"造动物\",\"active_filter\":\"rigged\"}模型的游戏。\n","architectural":"{ \"slug\": \"stairs\", \"title\": \"楼梯\"},{ \"slug\": \"fireplace\", \"title\": \"壁炉\"},3D模型以及像{ \"slug\": \"frame\", \"title\": \"帧\"}装饰品,{ \"slug\": \"pillow\", \"title\": \"枕\"}或{\"slug\": \"lamp\", \"title\": \"灯\"}可以在广阔的建筑细节轿车类别中找到包含汽车3D模型,{\"slug\": \"car\",\"title\":\"低聚\",\"active_filter\": \"low_poly\"}模型库。内部和外部可用于无论你是一名建筑师,工程师或独立游戏开发商建立了数字的厨房和卧室以及房间的城堡和地牢。\\n","car":"汽车类别包括汽车3D模型,其中\u003ca href=\"%{link_to_low_poly_items_url}\"\u003e低聚\u003c/A\u003e版本将是游戏设计者的吸引力,其他人 - 电影制造商或广告商。从老爷车到赛车和轨道车的概念的,有种类繁多的3D汽车模型。\n","character":"在这个类别中,你会发现3D模型 {\"slug\": \"human\", \"title\": \"人\"} 的,人,科幻,服装等。从 {\"slug\": \"heart\", \"title\": \"心脏\"}和{\"slug\": \"hand\", \"title\":\"手\"},以细致逼真 {\"slug\": \"male\", \"title\":\"男\"} 或 {\"slug\": \"female\", \"title\":\"女\"} 模型。许多机型都被操纵,并且可以很容易地添加到您的游戏或动画。\n","electronics":"电子是建筑师,室内设计师,关卡设计师,现场制定者和其他广告类别。 { \"slug\": \"smartphone\", \"title\": \"笔记本电脑\"},{ \"slug\": \"laptop\",\"title\": \"智能手机\"}, {\"slug\": \"camera\", \"title\":\"相机\"},{\"slug\": \"tv\",\"title\":\"电视\"},更有助于准确地捕捉现代生活与设备和技术的痴迷。\n","engineering-parts":"窗,管道,所必需的CAD项目,甚至DIY计划推拉门机制和其他位属于这一类。在这里,您可以从全球制造商,如ANVER,纳特和FCI找到的3D模型。\n","exterior":"从外墙浏览建筑外墙,城市建筑景观对象,住宅,工业建筑,和满量程3D {\"slug\": \"city\", \"title\": \"城\"}模型。有些资产是低聚和可以帮助你完成你的游戏或VR / AR应用。\n","food":"食品有助于打造国内生活的逼真的场景。 3D水果,蔬菜,饮料和餐点都在这里。广告餐馆,为健康的饮食AR应用。无论您正在寻找3D {\"slug\": \"pizza\", \"title\":\"比萨\"}或婚礼{\"slug\": \"cake\", \"title\":\"蛋糕\"}模型,他们在这里。\n","furniture":"3D桌子,椅子,沙发,在各种材质(木材,金属等)和样式({\"slug\": \"retro\", \"title\":\"复古\"},{\"slug\": \"modern\", \"title\":\"现代\"}等)的床是导通手。这个类别是为参与建筑和室内设计,以及广告主的人。\n","furniture-set":"如果你需要看起来都差不多的古董椅子和沙发看,或全套厨具,这是从厨房和客厅到办公室和食堂成套家具这一类收藏一系列的3D模型家具的地方。这有助于匹配的元素内部或级设计专家打造的房间。\n","household":"家居用品3D模型是用来给细节的房间。你可以把3D餐具放在桌上,或有胡椒和盐的容器,以完整的厨房内,可能把一些{\"slug\": \"light\",\"title\":\"灯\"}和{\"slug\": \"flowers\", \"title\":\"花\"}床边营造豪华的感觉。\n","industrial":"建筑材料,{\"slug\": \"engineering\", \"title\":\"工程\"}
16518工具,机械{\"slug\": \"container\",\"title\":\"容器\"}同时提供环境信息和互动,操纵元素。这是完美的模拟游戏开发商或建筑的人{\"slug\": \"construction\", \"title\":\"建设\"}院子里的水平。\n","interior":"这个类别包含逼真的3D元素与建筑,建筑与工程内部设计的细节。您可以使用这些3D模型,动画或游戏 - 其中一些是操纵和低聚。\n","military":"在这个类别中,你可以买到3D模型的武器,武器,{\"title\":\"剑\",\"slug\": \"sword\"}由军队和自卫使用和火炮现代和历史。您可以使用模型游戏 - 他们中的一些优化,并准备游戏引擎的多边形。\n","plant":"这个类别包含3D模型植物生物,如花卉,{\"slug\": \"tree\",\"title\":\"树\"},草,攀缘植物,绿化,草本,多年生植物,苗,芽,灌木,单,豆芽,藤蔓,杂草。购买或下载免费的3D模型,浏览以下类别。\n","scanned":"建模通过使用​​3D扫描技术,如RealSense创建这些模型。树桩,雕像,甚至男性和女性在这里受欢迎。该产品是准备在场景和动画中使用,提供了现实主义的无与伦比的水平。\n","scie
16518nce":"这个类别包含涉及医药,生物,物理,化学和技术的3D模型,如实验室设备,显微组织和亚原子世界。不要再观望,{\"slug\": \"cell\",\"title\":\"细胞\"},{\"slug\": \"skull\",\"title\":\"骷髅\"}和{\"slug\": \"skeleton\",\"title\":\"骨架\"} 3D模型可以在这里找到。\n","scripts-plugins":"脚本和插件为您提供软件的快捷方式。建模,渲染,纹理 - 这些都可以通过插件,来像Maya和的V-Ray各种软件变得更容易。\n","space":"空间是一个完整的低聚,科幻{\"slug\": \"spaceship\",\"title\":\"飞船\"}游戏准备的车型类别,{\"slug\": \"asteroid\",\"title\":\"小行星\"},行星(包括{\"slug\": \"earth\",\"title\":\"地球\"}和现实空间{\"slug\": \"shuttle\",\"title\":\"航天飞机\"}为更科学地倾斜3D空间模型对VR空间模拟器游戏有用。\n","sport-hobby":"此类别encompases任何与{\"slug\": \"field\",\"title\":\"田\"}一般事件,体育和爱好。从{\"slug\": \"football\",\"title\":\"足球\"}和滑雪斜坡{\"slug\": \"guitar\",\"title\":\"吉他\"}和{\"slug\": \"piano\",\"title\":\"钢琴\"},这些都是有用的3D模型的广告,场景和动画。低多边形版本有体育视频游戏。\n","textures":"浏览精美制作的3D材料库,并找到最适合的,准备使用的纹理准备\u003ca href=\"%{link_to_low_poly_items_url}\"\u003e游戏准备\u003c/a\u003e或逼真的模型,升级你的资产库,并提高您的3D作品。\n","various":"这些都是3D模型不容易融入其他类别,所以我们创建了一个会接受他们。 { \"title\": \"岩石\", \"slug\": \"rocks\"},{ \"slug\": \"clouds\",\"title\": \"云\"},{ \"title\": \"标志\", \"slug\": \"logo\"},{ \"title\":\"伞\",\"slug\": \"umbrella\"}和其他非分类3D模型都在这里。\n","vehicle":"卡车,{\"title\":\"救护车\",\"slug\": \"ambulance\"},公共汽车,火车,拖拉机,{\"title\":\"自行车\",\"slug\": \"bike\"}和摩托车 - 这个类别是为谁希望自己的城市街道上的人们充满了现实和科幻车辆,并为那些制作模拟游戏或在格式化的海报。\n","watercraft":"船舶井盖{ \"title\": \"船\", \"slug\": \"ship\"},{ \"title\": \"游艇\", \"slug\": \"yacht\"},{ \"title\": \"船\", \"slug\": \"ship\"}等水上交通工具。着装的3D模型将在视频游戏,动画做的很好,而CG模型是有你的场景或其他应用中的视觉保真度比polycount更重要。\n"},"printable":{"art":"这就是艺术的三维打印去。寻找花哨的雕塑设计再现历史人物的经典作品和雕像,或鼓舞人心俑,甚至3D打印的标志?这是类别。\n","fashion":"你可以穿上漂亮和时尚的东西,这个类别中被发现。看看你能不能找到任何使用花式太空时代{\"slug\": \"footwear\",\"title\":\"鞋类\"},新奇眼镜,角色扮演{\"slug\": \"accessories\",\"title\":\"配件\"}在这里找到。\n","gadgets":"所有{ \"title\": \"iPhone\", \"slug\": \"iphone\"},{ \"title\": \"iPad的\", \"slug\": \"ipad\"}(和其他智能设备)的情况下可以在小工具类中找到。从简单的3D附加组件到复杂的零件和\"黑客\"为您的项目,这是为广告素材类别和技术爱好者。\n","games-toys":"游戏和玩具是覆盖打印3D外星人或怪物微缩模型类,微小{\"title\":\"坦克\",\"slug\": \"tank\"},时髦{\"slug\": \"chess\",\"title\":\"国际象棋\"}片,电影里的人物头盔,业余爱好用具和配件。这是对-GO类别中所有的东西书呆子。\n","hobby-diy":"如果你是我们的下一个伟大的发明家,并希望建立自己的DIY项目或得到一些零部件为您{\"slug\": \"drone\",\"title\":\"无人驾驶\"},这是要去的地方。此外,它的功能谁想要让自己的雕像或塑料枪复制品的人很多东西。\n","house":"小摆设的各种可以在这里找到。 {\"title\":\"灯\",\"slug\": \"lamp\"},艺术{\"title\":\"碗\",\"slug\": \"bowl\"},以及各类{\"title\":\"持有人\", \"slug\": \"holder\"}是否找不到您的手机右侧的点,保持牙签,堆在桌上的笔或珠宝的顺序。\n","jewelry":"珠宝是你去哪里获得的手镯和吊坠或钻石戒指。虽然3D打印机都不够好,使钻石 - 但 - 你仍然可以打印出一个金属环为您的IT怪胎提案环的基础。\n","miniatures":"3D打印{ \"title\": \"人物\", \"slug\": \"character\"},名人,{ \"title\": \"地标\", \"slug\": \"landmark\"},动物,{ \"title\":\"男\",\"slug\": \"male\"}
16518,{ \"slug\": \"female\", \"title\": \"女\"},{ \"title\": \"怪物\", \"slug\": \"monster\"},{ \"title\": \"外国人\",\"slug\": \"alien\"}  - 这一切都在这里。无论您是打印多功能车,把你的表或为您的桌面游戏中制作的微缩模型,这是你的类别。\n","science":"有什么可以被归类为科学?那么,骨架{\"slug\": \"bones\",\"title\":\"筋骨\"}的车型,医生{\"slug\": \"tools\",\"title\":\"工具\"}和天体物理学相关3D,可以在许多研究过程中使用的打印模式。\n"}},"printable":{"art":"这就是艺术的三维打印去。寻找花哨的雕塑设计再现历史人物的经典作品和雕像,或鼓舞人心俑,甚至3D打印的标志?这是类别。\n","fashion":"你可以穿上漂亮和时尚的东西,这个类别中被发现。看看你能不能找到任何使用花式太空时代{\"slug\":\"鞋类\"},新奇眼镜,角色扮演{\"slug\":\"配件\"}在这里找到。\n","gadgets":"所有{ \"title\": \"iPhone\", \"slug\": \"iphone\"},{ \"title\": \"iPad的\", \"slug\": \"iPad的\"}(和其他智能设备)的情况下可以在小工具类中找到。从简单的3D附加组件到复杂的零件和\"黑客\"为您的项目,这是为广告素材类别和技术爱好者。\n","games-toys":"游戏和玩具是覆盖打印3D外星人或怪物微缩模型类,微小{\"title\":\"坦克\",\"slug\":\"坦克\"},时髦{\"slug\":\"国际象棋\"}片,电影里的人物头盔,业余爱好用具和配件。这是对-GO类别中所有的东西书呆子。\n","hobby-diy":"如果你是我们的下一个伟大的发明家,并希望建立自己的DIY项目或得到一些零部件为您{\"slug\":\"无人驾驶\"},这是要去的地方。此外,它的功能谁想要让自己的雕像或塑料枪复制品的人很多东西。\n","house":"小摆设的各种可以在这里找到。 {\"title\":\"灯\",\"slug\":\"灯\"},艺术{\"title\":\"碗\",\"slug\":\"碗\"},以及各类{\"title\":\"持有人\", \"slug\":\"持有人\"}是否找不到您的手机右侧的点,保持牙签,堆在桌上的笔或珠宝的顺序。\n","jewelry":"珠宝是你去哪里获得的手镯和吊坠或钻石戒指。虽然3D打印机都不够好,使钻石 - 但 - 你仍然可以打印出一个金属环为您的IT怪胎提案环的基础。\n","miniatures":"3D打印{ \"title\": \"人物\", \"slug\": \"字符\"},名人,{ \"title\": \"地标\", \"slug\": \"地标\"},动物,{ \"title\":\"男\",\"slug\": \"男性\"},{ \"slug\": \"女性\"},{ \"title\": \"怪物\", \"slug\": \"怪物\"},{ \"title\": \"外国人\",\"slug\":\"外星人\"}  - 这一切都在这里。无论您是打印多功能车,把你的表或为您的桌面游戏中制作的微缩模型,这是你的类别。\n","science":"有什么可以被归类为科学?那么,骨架{\"slug\":\"筋骨\"}的车型,医生{\"slug\":\"工具\"}和天体物理学相关3D,可以在许多研究过程中使用的打印模式。\n"},"titles":{"aircraft":"飞机3D模型","animals":"动物3D模型","architectural":"建筑3D模型","car":"汽车3D模型","character":"人物3D模型","exterior":"外观3D模型","food":"食物3D模型","furniture":"家具3D模型","household":"家居3D模型","industrial":"工业3D模型","interior":"室内3D模型","military":"军事3D模型","plant":"植物3D模型","space":"太空3D模型","textures":"CG贴图3D模型","vehicle":"交通工具3D模型","watercraft":"水上交通工具3D模型"}},"title":{"cg":" 3D模型","print":" 3D打印文件"}}
16518,"competition":{"digital-art-competition":{"categories":{"page_description":"提交您的作品角色插画,角色概念设计,环境插图,环境概念设计,对象设计和对象概念设计类别参加CGTrader数字艺术大赛。如果您有适合不同类别的几个伟大的作品,随意将其提交到相应的类别。选择一个类别,以查看该类别的奖项。"}}},"conversations":{"action_button_confirmation":"您确定要重新打开此请求吗?","reply_posted":"回复已发布。","with_yourself":"不能与发送者相同。"},"date":{"month_names":[null,"一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"]},"devise":{"failure":{"too_many_requests":"您已请求发送密码重置邮件。请稍等几分钟后再试。"}},"disabled_cookies_modal":{"button":"取消","text_1":"您将无法浏览数以百万计的巨大的三维模型,将其添加到购物车或购买。","text_2":"如果你想充分体验CGTrader,我们鼓励在这个网站上启用Cookie。","title":"看来你有你的浏览器的cookie禁用"},"emails":{"error_notification":"未找到订阅。","success_subscribed_notification":"您已成功订阅。","success_unsubscribed_notification":"您已成功退订。"},"file_size_validator":{"errors":{"invalid_carrierwave":"预期应为 CarrierWave::Uploader::Base 对象","invalid_key":":%{key} 必须是非负整数","range_format":":in 和 :within 必须是一个范围 (Range)","range_unspecified":"未指定范围。请指定 :within、:maximum、:minimum 或 :is 选项。"}},"file_type":{"default_error":"未提供文件路径。"},"footer":{"bottom-row":{"privacy":"隐私","terms-link":"条款与条件"},"links":{"buyLinks":[{"text":"购买3D模型","link":"/zh-cn/3d-models"},{"text":"企业版","link":"/discover/business-account"},{"text":"免费3D数据集","link":"/discover/datasets"},{"text":"聘请3D设计师","link":"/3d-modeling-jobs"},{"text":"免费3D模型","link":"/zh-cn/free-3d-models"},{"text":"免费3D打印模型","link":"/zh-cn/free-3d-print-models"}],"communityLinks":[{"text":"社区","link":""},{"text":"优秀设计师","link":"/designers"},{"text":"挑战赛","link":"/challenges"},{"text":"论坛","link":"/forum"}],"companyLinks":[{"text":"公司","link":"/discover/about-us"},{"text":"博客","link":"/blog"},{"text":"职业","link":"/pages/careers"},{"text":"帮助中心","link":"https://help.cgtrader.com"}],"sellLinks":[{"text":"出售3D模型","link":"/discover/sell-3d-models"},{"text":"提升销量","link":"/discover/sell-more"},{"text":"3D市场洞察","link":"/discover/3d-market-trends"},{"text":"热门3D模型搜索","link":"/discover/3d-models-top-searches"},{"text":"热门3D打印搜索","link":"/discover/3d-print-models-top-searches"}]},"subscribe":{"button-active":"订阅","button-disabled":"已订阅","header":"订阅我们的新闻:","placeholder":"输入您的电子邮箱","terms-link":"使用条款","text":"通过订阅您确认您已经阅读并接受我们的"}},"general":{"description":"CGTrader是一个在线市场,3D艺术家和设计师可以购买,出售或共享3D模型。它成立,目前正在由一个团队的3D设计师和工程师开发的。我们的目标是民主化3D股市和改变我们的在线销售的3D模型的方式。可让作者获得最大的特许权使用费,我们努力提高他们的积极性,并提高3D模型的质量。","navigation":{"buy_3d_models":"购买3D模型","sell_3d_models":"出售3D模型"},"search_placeholder":"在超过200万个模型中搜索","site-search__field":"搜索%{model_count}个3D模型","site_title":"三维模型店","site_title_postfix":" | CGTrader"},"generator":{"landing_pages":{"three_d_model":{"defaults":{"closing_cta":{"heading":"让AI实现您的梦想","subtitle":"将提示词或参考图像转化为生产就绪的3D模型,然后细化、重新纹理化并以您需要的格式导出。"},"faq":{"heading":"常见问题","items":{"commercial":{"answer":"您的%{object}三维模型的商业使用可在CGTrader AI付费计划中获得。请查阅CGTrader条款 和条件,了解适用于AI生成模型的许可条款。","question":"我可以将生成的%{object}模型用于商业目的吗?"},"formats":{"answer":"%{object}的3D模型提供GLB、FBX、OBJ、STL或USDZ格式。","question":"我可以以何种文件格式获取%{object}模型?"},"no_skills":{"answer":"无需任何3D建模知识。所有建模均通过%{object}模型生成器完成。当您输入提示词或上传 图像时,系统会自动处理几何形状、比例和结构。即使是初学者也能在几分钟内创建完整的 %{object}三维模型。","question":"使用%{object}
16518模型生成器需要3D建模知识吗?"},"what_is":{"answer":"CGTrader %{object}三维模型生成器是一款基于AI的工具,可根据文字指令或参考图像创建 %{object}三维模型。它分析您的想法,为设计、原型制作和可视化生成可下载模型。我们的 付费订阅提供更高的月度限额。","question":"什么是%{object}三维模型生成器?"},"why_cgtrader":{"answer":"用户可以信赖CGTrader提供流畅的体验、快速的处理速度和高质量的3D输出。该工具适合专 业人士和初学者,为创建%{object}三维模型提供可靠的解决方案。","question":"为什么使用CGTrader的AI %{object}生成器?"}}},"h1":"AI %{object}三维模型生成器","hero_alt":"AI %{object}三维模型生成器","hero_height":617,"hero_width":1080,"how_to":{"heading":"从图像或文本提示生成%{object}","paragraphs":["设计%{object}三维模型非常简单。在左侧边栏的左上角,点击按钮上传您的%{object}图像以 构建3D模型。用户也可以在底部的\"描述您想创建的内容\"字段中输入提示词。CGTrader AI会为 您优化提示词,因此简短的描述即可开始。","右侧边栏让您对%{object}三维模型拥有良好的控制。选择网格类型、材质、提示引导和文件 格式等设置。您还可以通过纹理自定义%{object}模型。输入文字说明或图像后,点击\"生成\"。 该工具将在几分钟内处理您的输入,具体时间取决于系统负载、材质、网格类型、形状、种子 值和质量。"],"steps":["输入文字描述并/或附上参考图像,以提高%{object}三维模型的最终精度。","选择我们的预设之一,或选择AI引擎、自定义多边形数量,并为您的%{object}三维模型选择材质类型。","点击\"生成\",从您的提示词和/或参考图像创建%{object}三维模型。","以不同格式(GLB、FBX、OBJ、STL或USDZ)下载您创建的%{object}三维模型。"]},"intro":"您是否在寻找一种简单快捷的方式,在几分钟内创建高质量的%{object}三维模型?我们先进的 %{object}三维模型生成器CGTrader AI可将文字指令或参考图像转化为完全可用的模型。它基于 包括Meshy、Hunyuan和Tripo3D在内的领先3D生成引擎运行,帮助设计初学者和专家快速创建用 于可视化、实验和原型设计的3D模型。支持文本转3D和图像转3D,并兼容3D打印机。您可以上传 JPG或PNG格式的图像,也可以以文字形式输入描述,CGTrader AI将为您完成后续步骤。","meta_description":"CGTrader AI %{object} 三维模型生成器 ☑️ 通过文字或图像生成精细的 %{object} 三维模型。立即免费开始。","primary_cta":"免费开始生成","title":"AI %{object}三维模型生成器(免费)| CGTrader"}}},"rules":{"errors":{"front_view_required":"需要正视图图片。"}}},"home":{"index":{"3D-projects":{"info-section__cta":"了解更多","info-section__description":"在这里,好的3D项目会遇到有才华的3D设计师","info-section__heading_html":"\u003cstrong\u003e 3D项目\u003c/strong\u003e特约中心"},"categories-conatainer":{"browse_all":"浏览所有模型","browse_all_tooltip":"浏览所有3D模型","discounts":"折扣"},"cgdream":{"cta":"免费入门!","heading_html":"\u003cstrong\u003e探索 CGDream,\u003c/strong\u003e\u003c/br\u003e 在这里 AI 与 3D 模型相遇。","text_html":"\u003cstrong\u003e CGDream \u003c/strong\u003e是一种尖端应用,它利用生成AI和3D模型的功能来产生惊人的2D图像。 \u003cstrong\u003e 您的创意旅程现在开始。\u003c/strong\u003e"},"community":{"info-section__cta":"加入社区","info-section__heading_html":"加入CGTrader的\u003cstrong\u003e社区\u003c/strong\u003e","info-section__icon-list_1":{"description":"展现您的最佳渲染,并获得反馈和认可","heading":"作品馆"},"info-section__icon-list_2":{"description":"学习新技能并教授他人","heading":"教程"},"info-section__icon-list_3":{"description":"查找教程和讨论其他3D相关事宜","heading":"论坛"}},"covid":{"timer-strip__item":"COVID-19防护的免费3D打印资源","timer-strip__item--button":"浏览3D模型"},"enterprise":{"info-section__cta":"了解更多","info-section__heading_html":"\u003cstrong\u003e企业\u003c/strong\u003e的3D服务","info-section__icon-list_1":"我们可以做的AR实现了你","info-section__icon-list_2":"更多的销售转化率,更少的产品退货","info-section__icon-list_3":"严格的质量保证"},"header":"购买专业3D模型","marketplace":{"info-section__description":"从%{models_count}个3D模型中挑选,满足游戏、VR/AR、广告、动画、影视和3D打印等不同应用需求。\n","info-section__heading_html":"3D模型\u003cstrong\u003e市场\u003c/strong\u003e","info-section__image-list_1":{"de
16518scription":"为您的项目购买专业的3D模型","heading":"3D模型"},"info-section__image-list_2":{"description":"低多边形PBR 3D模型,适用于视频游戏、VR和AR实时应用","heading":"低多边形PBR 3D模型"},"info-section__image-list_3":{"description":"为您的3D打印机查找3D打印设计","heading":"3D打印模型"},"view_all":"浏览所有模型"},"search_dropdown":{"ai_cards":{"image_to_3d":{"description":"将图像转换为3D模型","title":"图像转3D"},"text_to_3d":{"description":"描述您的创意以创建3D模型","title":"文本转3D"}},"ai_section_title":"用AI创建","aria_label":"搜索建议","columns":{"cg":{"search_label":"在3D模型中搜索","title":"3D模型"},"print":{"search_label":"在3D打印中搜索","title":"3D打印模型"}}},"sell":{"info-section__heading_html":"\u003cstrong\u003e销售\u003c/strong\u003e您的3D模型,并寻找特约项目","info-section__icon-list_1":{"description":"出售您的3D模型,并赚取高达85%的特许权使用费","link":"出售3D模型"},"info-section__icon-list_2":{"description":"与企业客户合作","link":"获得资格"},"info-section__icon-list_3":{"description":"外包您的技能并获得高达95%的特许权使用费","link":"浏览3D建模工作"},"info-section__icon-list_4":{"description":"构建自己的组合并使其可见","link":"浏览3D设计师"}},"seo":{"description":"探索2,390,000个在线3D模型,适用于打印、游戏、VR/AR和设计项目。在CGTrader上下载、定制或雇佣3D专家。","title":"CGTrader - 3D模型和3D打印市场"}}},"homepage":{"budget_counter":{"range_0_10":"10美元以下","range_10_20":"10美元 - 20美元","range_20_15000":"20美元以上"},"quotes":{"quote_0":{"author":{"name":"胡安·卡洛斯·希门尼斯","username":"CGShape"},"text":"从销售方收取的佣金越低,对购买方来说价格就越好。查看并比较CGTrader和其他商店的价格。"},"quote_1":{"author":{"name":"胡安·卡洛斯·希门尼斯","username":"CGShape"},"text":"CGTrader最显著的优势是提供高回报的固定费用激活系统。你们不会对我们的销售额收取佣金。出售产品后,我们可以得到100%价格的收入。只有当我们想继续销售该产品时,我们才需要支付5美元再次激活它。完美的解决方案。"},"quote_10":{"author":{"name":"Threed肯","username":"3DKen"},"text":"固定收费激活系统非常巧妙。我想知道为什么以前没人想到这个。在销售产品之后,通过支付一些钱来激活产品。这样卖家就可以完全掌控。"},"quote_11":{"author":{"name":"希德·耐克","username":"sidnaique"},"text":"CGTrader有两个东西我最喜欢。首先,这里没有任何隐藏的东西,你不需要小心CGTrader。第二,这家商店是由艺术家和艺术家开的,他们更了解你。如果有什么事困扰着你,那就让他们知道,他们会保证你在这里感到非常舒适。"},"quote_12":{"author":{"name":"希德·耐克","username":"sidnaique"},"text":"固定收费激活系统是最好的!(至少我没有遇到比这更好的)。这对卖方来说是非常现实和公平的。他们不在乎你赚多少钱,只要付固定租金就行了。但如果你没有任何销售,你也不必支付租金。公平的CG贸易就在这里!"},"quote_13":{"author":{"name":"莱昂德罗·萨勒诺","username":"leansaler"},"text":"CGTrader甚至吸引了发展中国家的客户,因为价格比其他商店便宜。同时,作者可以赚更多的钱,因为不存在高佣金率的问题。这是一个正确的方式来为3D艺术家的工作定价。"},"quote_14":{"author":{"name":"莱昂德罗·萨勒诺","username":"leansaler"},"text":"发布者的直观界面和创新工具,如标签推荐和灵活的描述表格,使发布过程简单快捷。"},"quote_15":{"author":{"name":"哈维尔·阿尔马佐尔","username":"jvr3D"},"text":"CGTrader销售3D的方法不同,与我以前在其他商店销售没有任何相同之处。在出售产品后付5美元是个好的想法,可以让我在这里提供最低的价格。这种工作方式使顾客和艺术家都受益。"},"quote_16":{"author":{"name":"哈维尔·阿尔马佐尔","username":"jvr3D"},"text":"上传系统简单明了。我很高兴了解到这个项目的幕后运作者是在计算机图形方面有多年经验的人。任何时候当我有问题,CGTrader团队都会很快解决。"},"quote_17":{"author":{"name":"哈维尔·阿尔马佐尔","username":"jvr3D"},"text":"CGTrader允许我将我的个人资料链接到我的个人网页。如果客户不确定要购买的商品的特性或只是想对商品进行修改,就可以直接联系我。就像在一个大市场上拥有一家个人的3D商店!"},"quote_2":{"author":{"name":"胡安·卡洛斯·希门尼斯","username":"CGShape"},"text":"我们得到了CGTrader团队的大力支持,工作人员很乐意为我们提供所有模型。这对专业卖家很有帮助。"},"quote_3":{"author":{"name":"胡安·卡洛斯·希门尼斯","username":"CGShape"},"text":"在这个平台上我们可以为客户提供更好的价格。例如,我们所有产
16518品的价格都比其他任何地方低20%,但我们仍然可以获得比其他商店更多的利润!"},"quote_4":{"author":{"name":"赫苏斯·佩德罗萨维达尔","username":"NoneCG"},"text":"这是迄今为止最好的系统。其他所有的在线3D商店都会拿走我们的部分利润。这也是我们一开始对CGTrader感兴趣的原因。在CGTrader上的买方客户知道,他们所付的钱大部分给到了产品的作者。当作者们的利润提高了,他们最终会制作出更多更好的模型。"},"quote_5":{"author":{"name":"赫苏斯·佩德罗萨维达尔","username":"NoneCG"},"text":"CGTrader是一家在线3D商店,为我们带来了最快的销售和利润增长。固定费用激活系统是你能找到的最好的的系统,因为它奖励内容创作者而不是中间商。"},"quote_6":{"author":{"name":"RičardasMarozas","username":"消防车"},"text":"当我的Paypal帐户收到160美元,我还不知道是怎么回事,因为之前从来没发生过。但转念一想,这可能是我在CGTrader的销售收入,后来证明我的猜测是对的。上个月我在Turbosquid上以189美元的价格卖出了同样的产品,但在交了40%的版税和30%的其他税之后,我只拿到了52.92美元,而在CGTrader的收入是160美元。而且客户还少付了29美元,所以现在才是一个正常的生意。"},"quote_7":{"author":{"name":"Threed肯","username":"3DKen"},"text":"起初我对这样一笔好买卖持怀疑态度,因为这对于大型在线3D商店来说并不正常。当我了解到固定收费激活系统时,我对这种销售数字资产的创意方式印象深刻。产品出售后,然后支付5美元的激活费用。这对我太有意义了。我上传了一些作品进行尝试,真的,当我卖出第一单时,我拿到了全部价格的收入!"},"quote_8":{"author":{"name":"Threed肯","username":"3DKen"},"text":"我习惯了其他网站收取30%-60%的佣金,直到我发现了CGTrader。作为一个卖家,我太喜欢高比例的版税回报了。"},"quote_9":{"author":{"name":"Threed肯","username":"3DKen"},"text":"卖家的体验真的太棒了。提交产品很容易。而且系统允许我在每个阶段保存产品提交的草稿。"}}},"hyperwallet":{"failed_payment_agreement":"发生了意外错误","success_notification":"支付协议已成功更新。","success_payment_agreement":"支付协议已成功提交。"},"item-search-filters":{"3d-models-url":"3d-models","3d-print-models-url":"3d-print-models","dedicated-search-page-url":{"animated":"/animated-","free":"/free-","low_poly":"/low-poly-","pbr":"/pbr-","rigged":"/rigged-"}},"items":{"already_purchased":"您已经购买了此商品。","filters":{"attributes":"属性","free":"自由","max_value":"$ 500+","min_value":"$ 2","reset":"复位过滤器"},"footer":{"next":"下一个","per_page":"每页:","previous":"以前"},"geometry_type_forbidden":"几何类型被禁止!","last_card":{"button_text":"要求自订的3D模型","text":"找不到你在寻找什么?"},"migrations":{"errors":{"invalid_owner":"新所有者无法接管物品。","unpublished_items":"所有物品必须发布。"}},"new_price_applied":"新价格已成功应用于所有商品。","new_price_applied_to_selected_items":"新价格已成功应用于所选商品。","new_prices_to_filtered_models":"新价格已成功应用于所有筛选的商品。","order_processed":"订单已处理。","order_processing":"订单处理中。","something_went_wrong":"出现错误","title_error":"标题不应包含非拉丁符号","uniq_user":"已经为该商品提交了使用案例","upload_error":"上传错误。","validate_license_for_zero_price":"对于免费商品无效","validate_minimum_price":"出价不得低于原价的 50%","verify_printable_formats_error":"请至少上传一个 .stl、.obj 或 .wrl 文件。","you_can_not_delete_models":"您不能删除模型"},"jobs":{"about":{"hire_designers_page":{"hero":{"description":"3D Projects 让您即可雇佣优质的 3D 自由职业艺术家,解决所有建模难题。","heading_html":"体验最佳自由职业平台,轻松应对您的\u003cstrong\u003e3D建模项目\u003c/strong\u003e"},"sections":{"call_to_action":{"description":"免费发布您的项目,看看通过3D项目解决下一个建模挑战有多容易。","title":"立即雇佣最优秀的3D人才!"},"freelancer_selection":{"title":"让您的下一个3D项目成功","widest_selection":{"description":"不要满足于更少。CGTrader拥有40,000名自由3D设计师,是与专业人才对接的最大平台:建模师、动画师、绑定师、3D打印专家等。","title":"最广泛的3D自由职业者选择"}},"how_it_works":{"steps":{"describe":{"description":"告诉我们您的需求ã€
16518‚使用我们的简单表格,添加图片,并在几分钟内发布自由职业需求。","title":"描述您的项目"},"pay":{"description":"安全地将预算预付给CGTrader,让您的自由职业者知道您已准备好开始。通过专用工作区聊天、共享附件和查看项目进度。对最终结果满意时释放付款。","title":"为结果付款"},"select":{"bullets":["直接在收件箱收到合格设计师的报价","与设计师实时聊天","浏览他们以前的项目","您还可以根据3D技能、评分和软件或格式偏好搜索、筛选并邀请自由职业者"],"title":"选择最佳自由职业者"}},"title":"用3个简单步骤完成任务"},"payment_quality":{"description":"CGTrader确保自由职业服务符合您的要求并按时交付。我们的QA团队随时为您提供支持。","title":"品质保证"},"secure_payments":{"description":"我们的托管服务意味着您的资金受到保护,只有当您对最终结果满意时才付款。","title":"安全支付"},"workspace_convenience":{"description":"忘掉在标签和工具之间切换的麻烦。通过专用工作区,您可以在一个地方轻松管理所有内容:多个项目、发票、对话、文件共享等。","title":"单屏便捷"}},"shared":{"post_project_cta":"发布项目"}},"navigation":{"find_jobs_cta":"寻找3D工作","hire_freelancers_cta":"雇佣3D自由职业者","how_it_works":"如何运作","why_projects":"为什么选择3D项目"}},"agreed_to_terms_of_use":"您必须同意 3D Projects 的条款和条件才能继续。","budget_error_message":"太高","check_designer_uniqueness_error":"设计师只能被邀请一次","check_ownership_error":"邀请者并不拥有该项目","check_status_error":"设计师只能被邀请到已批准的项目。","deadline_error_message":"截止日期必须是未来时间","discount_code_error":"抱歉,折扣码不存在或已过期。","email_subject":"3D项目机会: %{subject}","error_check_application_uniqueness":"您只能申请一次","error_check_applier":"您不能申请自己的3D项目","error_check_job_availability":"用户无权申请","error_check_job_status":"项目申请已暂停,直到我们的新审核流程完成,通常在3个工作日内。感谢您的理解!","modeling_request_failed":"建模请求失败!","offer_has_been_sent":"感谢您,您的报价已发送!","offer_is_closed":"报价已关闭。","payment_successful":"支付成功完成。","payment_under_review":"支付正在审核中。","share_with_yourself_error":"不允许与自己分享“雇用我”模板","system_messages":{"buyer_cancels_job":"项目已取消并移至 %{archive_url} 标签页。","buyer_edits_hire_me_template":"项目详情已由客户更新。","buyer_edits_job_details":"项目详情已更新。","buyer_marks_job_as_finished":"项目已标记为完成并移至 %{archive_url} 标签页。","buyer_marks_job_as_finished_with_resell":"客户将项目标记为完成,并允许您在CGTrader上出售成果。请务必\u003cb\u003e\u003ca href=\"/profile/upload/model\"\u003e上传\u003c/a\u003e\u003c/b\u003e创建的模型!","buyer_negotiates":"客户协商了您的报价。","buyer_pays_for_the_job":"项目已预付并移至 %{in_progress_url} 标签页。","designer_accepts_changes":"设计师已审核更新的详情。","designer_accepts_offer":"设计师接受了您的报价。","designer_edits_offer":"设计师修改了他的报价。","designer_negotiates":"设计师协商了您的报价。","designer_withdraws_offer":"设计师撤回了他的报价。","job_expires":"项目已过期并移至 %{archive_url} 标签页。"},"template_error":"未创建任何项目!","user_not_exists":"使用该用户名的用户不存在。","want_that_job":"我想要这份工作!"},"kodama":{"invalid_request":"无效请求!"},"landing":{"consent_not_saved":"同意未保存","failure_notification":"所有必填的 API 访问请求字段都必须有效。","success_notification":"感谢您的请求。我们会尽快与您联系。"},"layout":{"chunk_load_notice":"加载此页面时出现问题,请刷新页面。","cookies_policy":{"accept":"接受","learn_more":"学到更多","text":"我们的网站使用Cookies来收集统计访问者数据和轨道的相互作用直接营销传播/改进我们的网站,并改善您的浏览体验。请参阅我们的Cookie通知有关Cookie的信息,他们收集的数据,谁可以访问它们,你的权利。"},"translations_notice":{"cta":"切换到 English","text":"你好! 我们正å
16518œ¨ç¿»è¯‘CGTrader. 你好! 我们正在翻译CGTrader. 正在进行翻译, 有些部分可能没有中文."}},"model":{"already_purchased":"您已购买此产品。您可以从\u003ca href='/profile/cart/purchased'\u003e购物车\u003c/a\u003e下载。如果需要,您可以再次购买。","animated":"动画","artist_profile":"艺术家简介","author":"作者","author_get_your_payment":"作者将获得您支付金额的100%。CGTrader支持艺术家赚取最高收入,并鼓励销售高质量内容。","author_products":"更多来自 \u003ca href=\"%{author_url}\"\u003e%{username}\u003c/a\u003e 的产品","authorised_seller":"CGTrader认证卖家","authority":"权限","breadcrumb":{"3d_models":"3D模型","home":"首页"},"bulk_download":{"download_all":"下载所有文件","files_included":{"one":"包含 %{count} 个文件","other":"包含 %{count} 个文件"},"too_large":"此模型过大,无法作为单个压缩包下载(超过 %{limit})。请单独下载各个文件。"},"buy":"è´­ä¹°","cannot_download":"仅拥有%{experience_needed}声望积分的用户可以下载超过%{models}个免费模型。了解更多等级和声望信息,请\u003ca href='%{reputation_url}' target='_blank'\u003e点击这里\u003c/a\u003e。","collection":"3D资源包","contact_author":"联系作者","description":"描述","download":"下载","download_limit_exceeded":"您已超出每日下载限制。请明天再试。此限制不适用于过去12个月内有购买记录的用户。","download_modal":{"chat_with_us":"在线咨询","contact_support":"请通过聊天联系我们。","download_file":"下载 %{name}","empty":"未找到任何文件","generic_error":"出了点问题,请重试","link_error":"很抱歉,您的下载链接似乎出现了一点问题。","pbr_ready":"支持 PBR","preparing":"正在准备下载...","title":"下载文件"},"format_downloads":"3D软件格式下载","game_ready":"游戏就绪(低多边形)","geometry":"几何结构","license":"许可证","make_offer":"出价","materials":"材质","messages":{"no_files":"没有上传的文件","no_rating":"此模型尚无评分"},"model_details":"3D模型详情","pbr_textures":"PBR贴图","plugin_used":"使用的插件","polygons":"多边形","price":"ä»·æ ¼","print_ready":"3D打印就绪","product_id":"产品ID","product_support":"产品支持","product_type":"产品类型","published":"发布日期","rated":{"error":"您已对该模型评分","success":"感谢您为此模型评分"},"rating":"3D模型评分","ratings":{"desc":{"negative":"购买该3D模型的用户认为质量低于预期","positive":"购买该3D模型的用户认为质量良好"},"negative":"负面评价","positive":"正面评价"},"recently_viewed_models":"最近浏览的模型","renderer":"渲染器","report":"举报","rigged":"绑定","scripts-plugins":"脚本和插件","similar_models":"相似模型","statistics":"统计","subscription":{"limit_reached":"您已达到本计费周期的下载上限。","limit_reached_detailed":"您已达到本月%{limit}次下载上限。您的限制将于%{reset_date}重置。 如需立即下载更多模型,您可以单独购买。","limit_reached_detailed_no_date":"您已达到本月%{limit}次下载上限。 如需立即下载更多模型,您可以单独购买。"},"tags":"标签","textures":"贴图","title":"%{item_title} 3D模型","unwrapped_uvs":"展开UV","uv_mapping":"UV映射","vat_notice":"所有价格均不含增值税","version":"版本","vertices":"顶点","vr":"VR 模型"},"models":{"error":{"cannot_be_blank":" 不能为空。"}},"navigation":{"3d-models":{"3d-printing":"3D打印文件","discounts":"折扣","find_designers":"寻找最好的设计师","free":"免费的3D模型","pbr":"基于物理渲染","scanned_3d-models":"扫描的3D模型","scripts_apps":"脚本和应用","show-all":"显示所有","textures":"纹理","title":"3D模型","vr_low-poly":"虚拟现实 /低多边形"},"already_added_message":"已经被添加到购物车","already_purchased_message":"您已经购买此产品。","aria":{"account":"账户","cart":"购物车","close_menu":"关闭菜单","logo":"CGTrader - 3D模型 / 3D设计师","messages":"消息","notifications":"通知","open_menu":"打开菜单","search":"搜索","search_by_image":"以图搜索"},"budgets":{"below_10":"低于$10","collections":"组合包","free":"免费","more_than_20":"超过$20","more_than_50":"超过$50","sale":"促销中","subscription":"订阅"},"cart_limit_reached":"您的购物车已达到500件商品的限制。要继续购物,请完成当前订单,然后为其他商品开始新订单。","cart_widget":{"continue_shopping":"继续购物","discount":"用优惠券的企业:","discount_applied":"已应用折扣:","final":"最终价格:","have":"你有","items":" 在您的购物车中的商品。","left_to_unlock_discounts":"还差 {{leftToUnlock}} 即可解锁折扣","potential_discount":"潜在折扣:","proceed":"进行结算","recently_viewed_empty_text":"开始探索,您最近查看的商品会显示在这里。","recently_viewed_empty_title":"这里还没有内容","recently_viewed_tab":"最近查看","remainer":"要激活CGTrader积分,请添加:","sale_discounts":"折扣应用:","sale_time_left":"剩余时间","sale_tooltip":"我们促销活动的折扣仅在您的购物车达到 ${{threshold}} 或以上时才会应用。","sale_tooltip_text":"此商品正在促销,但只有当您的购物车小计达到 ${{threshold}} 或以上时才会应用折扣。","text":"你的购物车是空的。","title":"购物车","total":"总:","used":"积分使用:","view":" 查看购物车","wishlist_empty_text":"开始探索并收藏您喜欢的商品 — 它们会显示在这里。","wishlist_empty_title":"您的心愿单是空的","wishlist_tab":"心愿单"},"community":{"awards":"设计师奖项 🏆","blog":"博客","challenges":"挑战","community_ratings":"社区评级","designers":"3D设计师","digital_art":"数字艺术大赛","forum":"论坛","galleries":"画廊","invite":"邀请朋友","printlist":"3D的printList","sell_3d-models":"出售3D模型","title":"社区","tutorials":"教程"},"conversations_widget":{"conversations":"对话","messages":{"loading":"加载收件箱"},"types":{"cgtrader_support":"CGTrader支持","file_conversion_request":"文件转换请求","job_application":"三维工程","model_feedback":"产品反馈","model_support":"模型的支持","offer":"报价","personal":"个人消息","promotions":"促销"}},"create_ai":{"community_gallery":"社区画廊","free_trial":"免费试用","generate_3d_model":"生成3D模型","generation":{"dismiss":"关闭通知","error_title":"生成问题","generating_title":"正在生成您的3D模型","open_model":"打开模型","ready_title":"您的3D模型已就绪!"}},"create_with_ai":"使用AI创建","credits_guest_widget":{"create":"现在创建帐户!","discount":" 折扣","for":"为您","get":"并获得高达 ","learn_more":"了解更多关于CGTrader忠诚学分 ","link":"这里","purchase":"第一次购买!","sign-up":"注册","text_below_1":"积分可以在购买时“使用","text_below_2":"“签署标志着模式和覆盖到价格的30%折扣。"},"credits_user_widget":{"condition_1":"CGTrader积分可用于购买标有\u003ci class=\"custom-font-icon custom-font-icon--small icon-wallet\"\u003e\u003c/i\u003e标志的模型,可享受高达30%的折扣。","condition_2":"积分只能在购物车总价值达到\u003cstrong\u003e$%{minimum}\u003c/strong\u003e或以上时使用。","condition_3":"在促销活动或特价销售期间,积分无法使用。","condition_4":"请注意,积分有有效期 - 您的积分将在\u003cstrong\u003e%{days}\u003c/strong\u003e天后过期。","earn_rate":"信用赚取率","first_text_1":"认为这是一个礼物送给你的第一次购买 - 你可以使用积分购买时“","first_text_2":" “标模型,并得到了它的价格的30%的优惠。如果你的车的总价值等于或高于$ 的更大的学分才能使用。","important":"重要:","learn_more":"学到更多","lifetime_spending":"终身支出:","list_item":"通过对CGTrader增加你一生的消费,你的信用赚取率也增加。","message":" 积分不能在销售活动中使用,但你仍然可以增加你的提成水平,并收集更多学分您下次购买。","more_credits":"如何获得更多的积分?","next_level":"下一级:","notice":"注意:CGTrader积分在促销期间无法使用,但促销结束后将重新可用。与此同时,查看3D模型的超值优惠\u003ca href=\"https://www.cgtrader.com/3d-models?sale_off=1\"\u003e这里\u003c/a\u003e","par_1":"你有","par_2":" CGTrader学分。","par_2_variation":" 在没有促销活动时您可以花费的积分。","regular_text_1":"积分可以在购买时“使用","regular_text_2":" “签署标志着模型和掩盖它的价格的30%折扣。","title":"CGTrader忠诚折扣"},"enterprise":{"management":"3D资产管理","title":"3D内容制作"},"hire_a_freelancer":"聘请自由职业者","item_add_message":"已成功添加到购物车","item_restricted_message":"由于对设计师所在国家 (RU/BY) 的制裁,此商品无法购买。","navbar":{"actions":{"new":"最新","top_selling":"畅销","trending":"热门"},"budgets":{"below_10":"低于$10","free":"免费","more_than_20":"超过$20","more_than_50":"超过$50","sale":"促销中","up_to_10":"最多$10"}},"projects":{"3d_customs":"定制3D","3d_printing":"3D打印","about":"关于3D项目","browse":"浏览项目","buyer_perspective":"聘请设计师3D","for_designers":"为设计师","my_projects":"我的项目","seller_perspective":"自由3D项目","start":"开始项目","work":"与CGTrader工作"},"sell_3d-models":"出售3D模型","sellers_hub":"卖家中心","subscription_includes_item_message":"此商品已包含在您的订阅中。","tooltips":{"cart":"购物车","conversations":"我的对话","discounts":"CGTrader忠诚折扣","notifications":"通知","publisher":"出版者"},"user":{"ban_notification":"此用户账户已被暂停。请联系我们的支持团队以获
16518取说明。","cannot_refer_yourself":"不能推荐自己","email_confirmation":"您的电子邮件地址已验证。","email_reconfirmation":"已经确认,请尝试登录","ends_at":"结束时间:%{date}","handle_error_tfa_code_missmatch":"您输入的安全码不正确。请重试。","handle_error_tfa_code_missmatch_last":"您的安全码已过期。若要继续,请点击重新发送邮件并输入新的验证码。","handle_error_tfa_default":"出现错误,请稍后再试。","handle_error_tfa_expired":"您的安全码已过期。","hundred_percent_royalties_popover":"您正在参与一项临时100%收益活动:所有新模型的销售收入将在活动结束前全部归您所有","invalid_email_message":"请输入有效的电子邮件地址以继续。","login":"登录","meta_description":"查看 %{username} 在CGTrader的3D设计师资料、作品集和可用的3D模型。探索来自顶级建模艺术家的最佳3D模型。","meta_title_suffix":"3D设计师","not_valid_birthday":"出生日期无效。","offline":"离线","online":"在线","page_title":"设计师资料","rate_models_limit":"今天您不能再评价更多模型了。","semantic_description":"设计师的3D模型、艺术作品和作品集。来CGTrader看看他的创作","semantic_title":"设计师资料","session_failure":"提供的用户名/电子邮件或密码不正确","session_password_expired":"由于长时间未使用,您的密码已过期。请通过我们发送的电子邮件链接重置密码。","session_two_fa_failure":"验证码验证失败","session_unconfirmed":"您的电子邮件地址尚未验证。","token_not_recognized":"未识别令牌","twitter_shareable_text":"值得关注的#3dartist","username_error":"不应包含空格或非拉丁符号"},"user_menu":{"buyer":{"freelancers":"寻找自由职业者","purchases":"我的采购","sell":"出售3D模型","wishlist":"三维模型的收藏"},"dashboard":"管理仪表板","logout":"退出","public_profile":"公开资料","seller":{"analytics":"Analytics(分析)","find_new_projects":"寻找新的3D项目","freelance":"自由3D项目","freelance_cgtrader":"自由与CGTrader","models":"我的模型","publish_project":"发布项目","publishing":"出版","sales":"我的销售","subscription":"订阅","wishlist":"三维模型的收藏"},"settings":"设置"}},"owly":{"failed_set_pending":"设置为待处理失败。","failed_to_approve":"审批失败。","failed_to_suspend":"暂停失败。","failed_to_update_license":"更新许可证失败。","failed_to_update_status":"更新审批状态失败。"},"print_models":{"browse_all_makes":"浏览所有3D打印作品","browse_all_models":"浏览所有模型","description":"发现流行的可打印3D模型、打印资源和STL文件,适用于游戏、艺术、家庭项目等。","makes_description":"探索由社区上传的最新3D打印作品——展示3D模型在现实世界中样貌的真实示例。","makes_title":"新的3D打印作品","new_description":"查看我们收藏中新增的最新3D打印模型——新颖的设计,随时可下载并用于您的下一个项目。","new_title":"最新3D打印模型","title":"流行3D打印模型"},"profile":{"account_settings":{"account":{"email":"邮箱","submit":"保存","title":"个人资料","update":{"updated":"个人资料已更新"},"username":"用户名"},"email_settings":{"descriptions":{"ai_studio_notifications":"有关您的 CGTrader AI 生成的通知,包括您尚未打开的模型已准备就绪时","communication":"关于您与其他 CGTrader 用户的通信的信息,包括私人消息、新价格和自定义工作报价、反馈和产品支持请求","news":"关于 CGTrader 产品、新功能公告和社区新闻的信息","onboarding_and_edu":"关于您的入职流程和帮助您充分利用 CGTrader 的提示的信息","push_notifications":"关于重要账户活动和更新的通知","sales_and_offers":"关于特别优惠、3D 模型销售和 CGTrader 奖励积分的信息","seller_sold_item":"关于您出售的 3D 模型和收到的付款的信息"},"privacy_policy_html":"请阅读我们的 \u003ca href='https://www.cgtrader.com/pages/privacy-policy' 'target='_blank'\u003e隐私政策\u003c/a\u003e 了解更多信息。 ","titles":{"ai_studio_notifications":"CGTrader AI 通知","communication":"通信(私人消息、3D 模型反馈、社区活动、报价)","news":"3D 新闻 / CGTrader 更新","onboarding_and_edu":"入职和教育","push_notifications":"重要账户活动和更新的通知","sales_and_offers":"销售和特别优惠","seller_sold_item":"销售和付款信息"},"tracking_attributes":{"ai_studio_notifications":"CGTrader AI 通知","communication":"通信","news":"3D 新闻 / CGTrader 更新","onboarding_and_edu":"入职和教育","sales_and_offers":"销售和特别优惠","seller_sold_item":"销售和付款信息"},"update":{"updated":"您的电子邮件已成功更新。"}},"gdpr_success_notification":{"opt_in":"感谢您!您已成功重新订阅我们的电子邮件。","opt_out":"您已成功取消订阅我们的新闻通讯。您可以随时通过访问您的个人资料设置更改此偏好。\n"},"location_tooltip_text":"\u003cdiv\u003e此复选框启用在个人资料页面显示位置。\u003c/div\u003e \u003cdiv\u003e请注意,由于合规原因,无论如何您的位置都会显示给您的买家(如果您是卖家)以及您购买商品的卖家。\u003c/div\u003e","moneybookers":{"account":"Skrill 账户"},"password":{"current_password":"当前密码","new_password":"新密码","submit":"更改密码","title":"密码","updated":"您的密码已成功更新。您现在可以使用新密码登录。","verify_password":"验证密码"},"paypal":{"account":"PayPal 账户","submit":"保存","title":"PayPal 设置","update":{"updated":"您的 PayPal 设置
16518已更新"}},"personal_info":{"areas_of_expertise":"专业领域","country":"国家","description":"关于您的几句话","full_name":"全名","languages":"语言","preferred_tools":"首选工具","submit":"保存","title":"个人和专业信息","update":{"updated":"您的信息已成功更新"},"website":"网站"},"personalization":{"no_muted_title":"没有屏蔽的设计师","title":"屏蔽的设计师","tooltip":"列表中设计师的 3D 模型将从您的搜索结果中排除。","unmute_designer":{"confirm":"您确定吗?"}},"professional_info":{"allow_private_job_requests":"买家将能够向您发送自定义 3D 工作请求。","buyer_engagement_program_html":"通过让客户使用 CGTrader 积分购买您的模型来吸引更多客户。\u003ca href='https://www.cgtrader.com/pages/cgtrader-loyalty-discounts-explained' target='_blank'\u003e更多信息\u003c/a\u003e\n","pbr_program":"仅当下载转换文件时,才适用较低的收益率","pbr_program_agreement":"您已同意于 %{date} 参加 PBR 计划,为期 24 个月。有关更多详细信息,请参阅条款和条件。如有任何疑问,请联系我们的支持团队。","product_analytics_participation_html":"我参与了 \u003ca href='https://www.cgtrader.com/profile/analytics'target='_blank'\u003e分析\u003c/a\u003e 并同意其条款和条件\n","product_analytics_participation_tooltip_html":"您已同意参与分析并同意分析条款和条件 (\u003ca href='https://www.cgtrader.com/pages/cgtrader-analytics-terms-of-service'target='_blank'\u003e链接\u003c/a\u003e) 于 %{accepted_at}。如果您想终止参与,请联系 CGTrader 支持\n"},"security":{"allow_mfa":"启用两步验证以保护您的账户","use_authenticator_app":"使用身份验证器应用进行两步验证","use_authenticator_app_tooltip":"使用您的身份验证器应用(Google Authenticator / Authy)扫描二维码以生成验证码。","use_email":"使用电子邮件进行两步验证"},"sidebar":{"general_settings":"常规设置","member_since":"注册时间","paypal":"PayPal","title":"账户信息"},"title":"我的账户"},"conversations":{"conversations":"对话","messages":{"loading":"加载收件箱"},"types":{"cgtrader_support":"CGTrader支持","file_conversion_request":"文件转换请求","job_application":"三维工程","model_feedback":"产品反馈","model_support":"模型的支持","offer":"报价","personal":"个人消息","promotions":"促销"}},"meta":{"description":"在 CGTrader 上探索 %{username} 的高质量 3D 模型。浏览用于游戏、VR/AR 和可视化的独特资源。","title":"3D 模型设计师:%{username}"},"my_products":{"suspension_appeal":{"reasons":{"not_available":"N/A"}}},"settings_updated":"您的设置已成功更新。"},"publishing":{"banned_error":"您的账户因发布垃圾内容已被禁止。如果您认为这是一个错误,请联系 [email protected]。","files_not_finalized":"一个或多个文件尚未完成上传,无法发布。请删除它们并重新上传后再发布。","spam_suspect":"您的账户未被授权发布此模型。如果您认为这是一个错误,请联系 [email protected]。"},"rating":{"anonymous_buyer":"匿名买家","errors":{"not_verified_buyer":"只有经过验证的买家才能留下评论"}},"routes":{"3d-models":"3d-models","3d-print-models":"3d-print-models","animated-3d-models":"animated-3d-models","discover":"faxian","free-3d-models":"free-3d-models","free-3d-print-models":"free-3d-print-models","low-poly-3d-models":"low-poly-3d-models","pbr-3d-models":"pbr-3d-models","pbr-textures":"pbr-textures","rigged-3d-models":"rigged-3d-models","search":"search"},"sale_off":{"no_overlapping_campaigns":"活动日期与另一现有促销活动重叠。","parse_discount_rules_text":"第 #{index} 行缺少必需的 'discount:' 参数"},"search":{"not_found":"未找到搜索订阅","subscription_created":"搜索订阅已创建","successfully_unsubscribed":"您已成功退订搜索订阅"},"subscription_opt_ins":{"lock_period_error":"Some items cannot be opted out before the six-month lock period ends.","missing_items_error":"Items not found: %{items}."}
16518,"subscriptions":{"flash":{"active_subscription":"您已经拥有有效订阅。","premium_upgrade_unavailable":"升级到 Premium 暂时不可用。请稍后再试。","update_payment_method":"您的订阅存在付款问题。请更新您的付款方式,而不是购买新的订阅。"},"payment_form":{"current_price":"%{price}","features":{"first":"每月 %{downloads} 个资源 — 下载模型或使用 AI 生成","second":"即时访问 %{models_count}+ 精选 3D 模型","third":"免版税,无 AI"},"from_price_label":"èµ·","original_price":24.99,"per_model_label":"每个模型","plan_name":"CGTrader Essentials 订阅 — 年度计划,按月计费","premium":{"features":{"first":"每月 %{downloads} 个资源 — 下载模型或使用 AI 生成","fourth":"免版税,无 AI","second":"即时访问总计 %{models_count}+ 个可访问模型","third":"Essentials 中不包含的专业级模型"},"plan_name":"CGTrader Premium 订阅 — 年度计划,按月计费"},"price_unit":"/月","vat_amount":"%{vat_amount}"}},"tag":{"description":{"bottle":"科学家们说的葡萄酒瓶,每天是不是对你不好,但是当涉及到像威士忌或波旁更强的饮料,我们真的应该看我们的酒精摄入量。显然,玻璃比塑料瓶的水吧长相方式冷却器香槟,但想想活更长的使用寿命和啤酒会似乎是一个合理牺牲的可能性。这个类别提供一瓶3D模型的多种选择,从像酒架和开瓶器于各类瓶3D模型(从洗发水到橄榄油)的酒精相关的对象。","brain":"大脑是人体最大,最复杂的器官之一。由于其复杂的结构和功能,大脑的解剖结构也很复杂。然而,科学家们更深入地了解人类大脑的结构,揭示令人难以置信的秘密。广泛的逼真而细腻的大脑3D模型已经准备好计算机图形应用,如广告,三维可视化,3D游戏,网络和相关的3D设计任何其他领域的可供下载。","building":"目前还不清楚其中现代建筑师从他们的灵感,但世界上一些著名的建筑物是如此尖端的他们看起来像是来自未来的愿景。我们并不仅仅意味着外表部......在动手能力标准的建筑成为令人惊叹的建筑创作与创新的内饰设计,无论它是一个办公室或城市建筑。我们构建3D模型的选择可供下载,并且可以在各个领域的应用 - 三维动画,平面设计,广告,3D游戏或网页。","camera":"摄影和拍摄,目前在年轻人中最流行的职业领域之一,但不是每个人都用专业摄像机是一个艺术家或摄影。随着手机摄像头在逐年提高质量,我们看到有人拍照的每一步,那些谁拥有了新开发的图像编辑技能是真正的庆祝。生活看起来确实有点不同的,当你在镜头的另一边,但我们不要得意忘形......种类繁多详细的摄像头3D模型可以由平面设计师,动画和游戏开发人员使用下载和专业人士等3D相关的领域工作。","car":"看着这些非常详细的和高度逼真的3D车型,你几乎可以听到发动机的轰鸣声!广泛车辆的三维模型可以使你的生活变得更轻松,如果你与计算机图形,动画或任何其他3D相关领域拼杀。","cell":"科学家们估计,人体包含5十亿200万个万亿个细胞。植物和动物有细胞了。我们不想烦你这种生物的东西,但广泛的细胞的三维模型,我们已经涵盖了从血液,病毒和DNA骨髓和细胞壁。这不就是......伤脑筋?","chair":"我们就坐在椅子是理所当然的,并不总是发现他们有多么的魅力可以添加到我们的家中或办公室内。主席实际上是任何家庭中央件家具之一。在这里,你可以找到高品质,可以在不同的领域得到应用抢眼的设计椅子的3D模型 - 来自广告和三维可视化,以3D游戏。","city":"城市生活是一个古怪的游戏:街道赛车出租车,现代建筑,在完全适合办公室行人的衣服,睡觉桥梁和公园......从上海到纽约(!曼哈顿)爆棚,伦敦或巴黎 - 每个城市都有其自己创造奇迹。广告,计算机图形,动画,三维可视化或游戏 - 你可以从这个丰富详细的城市三维模型的选择,可以在许多领域的使用选择。探索城市生活。","dragon":"龙可能是最流行的生物生活在幻想的世界。它刊登在各种文化,动画,电影和书籍,它已成为传说中的那么多的神话。它尤其是游戏开发商的喜爱,因为它增加了那个神奇的魅力黑暗的洞穴和地牢。广泛的现实,操纵龙3D模型已经准备好计算机图形应用,如广告,三维可视化,3D游戏,网络和相关的3D设计任何其他领域可供下载。","earth":"细节在谷歌地图最近添加的地球视图水平震惊了许多人。您可以探索整个地球 - 海洋,山脉和沙漠,探索城市和道路隐患不离开你的房子ã€
16518‚这是一些不可思议的事情地球母亲之前没有经历过。现实的地球三维模型的,可用于计算机图形应用,如广告,三维可视化,3D游戏,网络和相关的3D设计任何其他领域的选择可供下载。","engine":"有些人能够通过简单地听着它发出的声音来命名的引擎,这确实是惊人的。不管它是喷气发动机或汽车发动机,我们使用如此频繁,但不要花太多时间思考一下他们​​极其复杂的机器。马达是一个电动机(直到它停止工作)。这个分类中有各种不同类型的发动机3D模型,而且还包括发动机部件,如螺旋桨或缸,所以我们相信,你会发现你所需要的。","female":"这可不是闹着玩的,有男性和女性的大脑之间,这意味着它是正常的,一个普通的男人和女人的行为,手感和响应以完全不同的方式显著差异。但是,这不是所有的解剖。没有爱情故事,电影或游戏有史以来一把扯下没有坚强的女孩的性格,事业的淑女魅力的力量是不可否认的。逼真的女三维模型的丰富选择可以为您提供你需要的一切 - 女性呼吸系统从裸3D模型。","hand":"我们用我们的双手为这么多东西的原因是其非凡的解剖。拇指单独由九个单独的肌肉控制。很难相信,但今天早动手比任何手更奇特的 - 有些种类有七个手指。人体有显著发展。手臂或手,因为我们看到今天看起来一点也不像它的早期版本。广泛的现实,操纵手的3D模型已经准备好计算机图形应用,如广告,三维可视化,3D游戏,网络和相关的3D设计任何其他领域可供下载。","head":"在人体解剖学,头部是人体的上部。建模三维人体头部可以证明有点复杂,因为(即使有这么多不同的形状和大小),我们对此表示赞赏对称的,逼真的头 - 没有搞笑的眼睛,耳朵,鼻子,嘴唇和下巴的线条,请。脸是一个敏感的区域,这纯粹是粗鲁有乐趣这一点。我们的选择包括现实的男性,女性,儿童,甚至故事人物像哈利·波特与蝙蝠侠或像汤姆·克鲁斯和斯嘉丽·约翰逊的头是可供下载名人。","helmet":"一定要记住,健康和安全是第一位的,所以保护你的头是一个健全的想法。中世纪骑士,Daft Punk的,甚至铁人戴头盔,所以定期的超级英雄一样骑自行车者和建设者不应该担心自己不够酷。如果你在平面设计,三维动画制作,3D游戏或可视化领域工作的头盔3D模型的这个集合,是可供下载将被证明是有用的。","house":"这是一个古老的真理,一个人的家就是他的城堡,但是当它涉及到建设理想的房子或买一个理想的公寓,我们确实会在它失去我们的头。幸运的是,这所房子的三维模型的广泛,从建筑细节,内外墙涵盖一切,家具,将帮助您创建现代化的3D可视化,以缓解疼痛。","human":"当你开始探索人体解剖学,你意识到人是这样错综复杂的机器,它需要你的呼吸。每一个身体的一部分,肌肉,关节,器官或骨骼组合连接成一个系统,没有什么工作孤立!这些超级逼真的3D人体模型可以让你从各个角度审视解剖结构。","lamp":"如果你看看你的家,算上各类灯具,你自己的,你会惊奇地发现:阅读,桌面,墙壁,天花板,桌子,三脚架灯......这是足以让你头晕!且不说风格,他们在来 - 从奢侈的现代灯的复古时尚或前卫。在这个类别中,我们有各种灯的3D模型,让浏览和下载到平面设计,三维可视化或任何其他3D相关的领域使用。","light":"你有没有想过为什么灯泡是一个绝妙的主意的象征?当然,一个好主意可以启迪心灵的架构,但不是它。路灯,一盏吊灯或任何其他类型的灯将永远不会有机会采取现场。如果当你在天花板抬头盯着某种建筑光天才的想法不打你,这可能是因为你在错误的闪电系统盯着......我们高品质的光的选择下一次如果您在建筑,计算机图形学,3D动画和设计领域工作的3D模型将证明得心应手。","machine":"试想一下,如果所有的机器突然停止工作:没有咖啡,没有方便洗衣,没有现金点在大街上,一个角落没有健身房的时间,没有高估自动售货机......这听起来令人沮丧,不是吗?我们已经习惯这些家用电器和电子产品,它会从字面上阻止世界转动!幸运的是,我们选择的详细机器的3D模型涵盖了从机枪到发电机,将证明与计算机图形,广告,3D动画,3D游戏或网页设计师的工作非常有用。","man":"男人的3D模型的庞大集合可供下载。坐在男人,简单的人,飞行的人还是普通人,我们就拥有了一切 - 除了完美的人,每个人的后。他是一个性格腥,我们不想提起这事,但也可能是一个神话......但是,嘿,人类是天生的真棒,并不完美。","robot":"R2D2可能是世界上最知名的机器人,但它肯定不是唯一的一个。从工业到玩具人类的机器人,我们看到了这些机械设备的越来越广泛的选择,这是既美妙又有点担忧。这不是太难理解谁坚持认为,计算机,技术,机器人正在对人类太显著影响的人。广泛的现实,操纵机器人的3D模型的,可用于计算机图形应用,如广告,三维可视化,3D游戏,网络和相关的3D设计任何其他领域可供下载。","ship":"船舶是强有力的血管和有各种尺寸,颜色和形状。它们可以被归类为货船,军舰或客船,但他们都在深海水域作业。如果你经历了晕车的不适,它可能不是代步自己喜欢的方式,但很多人都把乘船游览的相当优雅和愉快。我们的船3D模型的选择可供下载,将满足即使是最苛刻的队长的标准。","skeleton":"人类骨骼或骨骼系统(解剖)是在出生时由270个骨骼的主体的内部框架。它提供了车身结构,保护和运动,是最复杂的骨骼系统之一。了丰富的选择正确的解剖学,求实标的骨架的三维模型,非常适合于三维动画,平面设计,广告,3D游戏和网页应用可供下载。","skull":"最古老最完整的,完整的基因人类头骨被发现由多种灭绝的动物低于海平面40多米的包围。这是在新的世界发现的最古老的骨骼之一,它也包括身体的所有主要的骨骼和牙齿的完好的头盖骨和一套。人类头骨最近成为时尚和设计的事情,但是当你真正给它一些想法,他们那种化妆的全部样子解剖畸形......这一类提供广泛的头骨3D模型,可以在平面设计中使用,3D动画,3D游戏或广告。","sofa":"豪华或简约,现代或古典,皮革或简约的设计?你喜欢你的沙发?沙发3D模型的范围内,我们必须将满足即使是最严重的完美主义者。当你å
16518œä¸‹æ¥æƒ³ä¸€æƒ³ï¼Œæ²™å‘是每个客厅的中心,所以选择设计时,你必须有创意!","table":"最新的家具潮流的不同作用,选择一个现代的,时尚的表变得过于紧张。曾经有一段时间,当你有一个简单的饭桌上为您的节日温和,那是足够的,但是今天 - 表应该是一个语句或至少一个醒目的内部口音。这个选择给你一个广阔的范围表3D模型,以满足每一个心血来潮。","tower":"每个城市都有一个特殊的塔,每个人都真的很自豪。想想艾菲尔铁塔或比萨斜塔,或伦敦塔桥,更不用说各种现代化的摩天大楼,未来派建筑和城市景观,游客欣赏。然而,当涉及到的城市,水或电塔工业零件,大家往往把他们的眼睛了......但是,我们应该学会欣赏每一个建筑创作,无论古代,工业或现代化。详细塔3D模型的范围可供下载,并准备计算机图形应用,如广告,三维可视化,3D游戏,网络和相关的3D设计任何其他领域。","tree":"如果你在种植一些树木和奇花异草一个任务,你来对地方了。此类别提供一系列易于使用,非常逼真的3D树模型,以CG艺术家和建筑师,谁正在努力一些绿色植物添加到他们的项目。如果您欣赏优良的品质,我们相信这个选择3D树植物车型将满足你的标准!","truck":"驾驶一辆卡车是一项艰巨的任务,特别是如果你需要找到通过城市交通拥堵的方式。无论它是否是一个消防车,垃圾车,食品车或一般之一,它需要高超的技巧来操纵这些巨型车辆。我们相信,本次评选逼真,高品质卡车3D模型是完美的三维动画,平面设计,广告或任何其他3D相关的现场使用。","tv":"曾经有当壁炉是每一个可敬的家庭,现在的电视机与球员和扬声器的核心时间是客厅(甚至是卧室)的国王。近年来电视已经赶上了最新的技术。因此,我们有智能3D电视,平板和等离子屏幕,万个新功能和触摸屏设计。难怪电视是推动电影市场淘汰。丰富的实际,详细的电视3D模型已经准备好计算机图形应用,如广告,三维可视化,3D游戏,网络和相关的3D设计任何其他领域的可供下载。"}},"tag_promotion":{"days_left_error":"必须在 1 到 30 之间","no_other_pending_promotions_for_same_item_tag":"已经有一个待处理的促销活动"},"tooltips":{"buyer_loyalty_program_current_level_html":"目前,您已经在CGTrader用%{total_price_paid}和你的信用赚取率%{buyer_loyalty_rate} \u003cBR/\u003e这意味着你会从每次购买获取%{buyer_loyalty_rate}学分。\n","buyer_loyalty_program_html":"这是你需要多少,从您购买新的学分得到的百分比。 \u003cBR/\u003e这将增加至30%作为CGTrader增加你的一生支出。 \u003cBR/\u003e目前你的信用赚取率%{buyer_loyalty_rate}。\n","buyer_loyalty_program_next_level_html":"下一级%{next_lvl_expenses}会增加你的信用赚取率高达%{next_lvl_expenses_rate} 。 \u003cbr/\u003e您需要购买$%{left_until_next_lvl}更达到一个新的水平。\n"},"trending_headers":{"models":"查看其他流行的3D模型","print":"查看其他流行的3D打印模型"},"tutorial":{"destroy":"教程已成功删除。","submit_for_review_error":"请填写所有必填字段。","submitted":"感谢您提交教程。它将很快被审核并发布。"}}};
16519}));
16520
16521
16522
16523(function($) {
16524
16525  var cocoon_element_counter = 0;
16526
16527  var create_new_id = function() {
16528    return (new Date().getTime() + cocoon_element_counter++);
16529  }
16530
16531  var newcontent_braced = function(id) {
16532    return '[' + id + ']$1';
16533  }
16534
16535  var newcontent_underscord = function(id) {
16536    return '_' + id + '_$1';
16537  }
16538
16539  var getInsertionNodeElem = function(insertionNode, insertionTraversal, $this){
16540
16541    if (!insertionNode){
16542      return $this.parent();
16543    }
16544
16545    if (typeof insertionNode == 'function'){
16546      if(insertionTraversal){
16547        console.warn('association-insertion-traversal is ignored, because association-insertion-node is given as a function.')
16548      }
16549      return insertionNode($this);
16550    }
16551
16552    if(typeof insertionNode == 'string'){
16553      if (insertionTraversal){
16554        return $this[insertionTraversal](insertionNode);
16555      }else{
16556        return insertionNode == "this" ? $this : $(insertionNode);
16557      }
16558    }
16559
16560  }
16561
16562  $(document).on('click', '.add_fields', function(e) {
16563    e.preventDefault();
16564    e.stopPropagation();
16565    
16566    var $this                 = $(this),
16567        assoc                 = $this.data('association'),
16568        assocs                = $this.data('associations'),
16569        content               = $this.data('association-insertion-template'),
16570        insertionMethod       = $this.data('association-insertion-method') || $this.data('association-insertion-position') || 'before',
16571        insertionNode         = $this.data('association-insertion-node'),
16572        insertionTraversal    = $this.data('association-insertion-traversal'),
16573        count                 = parseInt($this.data('count'), 10),
16574        regexp_braced         = new RegExp('\\[new_' + assoc + '\\](.*?\\s)', 'g'),
16575        regexp_underscord     = new RegExp('_new_' + assoc + '_(\\w*)', 'g'),
16576        new_id                = create_new_id(),
16577        new_content           = content.replace(regexp_braced, newcontent_braced(new_id)),
16578        new_contents          = [],
16579        originalEvent         = e;
16580
16581
16582    if (new_content == content) {
16583      regexp_braced     = new RegExp('\\[new_' + assocs + '\\](.*?\\s)', 'g');
16584      regexp_underscord = new RegExp('_new_' + assocs + '_(\\w*)', 'g');
16585      new_content       = content.replace(regexp_braced, newcontent_braced(new_id));
16586    }
16587
16588    new_content = new_content.replace(regexp_underscord, newcontent_underscord(new_id));
16589    new_contents = [new_content];
16590
16591    count = (isNaN(count) ? 1 : Math.max(count, 1));
16592    count -= 1;
16593
16594    while (count) {
16595      new_id      = create_new_id();
16596      new_content = content.replace(regexp_braced, newcontent_braced(new_id));
16597      new_content = new_content.replace(regexp_underscord, newcontent_underscord(new_id));
16598      new_contents.push(new_content);
16599
16600      count -= 1;
16601    }
16602
16603    var insertionNodeElem = getInsertionNodeElem(insertionNode, insertionTraversal, $this)
16604
16605    if( !insertionNodeElem || (insertionNodeElem.length == 0) ){
16606      console.warn("Couldn't find the element to insert the template. Make sure your `data-association-insertion-*` on `link_to_add_association` is correct.")
16607    }
16608
16609    $.each(new_contents, function(i, node) {
16610      var contentNode = $(node);
16611
16612      var before_insert = jQuery.Event('cocoon:before-insert');
16613      insertionNodeElem.trigger(before_insert, [contentNode, originalEvent]);
16614
16615      if (!before_insert.isDefaultPrevented()) {
16616        // allow any of the jquery dom manipulation methods (after, before, append, prepend, etc)
16617        // to be called on the node.  allows the insertion node to be the parent of the inserted
16618        // code and doesn't force it to be a sibling like after/before does. default: 'before'
16619        var addedContent = insertionNodeElem[insertionMethod](contentNode);
16620
16621        insertionNodeElem.trigger('cocoon:after-insert', [contentNode,
16622          originalEvent]);
16623      }
16624    });
16625  });
16626
16627  $(document).on('click', '.remove_fields.dynamic, .remove_fields.existing', function(e) {
16628    var $this = $(this),
16629        wrapper_class = $this.data('wrapper-class') || 'nested-fields',
16630        node_to_delete = $this.closest('.' + wrapper_class),
16631        trigger_node = node_to_delete.parent(),
16632        originalEvent = e;
16633
16634    e.preventDefault();
16635    e.stopPropagation();
16636
16637    var before_remove = jQuery.Event('cocoon:before-remove');
16638    trigger_node.trigger(before_remove, [node_to_delete, originalEvent]);
16639
16640    if (!before_remove.isDefaultPrevented()) {
16641      var timeout = trigger_node.data('remove-timeout') || 0;
16642
16643      setTimeout(function() {
16644        if ($this.hasClass('dynamic')) {
16645            node_to_delete.detach();
16646        } else {
16647            $this.prev("input[type=hidden]").val("1");
16648            node_to_delete.hide();
16649        }
16650        trigger_node.trigger('cocoon:after-remove', [node_to_delete,
16651          originalEvent]);
16652      }, timeout);
16653    }
16654  });
16655
16656
16657  $(document).on("ready page:load turbolinks:load", function() {
16658    $('.remove_fields.existing.destroyed').each(function(i, obj) {
16659      var $this = $(this),
16660          wrapper_class = $this.data('wrapper-class') || 'nested-fields';
16661
16662      $this.closest('.' + wrapper_class).hide();
16663    });
16664  });
16665
16666})(jQuery);
16667
16668
16669// moment.js
16670// version : 1.7.2
16671// author : Tim Wood
16672// license : MIT
16673// momentjs.com
16674
16675(function (undefined) {
16676
16677    /************************************
16678        Constants
16679    ************************************/
16680
16681    var moment,
16682        VERSION = "1.7.2",
16683        round = Math.round, i,
16684        // internal storage for language config files
16685        languages = {},
16686        currentLanguage = 'en',
16687
16688        // check for nodeJS
16689        hasModule = (typeof module !== 'undefined' && module.exports),
16690
16691        // Parameters to check for on the lang config.  This list of properties
16692        // will be inherited from English if not provided in a language
16693        // definition.  monthsParse is also a lang config property, but it
16694        // cannot be inherited and as such cannot be enumerated here.
16695        langConfigProperties = 'months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'),
16696
16697        // ASP.NET json date format regex
16698        aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
16699
16700        // format tokens
16701        formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|.)/g,
16702        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?)/g,
16703
16704        // parsing tokens
16705        parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,
16706
16707        // parsing token regexes
16708        parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
16709        parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
16710        parseTokenThreeDigits = /\d{3}/, // 000 - 999
16711        parseTokenFourDigits = /\d{1,4}/, // 0 - 9999
16712        parseTokenWord = /[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i, // any word characters or numbers
16713        parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z
16714        parseTokenT = /T/i, // T (ISO seperator)
16715
16716        // preliminary iso regex
16717        // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000
16718        isoRegex = /^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,
16719        isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
16720
16721        // iso time formats and regexes
16722        isoTimes = [
16723            ['HH:mm:ss.S', /T\d\d:\d\d:\d\d\.\d{1,3}/],
16724            ['HH:mm:ss', /T\d\d:\d\d:\d\d/],
16725            ['HH:mm', /T\d\d:\d\d/],
16726            ['HH', /T\d\d/]
16727        ],
16728
16729        // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
16730        parseTimezoneChunker = /([\+\-]|\d\d)/gi,
16731
16732        // getter and setter names
16733        proxyGettersAndSetters = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
16734        unitMillisecondFactors = {
16735            'Milliseconds' : 1,
16736            'Seconds' : 1e3,
16737            'Minutes' : 6e4,
16738            'Hours' : 36e5,
16739            'Days' : 864e5,
16740            'Months' : 2592e6,
16741            'Years' : 31536e6
16742        },
16743
16744        // format function strings
16745        formatFunctions = {},
16746
16747        // tokens to ordinalize and pad
16748        ordinalizeTokens = 'DDD w M D d'.split(' '),
16749        paddedTokens = 'M D H h m s w'.split(' '),
16750
16751        /*
16752         * moment.fn.format uses new Function() to create an inlined formatting function.
16753         * Results are a 3x speed boost
16754         * http://jsperf.com/momentjs-cached-format-functions
16755         *
16756         * These strings are appended into a function using replaceFormatTokens and makeFormatFunction
16757         */
16758        formatTokenFunctions = {
16759            // a = placeholder
16760            // b = placeholder
16761            // t = the current moment being formatted
16762            // v = getValueAtKey function
16763            // o = language.ordinal function
16764            // p = leftZeroFill function
16765            // m = language.meridiem value or function
16766            M    : function () {
16767                return this.month() + 1;
16768            },
16769            MMM  : function (format) {
16770                return getValueFromArray("monthsShort", this.month(), this, format);
16771            },
16772            MMMM : function (format) {
16773                return getValueFromArray("months", this.month(), this, format);
16774            },
16775            D    : function () {
16776                return this.date();
16777            },
16778            DDD  : function () {
16779                var a = new Date(this.year(), this.month(), this.date()),
16780                    b = new Date(this.year(), 0, 1);
16781                return ~~(((a - b) / 864e5) + 1.5);
16782            },
16783            d    : function () {
16784                return this.day();
16785            },
16786            dd   : function (format) {
16787                return getValueFromArray("weekdaysMin", this.day(), this, format);
16788            },
16789            ddd  : function (format) {
16790                return getValueFromArray("weekdaysShort", this.day(), this, format);
16791            },
16792            dddd : function (format) {
16793                return getValueFromArray("weekdays", this.day(), this, format);
16794            },
16795            w    : function () {
16796                var a = new Date(this.year(), this.month(), this.date() - this.day() + 5),
16797                    b = new Date(a.getFullYear(), 0, 4);
16798                return ~~((a - b) / 864e5 / 7 + 1.5);
16799            },
16800            YY   : function () {
16801                return leftZeroFill(this.year() % 100, 2);
16802            },
16803            YYYY : function () {
16804                return leftZeroFill(this.year(), 4);
16805            },
16806            a    : function () {
16807                return this.lang().meridiem(this.hours(), this.minutes(), true);
16808            },
16809            A    : function () {
16810                return this.lang().meridiem(this.hours(), this.minutes(), false);
16811            },
16812            H    : function () {
16813                return this.hours();
16814            },
16815            h    : function () {
16816                return this.hours() % 12 || 12;
16817            },
16818            m    : function () {
16819                return this.minutes();
16820            },
16821            s    : function () {
16822                return this.seconds();
16823            },
16824            S    : function () {
16825                return ~~(this.milliseconds() / 100);
16826            },
16827            SS   : function () {
16828                return leftZeroFill(~~(this.milliseconds() / 10), 2);
16829            },
16830            SSS  : function () {
16831                return leftZeroFill(this.milliseconds(), 3);
16832            },
16833            Z    : function () {
16834                var a = -this.zone(),
16835                    b = "+";
16836                if (a < 0) {
16837                    a = -a;
16838                    b = "-";
16839                }
16840                return b + leftZeroFill(~~(a / 60), 2) + ":" + leftZeroFill(~~a % 60, 2);
16841            },
16842            ZZ   : function () {
16843                var a = -this.zone(),
16844                    b = "+";
16845                if (a < 0) {
16846                    a = -a;
16847                    b = "-";
16848                }
16849                return b + leftZeroFill(~~(10 * a / 6), 4);
16850            }
16851        };
16852
16853    function getValueFromArray(key, index, m, format) {
16854        var lang = m.lang();
16855        return lang[key].call ? lang[key](m, format) : lang[key][index];
16856    }
16857
16858    function padToken(func, count) {
16859        return function (a) {
16860            return leftZeroFill(func.call(this, a), count);
16861        };
16862    }
16863    function ordinalizeToken(func) {
16864        return function (a) {
16865            var b = func.call(this, a);
16866            return b + this.lang().ordinal(b);
16867        };
16868    }
16869
16870    while (ordinalizeTokens.length) {
16871        i = ordinalizeTokens.pop();
16872        formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i]);
16873    }
16874    while (paddedTokens.length) {
16875        i = paddedTokens.pop();
16876        formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
16877    }
16878    formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
16879
16880
16881    /************************************
16882        Constructors
16883    ************************************/
16884
16885
16886    // Moment prototype object
16887    function Moment(date, isUTC, lang) {
16888        this._d = date;
16889        this._isUTC = !!isUTC;
16890        this._a = date._a || null;
16891        this._lang = lang || false;
16892    }
16893
16894    // Duration Constructor
16895    function Duration(duration) {
16896        var data = this._data = {},
16897            years = duration.years || duration.y || 0,
16898            months = duration.months || duration.M || 0,
16899            weeks = duration.weeks || duration.w || 0,
16900            days = duration.days || duration.d || 0,
16901            hours = duration.hours || duration.h || 0,
16902            minutes = duration.minutes || duration.m || 0,
16903            seconds = duration.seconds || duration.s || 0,
16904            milliseconds = duration.milliseconds || duration.ms || 0;
16905
16906        // representation for dateAddRemove
16907        this._milliseconds = milliseconds +
16908            seconds * 1e3 + // 1000
16909            minutes * 6e4 + // 1000 * 60
16910            hours * 36e5; // 1000 * 60 * 60
16911        // Because of dateAddRemove treats 24 hours as different from a
16912        // day when working around DST, we need to store them separately
16913        this._days = days +
16914            weeks * 7;
16915        // It is impossible translate months into days without knowing
16916        // which months you are are talking about, so we have to store
16917        // it separately.
16918        this._months = months +
16919            years * 12;
16920
16921        // The following code bubbles up values, see the tests for
16922        // examples of what that means.
16923        data.milliseconds = milliseconds % 1000;
16924        seconds += absRound(milliseconds / 1000);
16925
16926        data.seconds = seconds % 60;
16927        minutes += absRound(seconds / 60);
16928
16929        data.minutes = minutes % 60;
16930        hours += absRound(minutes / 60);
16931
16932        data.hours = hours % 24;
16933        days += absRound(hours / 24);
16934
16935        days += weeks * 7;
16936        data.days = days % 30;
16937
16938        months += absRound(days / 30);
16939
16940        data.months = months % 12;
16941        years += absRound(months / 12);
16942
16943        data.years = years;
16944
16945        this._lang = false;
16946    }
16947
16948
16949    /************************************
16950        Helpers
16951    ************************************/
16952
16953
16954    function absRound(number) {
16955        if (number < 0) {
16956            return Math.ceil(number);
16957        } else {
16958            return Math.floor(number);
16959        }
16960    }
16961
16962    // left zero fill a number
16963    // see http://jsperf.com/left-zero-filling for performance comparison
16964    function leftZeroFill(number, targetLength) {
16965        var output = number + '';
16966        while (output.length < targetLength) {
16967            output = '0' + output;
16968        }
16969        return output;
16970    }
16971
16972    // helper function for _.addTime and _.subtractTime
16973    function addOrSubtractDurationFromMoment(mom, duration, isAdding) {
16974        var ms = duration._milliseconds,
16975            d = duration._days,
16976            M = duration._months,
16977            currentDate;
16978
16979        if (ms) {
16980            mom._d.setTime(+mom + ms * isAdding);
16981        }
16982        if (d) {
16983            mom.date(mom.date() + d * isAdding);
16984        }
16985        if (M) {
16986            currentDate = mom.date();
16987            mom.date(1)
16988                .month(mom.month() + M * isAdding)
16989                .date(Math.min(currentDate, mom.daysInMonth()));
16990        }
16991    }
16992
16993    // check if is an array
16994    function isArray(input) {
16995        return Object.prototype.toString.call(input) === '[object Array]';
16996    }
16997
16998    // compare two arrays, return the number of differences
16999    function compareArrays(array1, array2) {
17000        var len = Math.min(array1.length, array2.length),
17001            lengthDiff = Math.abs(array1.length - array2.length),
17002            diffs = 0,
17003            i;
17004        for (i = 0; i < len; i++) {
17005            if (~~array1[i] !== ~~array2[i]) {
17006                diffs++;
17007            }
17008        }
17009        return diffs + lengthDiff;
17010    }
17011
17012    // convert an array to a date.
17013    // the array should mirror the parameters below
17014    // note: all values past the year are optional and will default to the lowest possible value.
17015    // [year, month, day , hour, minute, second, millisecond]
17016    function dateFromArray(input, asUTC, hoursOffset, minutesOffset) {
17017        var i, date, forValid = [];
17018        for (i = 0; i < 7; i++) {
17019            forValid[i] = input[i] = (input[i] == null) ? (i === 2 ? 1 : 0) : input[i];
17020        }
17021        // we store whether we used utc or not in the input array
17022        input[7] = forValid[7] = asUTC;
17023        // if the parser flagged the input as invalid, we pass the value along
17024        if (input[8] != null) {
17025            forValid[8] = input[8];
17026        }
17027        // add the offsets to the time to be parsed so that we can have a clean array
17028        // for checking isValid
17029        input[3] += hoursOffset || 0;
17030        input[4] += minutesOffset || 0;
17031        date = new Date(0);
17032        if (asUTC) {
17033            date.setUTCFullYear(input[0], input[1], input[2]);
17034            date.setUTCHours(input[3], input[4], input[5], input[6]);
17035        } else {
17036            date.setFullYear(input[0], input[1], input[2]);
17037            date.setHours(input[3], input[4], input[5], input[6]);
17038        }
17039        date._a = forValid;
17040        return date;
17041    }
17042
17043    // Loads a language definition into the `languages` cache.  The function
17044    // takes a key and optionally values.  If not in the browser and no values
17045    // are provided, it will load the language file module.  As a convenience,
17046    // this function also returns the language values.
17047    function loadLang(key, values) {
17048        var i, m,
17049            parse = [];
17050
17051        if (!values && hasModule) {
17052            values = require('./lang/' + key);
17053        }
17054
17055        for (i = 0; i < langConfigProperties.length; i++) {
17056            // If a language definition does not provide a value, inherit
17057            // from English
17058            values[langConfigProperties[i]] = values[langConfigProperties[i]] ||
17059              languages.en[langConfigProperties[i]];
17060        }
17061
17062        for (i = 0; i < 12; i++) {
17063            m = moment([2000, i]);
17064            parse[i] = new RegExp('^' + (values.months[i] || values.months(m, '')) +
17065                '|^' + (values.monthsShort[i] || values.monthsShort(m, '')).replace('.', ''), 'i');
17066        }
17067        values.monthsParse = values.monthsParse || parse;
17068
17069        languages[key] = values;
17070
17071        return values;
17072    }
17073
17074    // Determines which language definition to use and returns it.
17075    //
17076    // With no parameters, it will return the global language.  If you
17077    // pass in a language key, such as 'en', it will return the
17078    // definition for 'en', so long as 'en' has already been loaded using
17079    // moment.lang.  If you pass in a moment or duration instance, it
17080    // will decide the language based on that, or default to the global
17081    // language.
17082    function getLangDefinition(m) {
17083        var langKey = (typeof m === 'string') && m ||
17084                      m && m._lang ||
17085                      null;
17086
17087        return langKey ? (languages[langKey] || loadLang(langKey)) : moment;
17088    }
17089
17090
17091    /************************************
17092        Formatting
17093    ************************************/
17094
17095
17096    function removeFormattingTokens(input) {
17097        if (input.match(/\[.*\]/)) {
17098            return input.replace(/^\[|\]$/g, "");
17099        }
17100        return input.replace(/\\/g, "");
17101    }
17102
17103    function makeFormatFunction(format) {
17104        var array = format.match(formattingTokens), i, length;
17105
17106        for (i = 0, length = array.length; i < length; i++) {
17107            if (formatTokenFunctions[array[i]]) {
17108                array[i] = formatTokenFunctions[array[i]];
17109            } else {
17110                array[i] = removeFormattingTokens(array[i]);
17111            }
17112        }
17113
17114        return function (mom) {
17115            var output = "";
17116            for (i = 0; i < length; i++) {
17117                output += typeof array[i].call === 'function' ? array[i].call(mom, format) : array[i];
17118            }
17119            return output;
17120        };
17121    }
17122
17123    // format date using native date object
17124    function formatMoment(m, format) {
17125        var i = 5;
17126
17127        function replaceLongDateFormatTokens(input) {
17128            return m.lang().longDateFormat[input] || input;
17129        }
17130
17131        while (i-- && localFormattingTokens.test(format)) {
17132            format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
17133        }
17134
17135        if (!formatFunctions[format]) {
17136            formatFunctions[format] = makeFormatFunction(format);
17137        }
17138
17139        return formatFunctions[format](m);
17140    }
17141
17142
17143    /************************************
17144        Parsing
17145    ************************************/
17146
17147
17148    // get the regex to find the next token
17149    function getParseRegexForToken(token) {
17150        switch (token) {
17151        case 'DDDD':
17152            return parseTokenThreeDigits;
17153        case 'YYYY':
17154            return parseTokenFourDigits;
17155        case 'S':
17156        case 'SS':
17157        case 'SSS':
17158        case 'DDD':
17159            return parseTokenOneToThreeDigits;
17160        case 'MMM':
17161        case 'MMMM':
17162        case 'dd':
17163        case 'ddd':
17164        case 'dddd':
17165        case 'a':
17166        case 'A':
17167            return parseTokenWord;
17168        case 'Z':
17169        case 'ZZ':
17170            return parseTokenTimezone;
17171        case 'T':
17172            return parseTokenT;
17173        case 'MM':
17174        case 'DD':
17175        case 'YY':
17176        case 'HH':
17177        case 'hh':
17178        case 'mm':
17179        case 'ss':
17180        case 'M':
17181        case 'D':
17182        case 'd':
17183        case 'H':
17184        case 'h':
17185        case 'm':
17186        case 's':
17187            return parseTokenOneOrTwoDigits;
17188        default :
17189            return new RegExp(token.replace('\\', ''));
17190        }
17191    }
17192
17193    // function to convert string input to date
17194    function addTimeToArrayFromToken(token, input, datePartArray, config) {
17195        var a, b;
17196
17197        switch (token) {
17198        // MONTH
17199        case 'M' : // fall through to MM
17200        case 'MM' :
17201            datePartArray[1] = (input == null) ? 0 : ~~input - 1;
17202            break;
17203        case 'MMM' : // fall through to MMMM
17204        case 'MMMM' :
17205            for (a = 0; a < 12; a++) {
17206                if (getLangDefinition().monthsParse[a].test(input)) {
17207                    datePartArray[1] = a;
17208                    b = true;
17209                    break;
17210                }
17211            }
17212            // if we didn't find a month name, mark the date as invali
17212d.
17213            if (!b) {
17214                datePartArray[8] = false;
17215            }
17216            break;
17217        // DAY OF MONTH
17218        case 'D' : // fall through to DDDD
17219        case 'DD' : // fall through to DDDD
17220        case 'DDD' : // fall through to DDDD
17221        case 'DDDD' :
17222            if (input != null) {
17223                datePartArray[2] = ~~input;
17224            }
17225            break;
17226        // YEAR
17227        case 'YY' :
17228            datePartArray[0] = ~~input + (~~input > 70 ? 1900 : 2000);
17229            break;
17230        case 'YYYY' :
17231            datePartArray[0] = ~~Math.abs(input);
17232            break;
17233        // AM / PM
17234        case 'a' : // fall through to A
17235        case 'A' :
17236            config.isPm = ((input + '').toLowerCase() === 'pm');
17237            break;
17238        // 24 HOUR
17239        case 'H' : // fall through to hh
17240        case 'HH' : // fall through to hh
17241        case 'h' : // fall through to hh
17242        case 'hh' :
17243            datePartArray[3] = ~~input;
17244            break;
17245        // MINUTE
17246        case 'm' : // fall through to mm
17247        case 'mm' :
17248            datePartArray[4] = ~~input;
17249            break;
17250        // SECOND
17251        case 's' : // fall through to ss
17252        case 'ss' :
17253            datePartArray[5] = ~~input;
17254            break;
17255        // MILLISECOND
17256        case 'S' :
17257        case 'SS' :
17258        case 'SSS' :
17259            datePartArray[6] = ~~ (('0.' + input) * 1000);
17260            break;
17261        // TIMEZONE
17262        case 'Z' : // fall through to ZZ
17263        case 'ZZ' :
17264            config.isUTC = true;
17265            a = (input + '').match(parseTimezoneChunker);
17266            if (a && a[1]) {
17267                config.tzh = ~~a[1];
17268            }
17269            if (a && a[2]) {
17270                config.tzm = ~~a[2];
17271            }
17272            // reverse offsets
17273            if (a && a[0] === '+') {
17274                config.tzh = -config.tzh;
17275                config.tzm = -config.tzm;
17276            }
17277            break;
17278        }
17279
17280        // if the input is null, the date is not valid
17281        if (input == null) {
17282            datePartArray[8] = false;
17283        }
17284    }
17285
17286    // date from string and format string
17287    function makeDateFromStringAndFormat(string, format) {
17288        // This array is used to make a Date, either with `new Date` or `Date.UTC`
17289        // We store some additional data on the array for validation
17290        // datePartArray[7] is true if the Date was created with `Date.UTC` and false if created with `new Date`
17291        // datePartArray[8] is false if the Date is invalid, and undefined if the validity is unknown.
17292        var datePartArray = [0, 0, 1, 0, 0, 0, 0],
17293            config = {
17294                tzh : 0, // timezone hour offset
17295                tzm : 0  // timezone minute offset
17296            },
17297            tokens = format.match(formattingTokens),
17298            i, parsedInput;
17299
17300        for (i = 0; i < tokens.length; i++) {
17301            parsedInput = (getParseRegexForToken(tokens[i]).exec(string) || [])[0];
17302            if (parsedInput) {
17303                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
17304            }
17305            // don't parse if its not a known token
17306            if (formatTokenFunctions[tokens[i]]) {
17307                addTimeToArrayFromToken(tokens[i], parsedInput, datePartArray, config);
17308            }
17309        }
17310        // handle am pm
17311        if (config.isPm && datePartArray[3] < 12) {
17312            datePartArray[3] += 12;
17313        }
17314        // if is 12 am, change hours to 0
17315        if (config.isPm === false && datePartArray[3] === 12) {
17316            datePartArray[3] = 0;
17317        }
17318        // return
17319        return dateFromArray(datePartArray, config.isUTC, config.tzh, config.tzm);
17320    }
17321
17322    // date from string and array of format strings
17323    function makeDateFromStringAndArray(string, formats) {
17324        var output,
17325            inputParts = string.match(parseMultipleFormatChunker) || [],
17326            formattedInputParts,
17327            scoreToBeat = 99,
17328            i,
17329            currentDate,
17330            currentScore;
17331        for (i = 0; i < formats.length; i++) {
17332            currentDate = makeDateFromStringAndFormat(string, formats[i]);
17333            formattedInputParts = formatMoment(new Moment(currentDate), formats[i]).match(parseMultipleFormatChunker) || [];
17334            currentScore = compareArrays(inputParts, formattedInputParts);
17335            if (currentScore < scoreToBeat) {
17336                scoreToBeat = currentScore;
17337                output = currentDate;
17338            }
17339        }
17340        return output;
17341    }
17342
17343    // date from iso format
17344    function makeDateFromString(string) {
17345        var format = 'YYYY-MM-DDT',
17346            i;
17347        if (isoRegex.exec(string)) {
17348            for (i = 0; i < 4; i++) {
17349                if (isoTimes[i][1].exec(string)) {
17350                    format += isoTimes[i][0];
17351                    break;
17352                }
17353            }
17354            return parseTokenTimezone.exec(string) ?
17355                makeDateFromStringAndFormat(string, format + ' Z') :
17356                makeDateFromStringAndFormat(string, format);
17357        }
17358        return new Date(string);
17359    }
17360
17361
17362    /************************************
17363        Relative Time
17364    ************************************/
17365
17366
17367    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
17368    function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
17369        var rt = lang.relativeTime[string];
17370        return (typeof rt === 'function') ?
17371            rt(number || 1, !!withoutSuffix, string, isFuture) :
17372            rt.replace(/%d/i, number || 1);
17373    }
17374
17375    function relativeTime(milliseconds, withoutSuffix, lang) {
17376        var seconds = round(Math.abs(milliseconds) / 1000),
17377            minutes = round(seconds / 60),
17378            hours = round(minutes / 60),
17379            days = round(hours / 24),
17380            years = round(days / 365),
17381            args = seconds < 45 && ['s', seconds] ||
17382                minutes === 1 && ['m'] ||
17383                minutes < 45 && ['mm', minutes] ||
17384                hours === 1 && ['h'] ||
17385                hours < 22 && ['hh', hours] ||
17386                days === 1 && ['d'] ||
17387                days <= 25 && ['dd', days] ||
17388                days <= 45 && ['M'] ||
17389                days < 345 && ['MM', round(days / 30)] ||
17390                years === 1 && ['y'] || ['yy', years];
17391        args[2] = withoutSuffix;
17392        args[3] = milliseconds > 0;
17393        args[4] = lang;
17394        return substituteTimeAgo.apply({}, args);
17395    }
17396
17397
17398    /************************************
17399        Top Level Functions
17400    ************************************/
17401
17402
17403    moment = function (input, format) {
17404        if (input === null || input === '') {
17405            return null;
17406        }
17407        var date,
17408            matched;
17409        // parse Moment object
17410        if (moment.isMoment(input)) {
17411            return new Moment(new Date(+input._d), input._isUTC, input._lang);
17412        // parse string and format
17413        } else if (format) {
17414            if (isArray(format)) {
17415                date = makeDateFromStringAndArray(input, format);
17416            } else {
17417                date = makeDateFromStringAndFormat(input, format);
17418            }
17419        // evaluate it as a JSON-encoded date
17420        } else {
17421            matched = aspNetJsonRegex.exec(input);
17422            date = input === undefined ? new Date() :
17423                matched ? new Date(+matched[1]) :
17424                input instanceof Date ? input :
17425                isArray(input) ? dateFromArray(input) :
17426                typeof input === 'string' ? makeDateFromString(input) :
17427                new Date(input);
17428        }
17429
17430        return new Moment(date);
17431    };
17432
17433    // creating with utc
17434    moment.utc = function (input, format) {
17435        if (isArray(input)) {
17436            return new Moment(dateFromArray(input, true), true);
17437        }
17438        // if we don't have a timezone, we need to add one to trigger parsing into utc
17439        if (typeof input === 'string' && !parseTokenTimezone.exec(input)) {
17440            input += ' +0000';
17441            if (format) {
17442                format += ' Z';
17443            }
17444        }
17445        return moment(input, format).utc();
17446    };
17447
17448    // creating with unix timestamp (in seconds)
17449    moment.unix = function (input) {
17450        return moment(input * 1000);
17451    };
17452
17453    // duration
17454    moment.duration = function (input, key) {
17455        var isDuration = moment.isDuration(input),
17456            isNumber = (typeof input === 'number'),
17457            duration = (isDuration ? input._data : (isNumber ? {} : input)),
17458            ret;
17459
17460        if (isNumber) {
17461            if (key) {
17462                duration[key] = input;
17463            } else {
17464                duration.milliseconds = input;
17465            }
17466        }
17467
17468        ret = new Duration(duration);
17469
17470        if (isDuration) {
17471            ret._lang = input._lang;
17472        }
17473
17474        return ret;
17475    };
17476
17477    // humanizeDuration
17478    // This method is deprecated in favor of the new Duration object.  Please
17479    // see the moment.duration method.
17480    moment.humanizeDuration = function (num, type, withSuffix) {
17481        return moment.duration(num, type === true ? null : type).humanize(type === true ? true : withSuffix);
17482    };
17483
17484    // version number
17485    moment.version = VERSION;
17486
17487    // default format
17488    moment.defaultFormat = isoFormat;
17489
17490    // This function will load languages and then set the global language.  If
17491    // no arguments are passed in, it will simply return the current global
17492    // language key.
17493    moment.lang = function (key, values) {
17494        var i;
17495
17496        if (!key) {
17497            return currentLanguage;
17498        }
17499        if (values || !languages[key]) {
17500            loadLang(key, values);
17501        }
17502        if (languages[key]) {
17503            // deprecated, to get the language definition variables, use the
17504            // moment.fn.lang method or the getLangDefinition function.
17505            for (i = 0; i < langConfigProperties.length; i++) {
17506                moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]];
17507            }
17508            moment.monthsParse = languages[key].monthsParse;
17509            currentLanguage = key;
17510        }
17511    };
17512
17513    // returns language data
17514    moment.langData = getLangDefinition;
17515
17516    // compare moment object
17517    moment.isMoment = function (obj) {
17518        return obj instanceof Moment;
17519    };
17520
17521    // for typechecking Duration objects
17522    moment.isDuration = function (obj) {
17523        return obj instanceof Duration;
17524    };
17525
17526    // Set default language, other languages will inherit from English.
17527    moment.lang('en', {
17528        months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
17529        monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
17530        weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
17531        weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
17532        weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
17533        longDateFormat : {
17534            LT : "h:mm A",
17535            L : "MM/DD/YYYY",
17536            LL : "MMMM D YYYY",
17537            LLL : "MMMM D YYYY LT",
17538            LLLL : "dddd, MMMM D YYYY LT"
17539        },
17540        meridiem : function (hours, minutes, isLower) {
17541            if (hours > 11) {
17542                return isLower ? 'pm' : 'PM';
17543            } else {
17544                return isLower ? 'am' : 'AM';
17545            }
17546        },
17547        calendar : {
17548            sameDay : '[Today at] LT',
17549            nextDay : '[Tomorrow at] LT',
17550            nextWeek : 'dddd [at] LT',
17551            lastDay : '[Yesterday at] LT',
17552            lastWeek : '[last] dddd [at] LT',
17553            sameElse : 'L'
17554        },
17555        relativeTime : {
17556            future : "in %s",
17557            past : "%s ago",
17558            s : "a few seconds",
17559            m : "a minute",
17560            mm : "%d minutes",
17561            h : "an hour",
17562            hh : "%d hours",
17563            d : "a day",
17564            dd : "%d days",
17565            M : "a month",
17566            MM : "%d months",
17567            y : "a year",
17568            yy : "%d years"
17569        },
17570        ordinal : function (number) {
17571            var b = number % 10;
17572            return (~~ (number % 100 / 10) === 1) ? 'th' :
17573                (b === 1) ? 'st' :
17574                (b === 2) ? 'nd' :
17575                (b === 3) ? 'rd' : 'th';
17576        }
17577    });
17578
17579
17580    /************************************
17581        Moment Prototype
17582    ************************************/
17583
17584
17585    moment.fn = Moment.prototype = {
17586
17587        clone : function () {
17588            return moment(this);
17589        },
17590
17591        valueOf : function () {
17592            return +this._d;
17593        },
17594
17595        unix : function () {
17596            return Math.floor(+this._d / 1000);
17597        },
17598
17599        toString : function () {
17600            return this._d.toString();
17601        },
17602
17603        toDate : function () {
17604            return this._d;
17605        },
17606
17607        toArray : function () {
17608            var m = this;
17609            return [
17610                m.year(),
17611                m.month(),
17612                m.date(),
17613                m.hours(),
17614                m.minutes(),
17615                m.seconds(),
17616                m.milliseconds(),
17617                !!this._isUTC
17618            ];
17619        },
17620
17621        isValid : function () {
17622            if (this._a) {
17623                // if the parser finds that the input is invalid, it sets
17624                // the eighth item in the input array to false.
17625                if (this._a[8] != null) {
17626                    return !!this._a[8];
17627                }
17628                return !compareArrays(this._a, (this._a[7] ? moment.utc(this._a) : moment(this._a)).toArray());
17629            }
17630            return !isNaN(this._d.getTime());
17631        },
17632
17633        utc : function () {
17634            this._isUTC = true;
17635            return this;
17636        },
17637
17638        local : function () {
17639            this._isUTC = false;
17640            return this;
17641        },
17642
17643        format : function (inputString) {
17644            return formatMoment(this, inputString ? inputString : moment.defaultFormat);
17645        },
17646
17647        add : function (input, val) {
17648            var dur = val ? moment.duration(+val, input) : moment.duration(input);
17649            addOrSubtractDurationFromMoment(this, dur, 1);
17650            return this;
17651        },
17652
17653        subtract : function (input, val) {
17654            var dur = val ? moment.duration(+val, input) : moment.duration(input);
17655            addOrSubtractDurationFromMoment(this, dur, -1);
17656            return this;
17657        },
17658
17659        diff : function (input, val, asFloat) {
17660            var inputMoment = this._isUTC ? moment(input).utc() : moment(input).local(),
17661                zoneDiff = (this.zone() - inputMoment.zone()) * 6e4,
17662                diff = this._d - inputMoment._d - zoneDiff,
17663                year = this.year() - inputMoment.year(),
17664                month = this.month() - inputMoment.month(),
17665                date = this.date() - inputMoment.date(),
17666                output;
17667            if (val === 'months') {
17668                output = year * 12 + month + date / 30;
17669            } else if (val === 'years') {
17670                output = year + (month + date / 30) / 12;
17671            } else {
17672                output = val === 'seconds' ? diff / 1e3 : // 1000
17673                    val === 'minutes' ? diff / 6e4 : // 1000 * 60
17674                    val === 'hours' ? diff / 36e5 : // 1000 * 60 * 60
17675                    val === 'days' ? diff / 864e5 : // 1000 * 60 * 60 * 24
17676                    val === 'weeks' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7
17677                    diff;
17678            }
17679            return asFloat ? output : round(output);
17680        },
17681
17682        from : function (time, withoutSuffix) {
17683            return moment.duration(this.diff(time)).lang(this._lang).humanize(!withoutSuffix);
17684        },
17685
17686        fromNow : function (withoutSuffix) {
17687            return this.from(moment(), withoutSuffix);
17688        },
17689
17690        calendar : function () {
17691            var diff = this.diff(moment().sod(), 'days', true),
17692                calendar = this.lang().calendar,
17693                allElse = calendar.sameElse,
17694                format = diff < -6 ? allElse :
17695                diff < -1 ? calendar.lastWeek :
17696                diff < 0 ? calendar.lastDay :
17697                diff < 1 ? calendar.sameDay :
17698                diff < 2 ? calendar.nextDay :
17699                diff < 7 ? calendar.nextWeek : allElse;
17700            return this.format(typeof format === 'function' ? format.apply(this) : format);
17701        },
17702
17703        isLeapYear : function () {
17704            var year = this.year();
17705            return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
17706        },
17707
17708        isDST : function () {
17709            return (this.zone() < moment([this.year()]).zone() ||
17710                this.zone() < moment([this.year(), 5]).zone());
17711        },
17712
17713        day : function (input) {
17714            var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
17715            return input == null ? day :
17716                this.add({ d : input - day });
17717        },
17718
17719        startOf: function (val) {
17720            // the following switch intentionally omits break keywords
17721            // to utilize falling through the cases.
17722            switch (val.replace(/s$/, '')) {
17723            case 'year':
17724                this.month(0);
17725                /* falls through */
17726            case 'month':
17727                this.date(1);
17728                /* falls through */
17729            case 'day':
17730                this.hours(0);
17731                /* falls through */
17732            case 'hour':
17733                this.minutes(0);
17734                /* falls through */
17735            case 'minute':
17736                this.seconds(0);
17737                /* falls through */
17738            case 'second':
17739                this.milliseconds(0);
17740                /* falls through */
17741            }
17742            return this;
17743        },
17744
17745        endOf: function (val) {
17746            return this.startOf(val).add(val.replace(/s?$/, 's'), 1).subtract('ms', 1);
17747        },
17748
17749        sod: function () {
17750            return this.clone().startOf('day');
17751        },
17752
17753        eod: function () {
17754            // end of day = start of day plus 1 day, minus 1 millisecond
17755            return this.clone().endOf('day');
17756        },
17757
17758        zone : function () {
17759            return this._isUTC ? 0 : this._d.getTimezoneOffset();
17760        },
17761
17762        daysInMonth : function () {
17763            return moment.utc([this.year(), this.month() + 1, 0]).date();
17764        },
17765
17766        // If passed a language key, it will set the language for this
17767        // instance.  Otherwise, it will return the language configuration
17768        // variables for this instance.
17769        lang : function (lang) {
17770            if (lang === undefined) {
17771                return getLangDefinition(this);
17772            } else {
17773                this._lang = lang;
17774                return this;
17775            }
17776        }
17777    };
17778
17779    // helper for adding shortcuts
17780    function makeGetterAndSetter(name, key) {
17781        moment.fn[name] = function (input) {
17782            var utc = this._isUTC ? 'UTC' : '';
17783            if (input != null) {
17784                this._d['set' + utc + key](input);
17785                return this;
17786            } else {
17787                return this._d['get' + utc + key]();
17788            }
17789        };
17790    }
17791
17792    // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds)
17793    for (i = 0; i < proxyGettersAndSetters.length; i ++) {
17794        makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase(), proxyGettersAndSetters[i]);
17795    }
17796
17797    // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
17798    makeGetterAndSetter('year', 'FullYear');
17799
17800
17801    /************************************
17802        Duration Prototype
17803    ************************************/
17804
17805
17806    moment.duration.fn = Duration.prototype = {
17807        weeks : function () {
17808            return absRound(this.days() / 7);
17809        },
17810
17811        valueOf : function () {
17812            return this._milliseconds +
17813              this._days * 864e5 +
17814              this._months * 2592e6;
17815        },
17816
17817        humanize : function (withSuffix) {
17818            var difference = +this,
17819                rel = this.lang().relativeTime,
17820                output = relativeTime(difference, !withSuffix, this.lang()),
17821                fromNow = difference <= 0 ? rel.past : rel.future;
17822
17823            if (withSuffix) {
17824                if (typeof fromNow === 'function') {
17825                    output = fromNow(output);
17826                } else {
17827                    output = fromNow.replace(/%s/i, output);
17828                }
17829            }
17830
17831            return output;
17832        },
17833
17834        lang : moment.fn.lang
17835    };
17836
17837    function makeDurationGetter(name) {
17838        moment.duration.fn[name] = function () {
17839            return this._data[name];
17840        };
17841    }
17842
17843    function makeDurationAsGetter(name, factor) {
17844        moment.duration.fn['as' + name] = function () {
17845            return +this / factor;
17846        };
17847    }
17848
17849    for (i in unitMillisecondFactors) {
17850        if (unitMillisecondFactors.hasOwnProperty(i)) {
17851            makeDurationAsGetter(i, unitMillisecondFactors[i]);
17852            makeDurationGetter(i.toLowerCase());
17853        }
17854    }
17855
17856    makeDurationAsGetter('Weeks', 6048e5);
17857
17858
17859    /************************************
17860        Exposing Moment
17861    ************************************/
17862
17863
17864    // CommonJS module is defined
17865    if (hasModule) {
17866        module.exports = moment;
17867    }
17868    /*global ender:false */
17869    if (typeof ender === 'undefined') {
17870        // here, `this` means `window` in the browser, or `global` on the server
17871        // add `moment` as a global object via a string identifier,
17872        // for Closure Compiler "advanced" mode
17873        this['moment'] = moment;
17874    }
17875    /*global define:false */
17876    if (typeof define === "function" && define.amd) {
17877        define("moment", [], function () {
17878            return moment;
17879        });
17880    }
17881}
17881).call(this);
17882/*! @preserve
17883 * numeral.js
17884 * version : 2.0.6
17885 * author : Adam Draper
17886 * license : MIT
17887 * http://adamwdraper.github.com/Numeral-js/
17888 */
vendor: 11,308 bytes, lines 17889-17890
17889
17890!function(a,b){"function"==typeof define&&define.amd?define(b):"object"==typeof module&&module.exports?module.exports=b():a.numeral=b()}(this,function(){function a(a,b){this._input=a,this._value=b}var b,c,d="2.0.6",e={},f={},g={currentLocale:"en",zeroFormat:null,nullFormat:null,defaultFormat:"0,0",scalePercentBy100:!0},h={currentLocale:g.currentLocale,zeroFormat:g.zeroFormat,nullFormat:g.nullFormat,defaultFormat:g.defaultFormat,scalePercentBy100:g.scalePercentBy100};return b=function(d){var f,g,i,j;if(b.isNumeral(d))f=d.value();else if(0===d||"undefined"==typeof d)f=0;else if(null===d||c.isNaN(d))f=null;else if("string"==typeof d)if(h.zeroFormat&&d===h.zeroFormat)f=0;else if(h.nullFormat&&d===h.nullFormat||!d.replace(/[^0-9]+/g,"").length)f=null;else{for(g in e)if(j="function"==typeof e[g].regexps.unformat?e[g].regexps.unformat():e[g].regexps.unformat,j&&d.match(j)){i=e[g].unformat;break}i=i||b._.stringToNumber,f=i(d)}else f=Number(d)||null;return new a(d,f)},b.version=d,b.isNumeral=function(b){return b instanceof a},b._=c={numberToFormat:function(a,c,d){var e,g,h,i,j,k,l,m=f[b.options.currentLocale],n=!1,o=!1,p=0,q="",r=1e12,s=1e9,t=1e6,u=1e3,v="",w=!1;if(a=a||0,g=Math.abs(a),b._.includes(c,"(")?(n=!0,c=c.replace(/[\(|\)]/g,"")):(b._.includes(c,"+")||b._.includes(c,"-"))&&(j=b._.includes(c,"+")?c.indexOf("+"):0>a?c.indexOf("-"):-1,c=c.replace(/[\+|\-]/g,"")),b._.includes(c,"a")&&(e=c.match(/a(k|m|b|t)?/),e=e?e[1]:!1,b._.includes(c," a")&&(q=" "),c=c.replace(new RegExp(q+"a[kmbt]?"),""),g>=r&&!e||"t"===e?(q+=m.abbreviations.trillion,a/=r):r>g&&g>=s&&!e||"b"===e?(q+=m.abbreviations.billion,a/=s):s>g&&g>=t&&!e||"m"===e?(q+=m.abbreviations.million,a/=t):(t>g&&g>=u&&!e||"k"===e)&&(q+=m.abbreviations.thousand,a/=u)),b._.includes(c,"[.]")&&(o=!0,c=c.replace("[.]",".")),h=a.toString().split(".")[0],i=c.split(".")[1],k=c.indexOf(","),p=(c.split(".")[0].split(",")[0].match(/0/g)||[]).length,i?(b._.includes(i,"[")?(i=i.replace("]",""),i=i.split("["),v=b._.toFixed(a,i[0].length+i[1].length,d,i[1].length)):v=b._.toFixed(a,i.length,d),h=v.split(".")[0],v=b._.includes(v,".")?m.delimiters.decimal+v.split(".")[1]:"",o&&0===Number(v.slice(1))&&(v="")):h=b._.toFixed(a,0,d),q&&!e&&Number(h)>=1e3&&q!==m.abbreviations.trillion)switch(h=String(Number(h)/1e3),q){case m.abbreviations.thousand:q=m.abbreviations.million;break;case m.abbreviations.million:q=m.abbreviations.billion;break;case m.abbreviations.billion:q=m.abbreviations.trillion}if(b._.includes(h,"-")&&(h=h.slice(1),w=!0),h.length<p)for(var x=p-h.length;x>0;x--)h="0"+h;return k>-1&&(h=h.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1"+m.delimiters.thousands)),0===c.indexOf(".")&&(h=""),l=h+v+(q?q:""),n?l=(n&&w?"(":"")+l+(n&&w?")":""):j>=0?l=0===j?(w?"-":"+")+l:l+(w?"-":"+"):w&&(l="-"+l),l},stringToNumber:function(a){var b,c,d,e=f[h.currentLocale],g=a,i={thousand:3,million:6,billion:9,trillion:12};if(h.zeroFormat&&a===h.zeroFormat)c=0;else if(h.nullFormat&&a===h.nullFormat||!a.replace(/[^0-9]+/g,"").length)c=null;else{c=1,"."!==e.delimiters.decimal&&(a=a.replace(/\./g,"").replace(e.delimiters.decimal,"."));for(b in i)if(d=new RegExp("[^a-zA-Z]"+e.abbreviations[b]+"(?:\\)|(\\"+e.currency.symbol+")?(?:\\))?)?$"),g.match(d)){c*=Math.pow(10,i[b]);break}c*=(a.split("-").length+Math.min(a.split("(").length-1,a.split(")").length-1))%2?1:-1,a=a.replace(/[^0-9\.]+/g,""),c*=Number(a)}return c},isNaN:function(a){return"number"==typeof a&&isNaN(a)},includes:function(a,b){return-1!==a.indexOf(b)},insert:function(a,b,c){return a.slice(0,c)+b+a.slice(c)},reduce:function(a,b){if(null===this)throw new TypeError("Array.prototype.reduce called on null or undefined");if("function"!=typeof b)throw new TypeError(b+" is not a function");var c,d=Object(a),e=d.length>>>0,f=0;if(3===arguments.length)c=arguments[2];else{for(;e>f&&!(f in d);)f++;if(f>=e)throw new TypeError("Reduce of empty array with no initial value");c=d[f++]}for(;e>f;f++)f in d&&(c=b(c,d[f],f,d));return c},multiplier:function(a){var b=a.toString().split(".");return b.length<2?1:Math.pow(10,b[1].length)},correctionFactor:function(){var a=Array.prototype.slice.call(arguments);return a.reduce(function(a,b){var d=c.multiplier(b);return a>d?a:d},1)},toFixed:function(a,b,c,d){var e,f,g,h,i=a.toString().split("."),j=b-(d||0);return e=2===i.length?Math.min(Math.max(i[1].length,j),b):j,g=Math.pow(10,e),h=(c(a+"e+"+e)/g).toFixed(e),d>b-e&&(f=new RegExp("\\.?0{1,"+(d-(b-e))+"}$"),h=h.replace(f,"")),h}},b.options=h,b.formats=e,b.locales=f,b.locale=function(a){return a&&(h.currentLocale=a.toLowerCase()),h.currentLocale},b.localeData=function(a){if(!a)return f[h.currentLocale];if(a=a.toLowerCase(),!f[a])throw new Error("Unknown locale : "+a);return f[a]},b.reset=function(){for(var a in g)h[a]=g[a]},b.zeroFormat=function(a){h.zeroFormat="string"==typeof a?a:null},b.nullFormat=function(a){h.nullFormat="string"==typeof a?a:null},b.defaultFormat=function(a){h.defaultFormat="string"==typeof a?a:"0.0"},b.register=function(a,b,c){if(b=b.toLowerCase(),this[a+"s"][b])throw new TypeError(b+" "+a+" already registered.");return this[a+"s"][b]=c,c},b.validate=function(a,c){var d,e,f,g,h,i,j,k;if("string"!=typeof a&&(a+="",console.warn&&console.warn("Numeral.js: Value is not string. It has been co-erced to: ",a)),a=a.trim(),a.match(/^\d+$/))return!0;if(""===a)return!1;try{j=b.localeData(c)}catch(l){j=b.localeData(b.locale())}return f=j.currency.symbol,h=j.abbreviations,d=j.delimiters.decimal,e="."===j.delimiters.thousands?"\\.":j.delimiters.thousands,k=a.match(/^[^\d]+/),null!==k&&(a=a.substr(1),k[0]!==f)?!1:(k=a.match(/[^\d]+$/),null!==k&&(a=a.slice(0,-1),k[0]!==h.thousand&&k[0]!==h.million&&k[0]!==h.billion&&k[0]!==h.trillion)?!1:(i=new RegExp(e+"{2}"),a.match(/[^\d.,]/g)?!1:(g=a.split(d),g.length>2?!1:g.length<2?!!g[0].match(/^\d+.*\d$/)&&!g[0].match(i):1===g[0].length?!!g[0].match(/^\d+$/)&&!g[0].match(i)&&!!g[1].match(/^\d+$/):!!g[0].match(/^\d+.*\d$/)&&!g[0].match(i)&&!!g[1].match(/^\d+$/))))},b.fn=a.prototype={clone:function(){return b(this)},format:function(a,c){var d,f,g,i=this._value,j=a||h.defaultFormat;if(c=c||Math.round,0===i&&null!==h.zeroFormat)f=h.zeroFormat;else if(null===i&&null!==h.nullFormat)f=h.nullFormat;else{for(d in e)if(j.match(e[d].regexps.format)){g=e[d].format;break}g=g||b._.numberToFormat,f=g(i,j,c)}return f},value:function(){return this._value},input:function(){return this._input},set:function(a){return this._value=Number(a),this},add:function(a){function b(a,b,c,e){return a+Math.round(d*b)}var d=c.correctionFactor.call(null,this._value,a);return this._value=c.reduce([this._value,a],b,0)/d,this},subtract:function(a){function b(a,b,c,e){return a-Math.round(d*b)}var d=c.correctionFactor.call(null,this._value,a);return this._value=c.reduce([a],b,Math.round(this._value*d))/d,this},multiply:function(a){function b(a,b,d,e){var f=c.correctionFactor(a,b);return Math.round(a*f)*Math.round(b*f)/Math.round(f*f)}return this._value=c.reduce([this._value,a],b,1),this},divide:function(a){function b(a,b,d,e){var f=c.correctionFactor(a,b);return Math.round(a*f)/Math.round(b*f)}return this._value=c.reduce([this._value,a],b),this},difference:function(a){return Math.abs(b(this._value).subtract(a).value())}},b.register("locale","en",{delimiters:{thousands:",",decimal:"."},abbreviations:{thousand:"k",million:"m",billion:"b",trillion:"t"},ordinal:function(a){var b=a%10;return 1===~~(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th"},currency:{symbol:"$"}}),function(){b.register("format","bps",{regexps:{format:/(BPS)/,unformat:/(BPS)/},format:function(a,c,d){var e,f=b._.includes(c," BPS")?" ":"";return a=1e4*a,c=c.replace(/\s?BPS/,""),e=b._.numberToFormat(a,c,d),b._.includes(e,")")?(e=e.split(""),e.splice(-1,0,f+"BPS"),e=e.join("")):e=e+f+"BPS",e},unformat:function(a){return+(1e-4*b._.stringToNumber(a)).toFixed(15)}})}(),function(){var a={base:1e3,suffixes:["B","KB","MB","GB","TB","PB","EB","ZB","YB"]},c={base:1024,suffixes:["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"]},d=a.suffixes.concat(c.suffixes.filter(function(b){return a.suffixes.indexOf(b)<0})),e=d.join("|");e="("+e.replace("B","B(?!PS)")+")",b.register("format","bytes",{regexps:{format:/([0\s]i?b)/,unformat:new RegExp(e)},format:function(d,e,f){var g,h,i,j,k=b._.includes(e,"ib")?c:a,l=b._.includes(e," b")||b._.includes(e," ib")?" ":"";for(e=e.replace(/\s?i?b/,""),h=0;h<=k.suffixes.length;h++)if(i=Math.pow(k.base,h),j=Math.pow(k.base,h+1),null===d||0===d||d>=i&&j>d){l+=k.suffixes[h],i>0&&(d/=i);break}return g=b._.numberToFormat(d,e,f),g+l},unformat:function(d){var e,f,g=b._.stringToNumber(d);if(g){for(e=a.suffixes.length-1;e>=0;e--){if(b._.includes(d,a.suffixes[e])){f=Math.pow(a.base,e);break}if(b._.includes(d,c.suffixes[e])){f=Math.pow(c.base,e);break}}g*=f||1}return g}})}(),function(){b.register("format","currency",{regexps:{format:/(\$)/},format:function(a,c,d){var e,f,g,h=b.locales[b.options.currentLocale],i={before:c.match(/^([\+|\-|\(|\s|\$]*)/)[0],after:c.match(/([\+|\-|\)|\s|\$]*)$/)[0]};for(c=c.replace(/\s?\$\s?/,""),e=b._.numberToFormat(a,c,d),a>=0?(i.before=i.before.replace(/[\-\(]/,""),i.after=i.after.replace(/[\-\)]/,"")):0>a&&!b._.includes(i.before,"-")&&!b._.includes(i.before,"(")&&(i.before="-"+i.before),g=0;g<i.before.length;g++)switch(f=i.before[g]){case"$":e=b._.insert(e,h.currency.symbol,g);break;case" ":e=b._.insert(e," ",g+h.currency.symbol.length-1)}for(g=i.after.length-1;g>=0;g--)switch(f=i.after[g]){case"$":e=g===i.after.length-1?e+h.currency.symbol:b._.insert(e,h.currency.symbol,-(i.after.length-(1+g)));break;case" ":e=g===i.after.length-1?e+" ":b._.insert(e," ",-(i.after.length-(1+g)+h.currency.symbol.length-1))}return e}})}(),function(){b.register("format","exponential",{regexps:{format:/(e\+|e-)/,unformat:/(e\+|e-)/},format:function(a,c,d){var e,f="number"!=typeof a||b._.isNaN(a)?"0e+0":a.toExponential(),g=f.split("e");return c=c.replace(/e[\+|\-]{1}0/,""),e=b._.numberToFormat(Number(g[0]),c,d),e+"e"+g[1]},unformat:function(a){function c(a,c,d,e){var f=b._.correctionFactor(a,c),g=a*f*(c*f)/(f*f);return g}var d=b._.includes(a,"e+")?a.split("e+"):a.split("e-"),e=Number(d[0]),f=Number(d[1]);return f=b._.includes(a,"e-")?f*=-1:f,b._.reduce([e,Math.pow(10,f)],c,1)}})}(),function(){b.register("format","ordinal",{regexps:{format:/(o)/},format:function(a,c,d){var e,f=b.locales[b.options.currentLocale],g=b._.includes(c," o")?" ":"";return c=c.replace(/\s?o/,""),g+=f.ordinal(a),e=b._.numberToFormat(a,c,d),e+g}})}(),function(){b.register("format","percentage",{regexps:{format:/(%)/,unformat:/(%)/},format:function(a,c,d){var e,f=b._.includes(c," %")?" ":"";return b.options.scalePercentBy100&&(a=100*a),c=c.replace(/\s?\%/,""),e=b._.numberToFormat(a,c,d),b._.includes(e,")")?(e=e.split(""),e.splice(-1,0,f+"%"),e=e.join("")):e=e+f+"%",e},unformat:function(a){var c=b._.stringToNumber(a);return b.options.scalePercentBy100?.01*c:c}})}(),function(){b.register("format","time",{regexps:{format:/(:)/,unformat:/(:)/},format:function(a,b,c){var d=Math.floor(a/60/60),e=Math.floor((a-60*d*60)/60),f=Math.round(a-60*d*60-60*e);return d+":"+(10>e?"0"+e:e)+":"+(10>f?"0"+f:f)},unformat:function(a){var b=a.split(":"),c=0;return 3===b.length?(c+=60*Number(b[0])*60,c+=60*Number(b[1]),c+=Number(b[2])):2===b.length&&(c+=60*Number(b[0]),c+=Number(b[1])),Number(c)}})}(),b});
17890
17891/**
17892 * @license
17893 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
17894 * Build: `lodash modern -o ./dist/lodash.js`
17895 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
17896 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
17897 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
17898 * Available under MIT license <http://lodash.com/license>
17899 */
17900
17901;(function() {
17902
17903  /** Used as a safe reference for `undefined` in pre ES5 environments */
17904  var undefined;
17905
17906  /** Used to pool arrays and objects used internally */
17907  var arrayPool = [],
17908      objectPool = [];
17909
17910  /** Used to generate unique IDs */
17911  var idCounter = 0;
17912
17913  /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
17914  var keyPrefix = +new Date + '';
17915
17916  /** Used as the size when optimizations are enabled for large arrays */
17917  var largeArraySize = 75;
17918
17919  /** Used as the max size of the `arrayPool` and `objectPool` */
17920  var maxPoolSize = 40;
17921
17922  /** Used to detect and test whitespace */
17923  var whitespace = (
17924    // whitespace
17925    ' \t\x0B\f\xA0\ufeff' +
17926
17927    // line terminators
17928    '\n\r\u2028\u2029' +
17929
17930    // unicode category "Zs" space separators
17931    '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
17932  );
17933
17934  /** Used to match empty string literals in compiled template source */
17935  var reEmptyStringLeading = /\b__p \+= '';/g,
17936      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
17937      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
17938
17939  /**
17940   * Used to match ES6 template delimiters
17941   * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals
17942   */
17943  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
17944
17945  /** Used to match regexp flags from their coerced string values */
17946  var reFlags = /\w*$/;
17947
17948  /** Used to detected named functions */
17949  var reFuncName = /^\s*function[ \n\r\t]+\w/;
17950
17951  /** Used to match "interpolate" template delimiters */
17952  var reInterpolate = /<%=([\s\S]+?)%>/g;
17953
17954  /** Used to match leading whitespace and zeros to be removed */
17955  var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');
17956
17957  /** Used to ensure capturing order of template delimiters */
17958  var reNoMatch = /($^)/;
17959
17960  /** Used to detect functions containing a `this` reference */
17961  var reThis = /\bthis\b/;
17962
17963  /** Used to match unescaped characters in compiled string literals */
17964  var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
17965
17966  /** Used to assign default `context` object properties */
17967  var contextProps = [
17968    'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object',
17969    'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
17970    'parseInt', 'setTimeout'
17971  ];
17972
17973  /** Used to make template sourceURLs easier to identify */
17974  var templateCounter = 0;
17975
17976  /** `Object#toString` result shortcuts */
17977  var argsClass = '[object Arguments]',
17978      arrayClass = '[object Array]',
17979      boolClass = '[object Boolean]',
17980      dateClass = '[object Date]',
17981      funcClass = '[object Function]',
17982      numberClass = '[object Number]',
17983      objectClass = '[object Object]',
17984      regexpClass = '[object RegExp]',
17985      stringClass = '[object String]';
17986
17987  /** Used to identify object classifications that `_.clone` supports */
17988  var cloneableClasses = {};
17989  cloneableClasses[funcClass] = false;
17990  cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
17991  cloneableClasses[boolClass] = cloneableClasses[dateClass] =
17992  cloneableClasses[numberClass] = cloneableClasses[objectClass] =
17993  cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
17994
17995  /** Used as an internal `_.debounce` options object */
17996  var debounceOptions = {
17997    'leading': false,
17998    'maxWait': 0,
17999    'trailing': false
18000  };
18001
18002  /** Used as the property descriptor for `__bindData__` */
18003  var descriptor = {
18004    'configurable': false,
18005    'enumerable': false,
18006    'value': null,
18007    'writable': false
18008  };
18009
18010  /** Used to determine if values are of the language type Object */
18011  var objectTypes = {
18012    'boolean': false,
18013    'function': true,
18014    'object': true,
18015    'number': false,
18016    'string': false,
18017    'undefined': false
18018  };
18019
18020  /** Used to escape characters for inclusion in compiled string literals */
18021  var stringEscapes = {
18022    '\\': '\\',
18023    "'": "'",
18024    '\n': 'n',
18025    '\r': 'r',
18026    '\t': 't',
18027    '\u2028': 'u2028',
18028    '\u2029': 'u2029'
18029  };
18030
18031  /** Used as a reference to the global object */
18032  var root = (objectTypes[typeof window] && window) || this;
18033
18034  /** Detect free variable `exports` */
18035  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
18036
18037  /** Detect free variable `module` */
18038  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
18039
18040  /** Detect the popular CommonJS extension `module.exports` */
18041  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
18042
18043  /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
18044  var freeGlobal = objectTypes[typeof global] && global;
18045  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
18046    root = freeGlobal;
18047  }
18048
18049  /*--------------------------------------------------------------------------*/
18050
18051  /**
18052   * The base implementation of `_.indexOf` without support for binary searches
18053   * or `fromIndex` constraints.
18054   *
18055   * @private
18056   * @param {Array} array The array to search.
18057   * @param {*} value The value to search for.
18058   * @param {number} [fromIndex=0] The index to search from.
18059   * @returns {number} Returns the index of the matched value or `-1`.
18060   */
18061  function baseIndexOf(array, value, fromIndex) {
18062    var index = (fromIndex || 0) - 1,
18063        length = array ? array.length : 0;
18064
18065    while (++index < length) {
18066      if (array[index] === value) {
18067        return index;
18068      }
18069    }
18070    return -1;
18071  }
18072
18073  /**
18074   * An implementation of `_.contains` for cache objects that mimics the return
18075   * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.
18076   *
18077   * @private
18078   * @param {Object} cache The cache object to inspect.
18079   * @param {*} value The value to search for.
18080   * @returns {number} Returns `0` if `value` is found, else `-1`.
18081   */
18082  function cacheIndexOf(cache, value) {
18083    var type = typeof value;
18084    cache = cache.cache;
18085
18086    if (type == 'boolean' || value == null) {
18087      return cache[value] ? 0 : -1;
18088    }
18089    if (type != 'number' && type != 'string') {
18090      type = 'object';
18091    }
18092    var key = type == 'number' ? value : keyPrefix + value;
18093    cache = (cache = cache[type]) && cache[key];
18094
18095    return type == 'object'
18096      ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)
18097      : (cache ? 0 : -1);
18098  }
18099
18100  /**
18101   * Adds a given value to the corresponding cache object.
18102   *
18103   * @private
18104   * @param {*} value The value to add to the cache.
18105   */
18106  function cachePush(value) {
18107    var cache = this.cache,
18108        type = typeof value;
18109
18110    if (type == 'boolean' || value == null) {
18111      cache[value] = true;
18112    } else {
18113      if (type != 'number' && type != 'string') {
18114        type = 'object';
18115      }
18116      var key = type == 'number' ? value : keyPrefix + value,
18117          typeCache = cache[type] || (cache[type] = {});
18118
18119      if (type == 'object') {
18120        (typeCache[key] || (typeCache[key] = [])).push(value);
18121      } else {
18122        typeCache[key] = true;
18123      }
18124    }
18125  }
18126
18127  /**
18128   * Used by `_.max` and `_.min` as the default callback when a given
18129   * collection is a string value.
18130   *
18131   * @private
18132   * @param {string} value The character to inspect.
18133   * @returns {number} Returns the code unit of given character.
18134   */
18135  function charAtCallback(value) {
18136    return value.charCodeAt(0);
18137  }
18138
18139  /**
18140   * Used by `sortBy` to compare transformed `collection` elements, stable sorting
18141   * them in ascending order.
18142   *
18143   * @private
18144   * @param {Object} a The object to compare to `b`.
18145   * @param {Object} b The object to compare to `a`.
18146   * @returns {number} Returns the sort order indicator of `1` or `-1`.
18147   */
18148  function compareAscending(a, b) {
18149    var ac = a.criteria,
18150        bc = b.criteria,
18151        index = -1,
18152        length = ac.length;
18153
18154    while (++index < length) {
18155      var value = ac[index],
18156          other = bc[index];
18157
18158      if (value !== other) {
18159        if (value > other || typeof value == 'undefined') {
18160          return 1;
18161        }
18162        if (value < other || typeof other == 'undefined') {
18163          return -1;
18164        }
18165      }
18166    }
18167    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
18168    // that causes it, under certain circumstances, to return the same value for
18169    // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
18170    //
18171    // This also ensures a stable sort in V8 and other engines.
18172    // See http://code.google.com/p/v8/issues/detail?id=90
18173    return a.index - b.index;
18174  }
18175
18176  /**
18177   * Creates a cache object to optimize linear searches of large arrays.
18178   *
18179   * @private
18180   * @param {Array} [array=[]] The array to search.
18181   * @returns {null|Object} Returns the cache object or `null` if caching should not be used.
18182   */
18183  function createCache(array) {
18184    var index = -1,
18185        length = array.length,
18186        first = array[0],
18187        mid = array[(length / 2) | 0],
18188        last = array[length - 1];
18189
18190    if (first && typeof first == 'object' &&
18191        mid && typeof mid == 'object' && last && typeof last == 'object') {
18192      return false;
18193    }
18194    var cache = getObject();
18195    cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;
18196
18197    var result = getObject();
18198    result.array = array;
18199    result.cache = cache;
18200    result.push = cachePush;
18201
18202    while (++index < length) {
18203      result.push(array[index]);
18204    }
18205    return result;
18206  }
18207
18208  /**
18209   * Used by `template` to escape characters for inclusion in compiled
18210   * string literals.
18211   *
18212   * @private
18213   * @param {string} match The matched character to escape.
18214   * @returns {string} Returns the escaped character.
18215   */
18216  function escapeStringChar(match) {
18217    return '\\' + stringEscapes[match];
18218  }
18219
18220  /**
18221   * Gets an array from the array pool or creates a new one if the pool is empty.
18222   *
18223   * @private
18224   * @returns {Array} The array from the pool.
18225   */
18226  function getArray() {
18227    return arrayPool.pop() || [];
18228  }
18229
18230  /**
18231   * Gets an object from the object pool or creates a new one if the pool is empty.
18232   *
18233   * @private
18234   * @returns {Object} The object from the pool.
18235   */
18236  function getObject() {
18237    return objectPool.pop() || {
18238      'array': null,
18239      'cache': null,
18240      'criteria': null,
18241      'false': false,
18242      'index': 0,
18243      'null': false,
18244      'number': null,
18245      'object': null,
18246      'push': null,
18247      'string': null,
18248      'true': false,
18249      'undefined': false,
18250      'value': null
18251    };
18252  }
18253
18254  /**
18255   * Releases the given array back to the array pool.
18256   *
18257   * @private
18258   * @param {Array} [array] The array to release.
18259   */
18260  function releaseArray(array) {
18261    array.length = 0;
18262    if (arrayPool.length < maxPoolSize) {
18263      arrayPool.push(array);
18264    }
18265  }
18266
18267  /**
18268   * Releases the given object back to the object pool.
18269   *
18270   * @private
18271   * @param {Object} [object] The object to release.
18272   */
18273  function releaseObject(object) {
18274    var cache = object.cache;
18275    if (cache) {
18276      releaseObject(cache);
18277    }
18278    object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
18279    if (objectPool.length < maxPoolSize) {
18280      objectPool.push(object);
18281    }
18282  }
18283
18284  /**
18285   * Slices the `collection` from the `start` index up to, but not including,
18286   * the `end` index.
18287   *
18288   * Note: This function is used instead of `Array#slice` to support node lists
18289   * in IE < 9 and to ensure dense arrays are returned.
18290   *
18291   * @private
18292   * @param {Array|Object|string} collection The collection to slice.
18293   * @param {number} start The start index.
18294   * @param {number} end The end index.
18295   * @returns {Array} Returns the new array.
18296   */
18297  function slice(array, start, end) {
18298    start || (start = 0);
18299    if (typeof end == 'undefined') {
18300      end = array ? array.length : 0;
18301    }
18302    var index = -1,
18303        length = end - start || 0,
18304        result = Array(length < 0 ? 0 : length);
18305
18306    while (++index < length) {
18307      result[index] = array[start + index];
18308    }
18309    return result;
18310  }
18311
18312  /*--------------------------------------------------------------------------*/
18313
18314  /**
18315   * Create a new `lodash` function using the given context object.
18316   *
18317   * @static
18318   * @memberOf _
18319   * @category Utilities
18320   * @param {Object} [context=root] The context object.
18321   * @returns {Function} Returns the `lodash` function.
18322   */
18323  function runInContext(context) {
18324    // Avoid issues with some ES3 environments that attempt to use values, named
18325    // after built-in constructors like `Object`, for the creation of literals.
18326    // ES5 clears this up by stating that literals must use built-in constructors.
18327    // See http://es5.github.io/#x11.1.5.
18328    context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
18329
18330    /** Native constructor references */
18331    var Array = context.Array,
18332        Boolean = context.Boolean,
18333        Date = context.Date,
18334        Function = context.Function,
18335        Math = context.Math,
18336        Number = context.Number,
18337        Object = context.Object,
18338        RegExp = context.RegExp,
18339        String = context.String,
18340        TypeError = context.TypeError;
18341
18342    /**
18343     * Used for `Array` method references.
18344     *
18345     * Normally `Array.prototype` would suffice, however, using an array literal
18346     * avoids issues in Narwhal.
18347     */
18348    var arrayRef = [];
18349
18350    /** Used for native method references */
18351    var objectProto = Object.prototype;
18352
18353    /** Used to restore the original `_` reference in `noConflict` */
18354    var oldDash = context._;
18355
18356    /** Used to resolve the internal [[Class]] of values */
18357    var toString = objectProto.toString;
18358
18359    /** Used to detect if a method is native */
18360    var reNative = RegExp('^' +
18361      String(toString)
18362        .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
18363        .replace(/toString| for [^\]]+/g, '.*?') + '$'
18364    );
18365
18366    /** Native method shortcuts */
18367    var ceil = Math.ceil,
18368        clearTimeout = context.clearTimeout,
18369        floor = Math.floor,
18370        fnToString = Function.prototype.toString,
18371        getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
18372        hasOwnProperty = objectProto.hasOwnProperty,
18373        push = arrayRef.push,
18374        setTimeout = context.setTimeout,
18375        splice = arrayRef.splice,
18376        unshift = arrayRef.unshift;
18377
18378    /** Used to set meta data on functions */
18379    var defineProperty = (function() {
18380      // IE 8 only accepts DOM elements
18381      try {
18382        var o = {},
18383            func = isNative(func = Object.defineProperty) && func,
18384            result = func(o, o, o) && func;
18385      } catch(e) { }
18386      return result;
18387    }());
18388
18389    /* Native method shortcuts for methods with the same name as other `lodash` methods */
18390    var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
18391        nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
18392        nativeIsFinite = context.isFinite,
18393        nativeIsNaN = context.isNaN,
18394        nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
18395        nativeMax = Math.max,
18396        nativeMin = Math.min,
18397        nativeParseInt = context.parseInt,
18398        nativeRandom = Math.random;
18399
18400    /** Used to lookup a built-in constructor by [[Class]] */
18401    var ctorByClass = {};
18402    ctorByClass[arrayClass] = Array;
18403    ctorByClass[boolClass] = Boolean;
18404    ctorByClass[dateClass] = Date;
18405    ctorByClass[funcClass] = Function;
18406    ctorByClass[objectClass] = Object;
18407    ctorByClass[numberClass] = Number;
18408    ctorByClass[regexpClass] = RegExp;
18409    ctorByClass[stringClass] = String;
18410
18411    /*--------------------------------------------------------------------------*/
18412
18413    /**
18414     * Creates a `lodash` object which wraps the given value to enable intuitive
18415     * method chaining.
18416     *
18417     * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
18418     * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
18419     * and `unshift`
18420     *
18421     * Chaining is supported in custom builds as long as the `value` method is
18422     * implicitly or explicitly included in the build.
18423     *
18424     * The chainable wrapper functions are:
18425     * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
18426     * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
18427     * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
18428     * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
18429     * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
18430     * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
18431     * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
18432     * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
18433     * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
18434     * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
18435     * and `zip`
18436     *
18437     * The non-chainable wrapper functions are:
18438     * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
18439     * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
18440     * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
18441     * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
18442     * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
18443     * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
18444     * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
18445     * `template`, `unescape`, `uniqueId`, and `value`
18446     *
18447     * The wrapper functions `first` and `last` return wrapped values when `n` is
18448     * provided, otherwise they return unwrapped values.
18449     *
18450     * Explicit chaining can be enabled by using the `_.chain` method.
18451     *
18452     * @name _
18453     * @constructor
18454     * @category Chaining
18455     * @param {*} value The value to wrap in a `lodash` instance.
18456     * @returns {Object} Returns a `lodash` instance.
18457     * @example
18458     *
18459     * var wrapped = _([1, 2, 3]);
18460     *
18461     * // returns an unwrapped value
18462     * wrapped.reduce(function(sum, num) {
18463     *   return sum + num;
18464     * });
18465     * // => 6
18466     *
18467     * // returns a wrapped value
18468     * var squares = wrapped.map(function(num) {
18469     *   return num * num;
18470     * });
18471     *
18472     * _.isArray(squares);
18473     * // => false
18474     *
18475     * _.isArray(squares.value());
18476     * // => true
18477     */
18478    function lodash(value) {
18479      // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
18480      return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
18481       ? value
18482       : new lodashWrapper(value);
18483    }
18484
18485    /**
18486     * A fast path for creating `lodash` wrapper objects.
18487     *
18488     * @private
18489     * @param {*} value The value to wrap in a `lodash` instance.
18490     * @param {boolean} chainAll A flag to enable chaining for all methods
18491     * @returns {Object} Returns a `lodash` instance.
18492     */
18493    function lodashWrapper(value, chainAll) {
18494      this.__chain__ = !!chainAll;
18495      this.__wrapped__ = value;
18496    }
18497    // ensure `new lodashWrapper` is an instance of `lodash`
18498    lodashWrapper.prototype = lodash.prototype;
18499
18500    /**
18501     * An object used to flag environments features.
18502     *
18503     * @static
18504     * @memberOf _
18505     * @type Object
18506     */
18507    var support = lodash.support = {};
18508
18509    /**
18510     * Detect if functions can be decompiled by `Function#toString`
18511     * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
18512     *
18513     * @memberOf _.support
18514     * @type boolean
18515     */
18516    support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);
18517
18518    /**
18519     * Detect if `Function#name` is supported (all but IE).
18520     *
18521     * @memberOf _.support
18522     * @type boolean
18523     */
18524    support.funcNames = typeof Function.name == 'string';
18525
18526    /**
18527     * By default, the template delimiters used by Lo-Dash are similar to those in
18528     * embedded Ruby (ERB). Change the following template settings to use alternative
18529     * delimiters.
18530     *
18531     * @static
18532     * @memberOf _
18533     * @type Object
18534     */
18535    lodash.templateSettings = {
18536
18537      /**
18538       * Used to detect `data` property values to be HTML-escaped.
18539       *
18540       * @memberOf _.templateSettings
18541       * @type RegExp
18542       */
18543      'escape': /<%-([\s\S]+?)%>/g,
18544
18545      /**
18546       * Used to detect code to be evaluated.
18547       *
18548       * @memberOf _.templateSettings
18549       * @type RegExp
18550       */
18551      'evaluate': /<%([\s\S]+?)%>/g,
18552
18553      /**
18554       * Used to detect `data` property values to inject.
18555       *
18556       * @memberOf _.templateSettings
18557       * @type RegExp
18558       */
18559      'interpolate': reInterpolate,
18560
18561      /**
18562       * Used to reference the data object in the template text.
18563       *
18564       * @memberOf _.templateSettings
18565       * @type string
18566       */
18567      'variable': '',
18568
18569      /**
18570       * Used to import variables into the compiled template.
18571       *
18572       * @memberOf _.templateSettings
18573       * @type Object
18574       */
18575      'imports': {
18576
18577        /**
18578         * A reference to the `lodash` function.
18579         *
18580         * @memberOf _.templateSettings.imports
18581         * @type Function
18582         */
18583        '_': lodash
18584      }
18585    };
18586
18587    /*--------------------------------------------------------------------------*/
18588
18589    /**
18590     * The base implementation of `_.bind` that creates the bound function and
18591     * sets its meta data.
18592     *
18593     * @private
18594     * @param {Array} bindData The bind data array.
18595     * @returns {Function} Returns the new bound function.
18596     */
18597    function baseBind(bindData) {
18598      var func = bindData[0],
18599          partialArgs = bindData[2],
18600          thisArg = bindData[4];
18601
18602      function bound() {
18603        // `Function#bind` spec
18604        // http://es5.github.io/#x15.3.4.5
18605        if (partialArgs) {
18606          // avoid `arguments` object deoptimizations by using `slice` instead
18607          // of `Array.prototype.slice.call` and not assigning `arguments` to a
18608          // variable as a ternary expression
18609          var args = slice(partialArgs);
18610          push.apply(args, arguments);
18611        }
18612        // mimic the constructor's `return` behavior
18613        // http://es5.github.io/#x13.2.2
18614        if (this instanceof bound) {
18615          // ensure `new bound` is an instance of `func`
18616          var thisBinding = baseCreate(func.prototype),
18617              result = func.apply(thisBinding, args || arguments);
18618          return isObject(result) ? result : thisBinding;
18619        }
18620        return func.apply(thisArg, args || arguments);
18621      }
18622      setBindData(bound, bindData);
18623      return bound;
18624    }
18625
18626    /**
18627     * The base implementation of `_.clone` without argument juggling or support
18628     * for `thisArg` binding.
18629     *
18630     * @private
18631     * @param {*} value The value to clone.
18632     * @param {boolean} [isDeep=false] Specify a deep clone.
18633     * @param {Function} [callback] The function to customize cloning values.
18634     * @param {Array} [stackA=[]] Tracks traversed source objects.
18635     * @param {Array} [stackB=[]] Associates clones with source counterparts.
18636     * @returns {*} Returns the cloned value.
18637     */
18638    function baseClone(value, isDeep, callback, stackA, stackB) {
18639      if (callback) {
18640        var result = callback(value);
18641        if (typeof result != 'undefined') {
18642          return result;
18643        }
18644      }
18645      // inspect [[Class]]
18646      var isObj = isObject(value);
18647      if (isObj) {
18648        var className = toString.call(value);
18649        if (!cloneableClasses[className]) {
18650          return value;
18651        }
18652        var ctor = ctorByClass[className];
18653        switch (className) {
18654          case boolClass:
18655          case dateClass:
18656            return new ctor(+value);
18657
18658          case numberClass:
18659          case stringClass:
18660            return new ctor(value);
18661
18662          case regexpClass:
18663            result = ctor(value.source, reFlags.exec(value));
18664            result.lastIndex = value.lastIndex;
18665            return result;
18666        }
18667      } else {
18668        return value;
18669      }
18670      var isArr = isArray(value);
18671      if (isDeep) {
18672        // check for circular references and return corresponding clone
18673        var initedStack = !stackA;
18674        stackA || (stackA = getArray());
18675        stackB || (stackB = getArray());
18676
18677        var length = stackA.length;
18678        while (length--) {
18679          if (stackA[length] == value) {
18680            return stackB[length];
18681          }
18682        }
18683        result = isArr ? ctor(value.length) : {};
18684      }
18685      else {
18686        result = isArr ? slice(value) : assign({}, value);
18687      }
18688      // add array properties assigned by `RegExp#exec`
18689      if (isArr) {
18690        if (hasOwnProperty.call(value, 'index')) {
18691          result.index = value.index;
18692        }
18693        if (hasOwnProperty.call(value, 'input')) {
18694          result.input = value.input;
18695        }
18696      }
18697      // exit for shallow clone
18698      if (!isDeep) {
18699        return result;
18700      }
18701      // add the source value to the stack of traversed objects
18702      // and associate it with its clone
18703      stackA.push(value);
18704      stackB.push(result);
18705
18706      // recursively populate clone (susceptible to call stack limits)
18707      (isArr ? forEach : forOwn)(value, function(objValue, key) {
18708        result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);
18709      });
18710
18711      if (initedStack) {
18712        releaseArray(stackA);
18713        releaseArray(stackB);
18714      }
18715      return result;
18716    }
18717
18718    /**
18719     * The base implementation of `_.create` without support for assigning
18720     * properties to the created object.
18721     *
18722     * @private
18723     * @param {Object} prototype The object to inherit from.
18724     * @returns {Object} Returns the new object.
18725     */
18726    function baseCreate(prototype, properties) {
18727      return isObject(prototype) ? nativeCreate(prototype) : {};
18728    }
18729    // fallback for browsers without `Object.create`
18730    if (!nativeCreate) {
18731      baseCreate = (function() {
18732        function Object() {}
18733        return function(prototype) {
18734          if (isObject(prototype)) {
18735            Object.prototype = prototype;
18736            var result = new Object;
18737            Object.prototype = null;
18738          }
18739          return result || context.Object();
18740        };
18741      }());
18742    }
18743
18744    /**
18745     * The base implementation of `_.createCallback` without support for creating
18746     * "_.pluck" or "_.where" style callbacks.
18747     *
18748     * @private
18749     * @param {*} [func=identity] The value to convert to a callback.
18750     * @param {*} [thisArg] The `this` binding of the created callback.
18751     * @param {number} [argCount] The number of arguments the callback accepts.
18752     * @returns {Function} Returns a callback function.
18753     */
18754    function baseCreateCallback(func, thisArg, argCount) {
18755      if (typeof func != 'function') {
18756        return identity;
18757      }
18758      // exit early for no `thisArg` or already bound by `Function#bind`
18759      if (typeof thisArg == 'undefined' || !('prototype' in func)) {
18760        return func;
18761      }
18762      var bindData = func.__bindData__;
18763      if (typeof bindData == 'undefined') {
18764        if (support.funcNames) {
18765          bindData = !func.name;
18766        }
18767        bindData = bindData || !support.funcDecomp;
18768        if (!bindData) {
18769          var source = fnToString.call(func);
18770          if (!support.funcNames) {
18771            bindData = !reFuncName.test(source);
18772          }
18773          if (!bindData) {
18774            // checks if `func` references the `this` keyword and stores the result
18775            bindData = reThis.test(source);
18776            setBindData(func, bindData);
18777          }
18778        }
18779      }
18780      // exit early if there are no `this` references or `func` is bound
18781      if (bindData === false || (bindData !== true && bindData[1] & 1)) {
18782        return func;
18783      }
18784      switch (argCount) {
18785        case 1: return function(value) {
18786          return func.call(thisArg, value);
18787        };
18788        case 2: return function(a, b) {
18789          return func.call(thisArg, a, b);
18790        };
18791        case 3: return function(value, index, collection) {
18792          return func.call(thisArg, value, index, collection);
18793        };
18794        case 4: return function(accumulator, value, index, collection) {
18795          return func.call(thisArg, accumulator, value, index, collection);
18796        };
18797      }
18798      return bind(func, thisArg);
18799    }
18800
18801    /**
18802     * The base implementation of `createWrapper` that creates the wrapper and
18803     * sets its meta data.
18804     *
18805     * @private
18806     * @param {Array} bindData The bind data array.
18807     * @returns {Function} Returns the new function.
18808     */
18809    function baseCreateWrapper(bindData) {
18810      var func = bindData[0],
18811          bitmask = bindData[1],
18812          partialArgs = bindData[2],
18813          partialRightArgs = bindData[3],
18814          thisArg = bindData[4],
18815          arity = bindData[5];
18816
18817      var isBind = bitmask & 1,
18818          isBindKey = bitmask & 2,
18819          isCurry = bitmask & 4,
18820          isCurryBound = bitmask & 8,
18821          key = func;
18822
18823      function bound() {
18824        var thisBinding = isBind ? thisArg : this;
18825        if (partialArgs) {
18826          var args = slice(partialArgs);
18827          push.apply(args, arguments);
18828        }
18829        if (partialRightArgs || isCurry) {
18830          args || (args = slice(arguments));
18831          if (partialRightArgs) {
18832            push.apply(args, partialRightArgs);
18833          }
18834          if (isCurry && args.length < arity) {
18835            bitmask |= 16 & ~32;
18836            return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
18837          }
18838        }
18839        args || (args = arguments);
18840        if (isBindKey) {
18841          func = thisBinding[key];
18842        }
18843        if (this instanceof bound) {
18844          thisBinding = baseCreate(func.prototype);
18845          var result = func.apply(thisBinding, args);
18846          return isObject(result) ? result : thisBinding;
18847        }
18848        return func.apply(thisBinding, args);
18849      }
18850      setBindData(bound, bindData);
18851      return bound;
18852    }
18853
18854    /**
18855     * The base implementation of `_.difference` that accepts a single array
18856     * of values to exclude.
18857     *
18858     * @private
18859     * @param {Array} array The array to process.
18860     * @param {Array} [values] The array of values to exclude.
18861     * @returns {Array} Returns a new array of filtered values.
18862     */
18863    function baseDifference(array, values) {
18864      var index = -1,
18865          indexOf = getIndexOf(),
18866          length = array ? array.length : 0,
18867          isLarge = length >= largeArraySize && indexOf === baseIndexOf,
18868          result = [];
18869
18870      if (isLarge) {
18871        var cache = createCache(values);
18872        if (cache) {
18873          indexOf = cacheIndexOf;
18874          values = cache;
18875        } else {
18876          isLarge = false;
18877        }
18878      }
18879      while (++index < length) {
18880        var value = array[index];
18881        if (indexOf(values, value) < 0) {
18882          result.push(value);
18883        }
18884      }
18885      if (isLarge) {
18886        releaseObject(values);
18887      }
18888      return result;
18889    }
18890
18891    /**
18892     * The base implementation of `_.flatten` without support for callback
18893     * shorthands or `thisArg` binding.
18894     *
18895     * @private
18896     * @param {Array} array The array to flatten.
18897     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
18898     * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
18899     * @param {number} [fromIndex=0] The index to start from.
18900     * @returns {Array} Returns a new flattened array.
18901     */
18902    function baseFlatten(array, isShallow, isStrict, fromIndex) {
18903      var index = (fromIndex || 0) - 1,
18904          length = array ? array.length : 0,
18905          result = [];
18906
18907      while (++index < length) {
18908        var value = array[index];
18909
18910        if (value && typeof value == 'object' && typeof value.length == 'number'
18911            && (isArray(value) || isArguments(value))) {
18912          // recursively flatten arrays (susceptible to call stack limits)
18913          if (!isShallow) {
18914            value = baseFlatten(value, isShallow, isStrict);
18915          }
18916          var valIndex = -1,
18917              valLength = value.length,
18918              resIndex = result.length;
18919
18920          result.length += valLength;
18921          while (++valIndex < valLength) {
18922            result[resIndex++] = value[valIndex];
18923          }
18924        } else if (!isStrict) {
18925          result.push(value);
18926        }
18927      }
18928      return result;
18929    }
18930
18931    /**
18932     * The base implementation of `_.isEqual`, without support for `thisArg` binding,
18933     * that allows partial "_.where" style comparisons.
18934     *
18935     * @private
18936     * @param {*} a The value to compare.
18937     * @param {*} b The other value to compare.
18938     * @param {Function} [callback] The function to customize comparing values.
18939     * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
18940     * @param {Array} [stackA=[]] Tracks traversed `a` objects.
18941     * @param {Array} [stackB=[]] Tracks traversed `b` objects.
18942     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
18943     */
18944    function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
18945      // used to indicate that when comparing objects, `a` has at least the properties of `b`
18946      if (callback) {
18947        var result = callback(a, b);
18948        if (typeof result != 'undefined') {
18949          return !!result;
18950        }
18951      }
18952      // exit early for identical values
18953      if (a === b) {
18954        // treat `+0` vs. `-0` as not equal
18955        return a !== 0 || (1 / a == 1 / b);
18956      }
18957      var type = typeof a,
18958          otherType = typeof b;
18959
18960      // exit early for unlike primitive values
18961      if (a === a &&
18962          !(a && objectTypes[type]) &&
18963          !(b && objectTypes[otherType])) {
18964        return false;
18965      }
18966      // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
18967      // http://es5.github.io/#x15.3.4.4
18968      if (a == null || b == null) {
18969        return a === b;
18970      }
18971      // compare [[Class]] names
18972      var className = toString.call(a),
18973          otherClass = toString.call(b);
18974
18975      if (className == argsClass) {
18976        className = objectClass;
18977      }
18978      if (otherClass == argsClass) {
18979        otherClass = objectClass;
18980      }
18981      if (className != otherClass) {
18982        return false;
18983      }
18984      switch (className) {
18985        case boolClass:
18986        case dateClass:
18987          // coerce dates and booleans to numbers, dates to milliseconds and booleans
18988          // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
18989          return +a == +b;
18990
18991        case numberClass:
18992          // treat `NaN` vs. `NaN` as equal
18993          return (a != +a)
18994            ? b != +b
18995            // but treat `+0` vs. `-0` as not equal
18996            : (a == 0 ? (1 / a == 1 / b) : a == +b);
18997
18998        case regexpClass:
18999        case stringClass:
19000          // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
19001          // treat string primitives and their corresponding object instances as equal
19002          return a == String(b);
19003      }
19004      var isArr = className == arrayClass;
19005      if (!isArr) {
19006        // unwrap any `lodash` wrapped values
19007        var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
19008            bWrapped = hasOwnProperty.call(b, '__wrapped__');
19009
19010        if (aWrapped || bWrapped) {
19011          return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
19012        }
19013        // exit for functions and DOM nodes
19014        if (className != objectClass) {
19015          return false;
19016        }
19017        // in older versions of Opera, `arguments` objects have `Array` constructors
19018        var ctorA = a.constructor,
19019            ctorB = b.constructor;
19020
19021        // non `Object` object instances with different constructors are not equal
19022        if (ctorA != ctorB &&
19023              !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
19024              ('constructor' in a && 'constructor' in b)
19025            ) {
19026          return false;
19027        }
19028      }
19029      // assume cyclic structures are equal
19030      // the algorithm for detecting cyclic structures is adapted from ES 5.1
19031      // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
19032      var initedStack = !stackA;
19033      stackA || (stackA = getArray());
19034      stackB || (stackB = getArray());
19035
19036      var length = stackA.length;
19037      while (length--) {
19038        if (stackA[length] == a) {
19039          return stackB[length] == b;
19040        }
19041      }
19042      var size = 0;
19043      result = true;
19044
19045      // add `a` and `b` to the stack of traversed objects
19046      stackA.push(a);
19047      stackB.push(b);
19048
19049      // recursively compare objects and arrays (susceptible to call stack limits)
19050      if (isArr) {
19051        // compare lengths to determine if a deep comparison is necessary
19052        length = a.length;
19053        size = b.length;
19054        result = size == length;
19055
19056        if (result || isWhere) {
19057          // deep compare the contents, ignoring non-numeric properties
19058          while (size--) {
19059            var index = length,
19060                value = b[size];
19061
19062            if (isWhere) {
19063              while (index--) {
19064                if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
19065                  break;
19066                }
19067              }
19068            } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
19069              break;
19070            }
19071          }
19072        }
19073      }
19074      else {
19075        // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
19076        // which, in this case, is more costly
19077        forIn(b, function(value, key, b) {
19078          if (hasOwnProperty.call(b, key)) {
19079            // count the number of properties.
19080            size++;
19081            // deep compare each property value.
19082            return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
19083          }
19084        });
19085
19086        if (result && !isWhere) {
19087          // ensure both objects have the same number of properties
19088          forIn(a, function(value, key, a) {
19089            if (hasOwnProperty.call(a, key)) {
19090              // `size` will be `-1` if `a` has more properties than `b`
19091              return (result = --size > -1);
19092            }
19093          });
19094        }
19095      }
19096      stackA.pop();
19097      stackB.pop();
19098
19099      if (initedStack) {
19100        releaseArray(stackA);
19101        releaseArray(stackB);
19102      }
19103      return result;
19104    }
19105
19106    /**
19107     * The base implementation of `_.merge` without argument juggling or support
19108     * for `thisArg` binding.
19109     *
19110     * @private
19111     * @param {Object} object The destination object.
19112     * @param {Object} source The source object.
19113     * @param {Function} [callback] The function to customize merging properties.
19114     * @param {Array} [stackA=[]] Tracks traversed source objects.
19115     * @param {Array} [stackB=[]] Associates values with source counterparts.
19116     */
19117    function baseMerge(object, source, callback, stackA, stackB) {
19118      (isArray(source) ? forEach : forOwn)(source, function(source, key) {
19119        var found,
19120            isArr,
19121            result = source,
19122            value = object[key];
19123
19124        if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
19125          // avoid merging previously merged cyclic sources
19126          var stackLength = stackA.length;
19127          while (stackLength--) {
19128            if ((found = stackA[stackLength] == source)) {
19129              value = stackB[stackLength];
19130              break;
19131            }
19132          }
19133          if (!found) {
19134            var isShallow;
19135            if (callback) {
19136              result = callback(value, source);
19137              if ((isShallow = typeof result != 'undefined')) {
19138                value = result;
19139              }
19140            }
19141            if (!isShallow) {
19142              value = isArr
19143                ? (isArray(value) ? value : [])
19144                : (isPlainObject(value) ? value : {});
19145            }
19146            // add `source` and associated `value` to the stack of traversed objects
19147            stackA.push(source);
19148            stackB.push(value);
19149
19150            // recursively merge objects and arrays (susceptible to call stack limits)
19151            if (!isShallow) {
19152              baseMerge(value, source, callback, stackA, stackB);
19153            }
19154          }
19155        }
19156        else {
19157          if (callback) {
19158            result = callback(value, source);
19159            if (typeof result == 'undefined') {
19160              result = source;
19161            }
19162          }
19163          if (typeof result != 'undefined') {
19164            value = result;
19165          }
19166        }
19167        object[key] = value;
19168      });
19169    }
19170
19171    /**
19172     * The base implementation of `_.random` without argument juggling or support
19173     * for returning floating-point numbers.
19174     *
19175     * @private
19176     * @param {number} min The minimum possible value.
19177     * @param {number} max The maximum possible value.
19178     * @returns {number} Returns a random number.
19179     */
19180    function baseRandom(min, max) {
19181      return min + floor(nativeRandom() * (max - min + 1));
19182    }
19183
19184    /**
19185     * The base implementation of `_.uniq` without support for callback shorthands
19186     * or `thisArg` binding.
19187     *
19188     * @private
19189     * @param {Array} array The array to process.
19190     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
19191     * @param {Function} [callback] The function called per iteration.
19192     * @returns {Array} Returns a duplicate-value-free array.
19193     */
19194    function baseUniq(array, isSorted, callback) {
19195      var index = -1,
19196          indexOf = getIndexOf(),
19197          length = array ? array.length : 0,
19198          result = [];
19199
19200      var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,
19201          seen = (callback || isLarge) ? getArray() : result;
19202
19203      if (isLarge) {
19204        var cache = createCache(seen);
19205        indexOf = cacheIndexOf;
19206        seen = cache;
19207      }
19208      while (++index < length) {
19209        var value = array[index],
19210            computed = callback ? callback(value, index, array) : value;
19211
19212        if (isSorted
19213              ? !index || seen[seen.length - 1] !== computed
19214              : indexOf(seen, computed) < 0
19215            ) {
19216          if (callback || isLarge) {
19217            seen.push(computed);
19218          }
19219          result.push(value);
19220        }
19221      }
19222      if (isLarge) {
19223        releaseArray(seen.array);
19224        releaseObject(seen);
19225      } else if (callback) {
19226        releaseArray(seen);
19227      }
19228      return result;
19229    }
19230
19231    /**
19232     * Creates a function that aggregates a collection, creating an object c
19232omposed
19233     * of keys generated from the results of running each element of the collection
19234     * through a callback. The given `setter` function sets the keys and values
19235     * of the composed object.
19236     *
19237     * @private
19238     * @param {Function} setter The setter function.
19239     * @returns {Function} Returns the new aggregator function.
19240     */
19241    function createAggregator(setter) {
19242      return function(collection, callback, thisArg) {
19243        var result = {};
19244        callback = lodash.createCallback(callback, thisArg, 3);
19245
19246        var index = -1,
19247            length = collection ? collection.length : 0;
19248
19249        if (typeof length == 'number') {
19250          while (++index < length) {
19251            var value = collection[index];
19252            setter(result, value, callback(value, index, collection), collection);
19253          }
19254        } else {
19255          forOwn(collection, function(value, key, collection) {
19256            setter(result, value, callback(value, key, collection), collection);
19257          });
19258        }
19259        return result;
19260      };
19261    }
19262
19263    /**
19264     * Creates a function that, when called, either curries or invokes `func`
19265     * with an optional `this` binding and partially applied arguments.
19266     *
19267     * @private
19268     * @param {Function|string} func The function or method name to reference.
19269     * @param {number} bitmask The bitmask of method flags to compose.
19270     *  The bitmask may be composed of the following flags:
19271     *  1 - `_.bind`
19272     *  2 - `_.bindKey`
19273     *  4 - `_.curry`
19274     *  8 - `_.curry` (bound)
19275     *  16 - `_.partial`
19276     *  32 - `_.partialRight`
19277     * @param {Array} [partialArgs] An array of arguments to prepend to those
19278     *  provided to the new function.
19279     * @param {Array} [partialRightArgs] An array of arguments to append to those
19280     *  provided to the new function.
19281     * @param {*} [thisArg] The `this` binding of `func`.
19282     * @param {number} [arity] The arity of `func`.
19283     * @returns {Function} Returns the new function.
19284     */
19285    function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
19286      var isBind = bitmask & 1,
19287          isBindKey = bitmask & 2,
19288          isCurry = bitmask & 4,
19289          isCurryBound = bitmask & 8,
19290          isPartial = bitmask & 16,
19291          isPartialRight = bitmask & 32;
19292
19293      if (!isBindKey && !isFunction(func)) {
19294        throw new TypeError;
19295      }
19296      if (isPartial && !partialArgs.length) {
19297        bitmask &= ~16;
19298        isPartial = partialArgs = false;
19299      }
19300      if (isPartialRight && !partialRightArgs.length) {
19301        bitmask &= ~32;
19302        isPartialRight = partialRightArgs = false;
19303      }
19304      var bindData = func && func.__bindData__;
19305      if (bindData && bindData !== true) {
19306        // clone `bindData`
19307        bindData = slice(bindData);
19308        if (bindData[2]) {
19309          bindData[2] = slice(bindData[2]);
19310        }
19311        if (bindData[3]) {
19312          bindData[3] = slice(bindData[3]);
19313        }
19314        // set `thisBinding` is not previously bound
19315        if (isBind && !(bindData[1] & 1)) {
19316          bindData[4] = thisArg;
19317        }
19318        // set if previously bound but not currently (subsequent curried functions)
19319        if (!isBind && bindData[1] & 1) {
19320          bitmask |= 8;
19321        }
19322        // set curried arity if not yet set
19323        if (isCurry && !(bindData[1] & 4)) {
19324          bindData[5] = arity;
19325        }
19326        // append partial left arguments
19327        if (isPartial) {
19328          push.apply(bindData[2] || (bindData[2] = []), partialArgs);
19329        }
19330        // append partial right arguments
19331        if (isPartialRight) {
19332          unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
19333        }
19334        // merge flags
19335        bindData[1] |= bitmask;
19336        return createWrapper.apply(null, bindData);
19337      }
19338      // fast path for `_.bind`
19339      var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
19340      return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
19341    }
19342
19343    /**
19344     * Used by `escape` to convert characters to HTML entities.
19345     *
19346     * @private
19347     * @param {string} match The matched character to escape.
19348     * @returns {string} Returns the escaped character.
19349     */
19350    function escapeHtmlChar(match) {
19351      return htmlEscapes[match];
19352    }
19353
19354    /**
19355     * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
19356     * customized, this method returns the custom method, otherwise it returns
19357     * the `baseIndexOf` function.
19358     *
19359     * @private
19360     * @returns {Function} Returns the "indexOf" function.
19361     */
19362    function getIndexOf() {
19363      var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
19364      return result;
19365    }
19366
19367    /**
19368     * Checks if `value` is a native function.
19369     *
19370     * @private
19371     * @param {*} value The value to check.
19372     * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
19373     */
19374    function isNative(value) {
19375      return typeof value == 'function' && reNative.test(value);
19376    }
19377
19378    /**
19379     * Sets `this` binding data on a given function.
19380     *
19381     * @private
19382     * @param {Function} func The function to set data on.
19383     * @param {Array} value The data array to set.
19384     */
19385    var setBindData = !defineProperty ? noop : function(func, value) {
19386      descriptor.value = value;
19387      defineProperty(func, '__bindData__', descriptor);
19388    };
19389
19390    /**
19391     * A fallback implementation of `isPlainObject` which checks if a given value
19392     * is an object created by the `Object` constructor, assuming objects created
19393     * by the `Object` constructor have no inherited enumerable properties and that
19394     * there are no `Object.prototype` extensions.
19395     *
19396     * @private
19397     * @param {*} value The value to check.
19398     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
19399     */
19400    function shimIsPlainObject(value) {
19401      var ctor,
19402          result;
19403
19404      // avoid non Object objects, `arguments` objects, and DOM elements
19405      if (!(value && toString.call(value) == objectClass) ||
19406          (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) {
19407        return false;
19408      }
19409      // In most environments an object's own properties are iterated before
19410      // its inherited properties. If the last iterated property is an object's
19411      // own property then there are no inherited enumerable properties.
19412      forIn(value, function(value, key) {
19413        result = key;
19414      });
19415      return typeof result == 'undefined' || hasOwnProperty.call(value, result);
19416    }
19417
19418    /**
19419     * Used by `unescape` to convert HTML entities to characters.
19420     *
19421     * @private
19422     * @param {string} match The matched character to unescape.
19423     * @returns {string} Returns the unescaped character.
19424     */
19425    function unescapeHtmlChar(match) {
19426      return htmlUnescapes[match];
19427    }
19428
19429    /*--------------------------------------------------------------------------*/
19430
19431    /**
19432     * Checks if `value` is an `arguments` object.
19433     *
19434     * @static
19435     * @memberOf _
19436     * @category Objects
19437     * @param {*} value The value to check.
19438     * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
19439     * @example
19440     *
19441     * (function() { return _.isArguments(arguments); })(1, 2, 3);
19442     * // => true
19443     *
19444     * _.isArguments([1, 2, 3]);
19445     * // => false
19446     */
19447    function isArguments(value) {
19448      return value && typeof value == 'object' && typeof value.length == 'number' &&
19449        toString.call(value) == argsClass || false;
19450    }
19451
19452    /**
19453     * Checks if `value` is an array.
19454     *
19455     * @static
19456     * @memberOf _
19457     * @type Function
19458     * @category Objects
19459     * @param {*} value The value to check.
19460     * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
19461     * @example
19462     *
19463     * (function() { return _.isArray(arguments); })();
19464     * // => false
19465     *
19466     * _.isArray([1, 2, 3]);
19467     * // => true
19468     */
19469    var isArray = nativeIsArray || function(value) {
19470      return value && typeof value == 'object' && typeof value.length == 'number' &&
19471        toString.call(value) == arrayClass || false;
19472    };
19473
19474    /**
19475     * A fallback implementation of `Object.keys` which produces an array of the
19476     * given object's own enumerable property names.
19477     *
19478     * @private
19479     * @type Function
19480     * @param {Object} object The object to inspect.
19481     * @returns {Array} Returns an array of property names.
19482     */
19483    var shimKeys = function(object) {
19484      var index, iterable = object, result = [];
19485      if (!iterable) return result;
19486      if (!(objectTypes[typeof object])) return result;
19487        for (index in iterable) {
19488          if (hasOwnProperty.call(iterable, index)) {
19489            result.push(index);
19490          }
19491        }
19492      return result
19493    };
19494
19495    /**
19496     * Creates an array composed of the own enumerable property names of an object.
19497     *
19498     * @static
19499     * @memberOf _
19500     * @category Objects
19501     * @param {Object} object The object to inspect.
19502     * @returns {Array} Returns an array of property names.
19503     * @example
19504     *
19505     * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
19506     * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
19507     */
19508    var keys = !nativeKeys ? shimKeys : function(object) {
19509      if (!isObject(object)) {
19510        return [];
19511      }
19512      return nativeKeys(object);
19513    };
19514
19515    /**
19516     * Used to convert characters to HTML entities:
19517     *
19518     * Though the `>` character is escaped for symmetry, characters like `>` and `/`
19519     * don't require escaping in HTML and have no special meaning unless they're part
19520     * of a tag or an unquoted attribute value.
19521     * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
19522     */
19523    var htmlEscapes = {
19524      '&': '&amp;',
19525      '<': '&lt;',
19526      '>': '&gt;',
19527      '"': '&quot;',
19528      "'": '&#39;'
19529    };
19530
19531    /** Used to convert HTML entities to characters */
19532    var htmlUnescapes = invert(htmlEscapes);
19533
19534    /** Used to match HTML entities and HTML characters */
19535    var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
19536        reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
19537
19538    /*--------------------------------------------------------------------------*/
19539
19540    /**
19541     * Assigns own enumerable properties of source object(s) to the destination
19542     * object. Subsequent sources will overwrite property assignments of previous
19543     * sources. If a callback is provided it will be executed to produce the
19544     * assigned values. The callback is bound to `thisArg` and invoked with two
19545     * arguments; (objectValue, sourceValue).
19546     *
19547     * @static
19548     * @memberOf _
19549     * @type Function
19550     * @alias extend
19551     * @category Objects
19552     * @param {Object} object The destination object.
19553     * @param {...Object} [source] The source objects.
19554     * @param {Function} [callback] The function to customize assigning values.
19555     * @param {*} [thisArg] The `this` binding of `callback`.
19556     * @returns {Object} Returns the destination object.
19557     * @example
19558     *
19559     * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
19560     * // => { 'name': 'fred', 'employer': 'slate' }
19561     *
19562     * var defaults = _.partialRight(_.assign, function(a, b) {
19563     *   return typeof a == 'undefined' ? b : a;
19564     * });
19565     *
19566     * var object = { 'name': 'barney' };
19567     * defaults(object, { 'name': 'fred', 'employer': 'slate' });
19568     * // => { 'name': 'barney', 'employer': 'slate' }
19569     */
19570    var assign = function(object, source, guard) {
19571      var index, iterable = object, result = iterable;
19572      if (!iterable) return result;
19573      var args = arguments,
19574          argsIndex = 0,
19575          argsLength = typeof guard == 'number' ? 2 : args.length;
19576      if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {
19577        var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);
19578      } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {
19579        callback = args[--argsLength];
19580      }
19581      while (++argsIndex < argsLength) {
19582        iterable = args[argsIndex];
19583        if (iterable && objectTypes[typeof iterable]) {
19584        var ownIndex = -1,
19585            ownProps = objectTypes[typeof iterable] && keys(iterable),
19586            length = ownProps ? ownProps.length : 0;
19587
19588        while (++ownIndex < length) {
19589          index = ownProps[ownIndex];
19590          result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
19591        }
19592        }
19593      }
19594      return result
19595    };
19596
19597    /**
19598     * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
19599     * be cloned, otherwise they will be assigned by reference. If a callback
19600     * is provided it will be executed to produce the cloned values. If the
19601     * callback returns `undefined` cloning will be handled by the method instead.
19602     * The callback is bound to `thisArg` and invoked with one argument; (value).
19603     *
19604     * @static
19605     * @memberOf _
19606     * @category Objects
19607     * @param {*} value The value to clone.
19608     * @param {boolean} [isDeep=false] Specify a deep clone.
19609     * @param {Function} [callback] The function to customize cloning values.
19610     * @param {*} [thisArg] The `this` binding of `callback`.
19611     * @returns {*} Returns the cloned value.
19612     * @example
19613     *
19614     * var characters = [
19615     *   { 'name': 'barney', 'age': 36 },
19616     *   { 'name': 'fred',   'age': 40 }
19617     * ];
19618     *
19619     * var shallow = _.clone(characters);
19620     * shallow[0] === characters[0];
19621     * // => true
19622     *
19623     * var deep = _.clone(characters, true);
19624     * deep[0] === characters[0];
19625     * // => false
19626     *
19627     * _.mixin({
19628     *   'clone': _.partialRight(_.clone, function(value) {
19629     *     return _.isElement(value) ? value.cloneNode(false) : undefined;
19630     *   })
19631     * });
19632     *
19633     * var clone = _.clone(document.body);
19634     * clone.childNodes.length;
19635     * // => 0
19636     */
19637    function clone(value, isDeep, callback, thisArg) {
19638      // allows working with "Collections" methods without using their `index`
19639      // and `collection` arguments for `isDeep` and `callback`
19640      if (typeof isDeep != 'boolean' && isDeep != null) {
19641        thisArg = callback;
19642        callback = isDeep;
19643        isDeep = false;
19644      }
19645      return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
19646    }
19647
19648    /**
19649     * Creates a deep clone of `value`. If a callback is provided it will be
19650     * executed to produce the cloned values. If the callback returns `undefined`
19651     * cloning will be handled by the method instead. The callback is bound to
19652     * `thisArg` and invoked with one argument; (value).
19653     *
19654     * Note: This method is loosely based on the structured clone algorithm. Functions
19655     * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and
19656     * objects created by constructors other than `Object` are cloned to plain `Object` objects.
19657     * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.
19658     *
19659     * @static
19660     * @memberOf _
19661     * @category Objects
19662     * @param {*} value The value to deep clone.
19663     * @param {Function} [callback] The function to customize cloning values.
19664     * @param {*} [thisArg] The `this` binding of `callback`.
19665     * @returns {*} Returns the deep cloned value.
19666     * @example
19667     *
19668     * var characters = [
19669     *   { 'name': 'barney', 'age': 36 },
19670     *   { 'name': 'fred',   'age': 40 }
19671     * ];
19672     *
19673     * var deep = _.cloneDeep(characters);
19674     * deep[0] === characters[0];
19675     * // => false
19676     *
19677     * var view = {
19678     *   'label': 'docs',
19679     *   'node': element
19680     * };
19681     *
19682     * var clone = _.cloneDeep(view, function(value) {
19683     *   return _.isElement(value) ? value.cloneNode(true) : undefined;
19684     * });
19685     *
19686     * clone.node == view.node;
19687     * // => false
19688     */
19689    function cloneDeep(value, callback, thisArg) {
19690      return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
19691    }
19692
19693    /**
19694     * Creates an object that inherits from the given `prototype` object. If a
19695     * `properties` object is provided its own enumerable properties are assigned
19696     * to the created object.
19697     *
19698     * @static
19699     * @memberOf _
19700     * @category Objects
19701     * @param {Object} prototype The object to inherit from.
19702     * @param {Object} [properties] The properties to assign to the object.
19703     * @returns {Object} Returns the new object.
19704     * @example
19705     *
19706     * function Shape() {
19707     *   this.x = 0;
19708     *   this.y = 0;
19709     * }
19710     *
19711     * function Circle() {
19712     *   Shape.call(this);
19713     * }
19714     *
19715     * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });
19716     *
19717     * var circle = new Circle;
19718     * circle instanceof Circle;
19719     * // => true
19720     *
19721     * circle instanceof Shape;
19722     * // => true
19723     */
19724    function create(prototype, properties) {
19725      var result = baseCreate(prototype);
19726      return properties ? assign(result, properties) : result;
19727    }
19728
19729    /**
19730     * Assigns own enumerable properties of source object(s) to the destination
19731     * object for all destination properties that resolve to `undefined`. Once a
19732     * property is set, additional defaults of the same property will be ignored.
19733     *
19734     * @static
19735     * @memberOf _
19736     * @type Function
19737     * @category Objects
19738     * @param {Object} object The destination object.
19739     * @param {...Object} [source] The source objects.
19740     * @param- {Object} [guard] Allows working with `_.reduce` without using its
19741     *  `key` and `object` arguments as sources.
19742     * @returns {Object} Returns the destination object.
19743     * @example
19744     *
19745     * var object = { 'name': 'barney' };
19746     * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
19747     * // => { 'name': 'barney', 'employer': 'slate' }
19748     */
19749    var defaults = function(object, source, guard) {
19750      var index, iterable = object, result = iterable;
19751      if (!iterable) return result;
19752      var args = arguments,
19753          argsIndex = 0,
19754          argsLength = typeof guard == 'number' ? 2 : args.length;
19755      while (++argsIndex < argsLength) {
19756        iterable = args[argsIndex];
19757        if (iterable && objectTypes[typeof iterable]) {
19758        var ownIndex = -1,
19759            ownProps = objectTypes[typeof iterable] && keys(iterable),
19760            length = ownProps ? ownProps.length : 0;
19761
19762        while (++ownIndex < length) {
19763          index = ownProps[ownIndex];
19764          if (typeof result[index] == 'undefined') result[index] = iterable[index];
19765        }
19766        }
19767      }
19768      return result
19769    };
19770
19771    /**
19772     * This method is like `_.findIndex` except that it returns the key of the
19773     * first element that passes the callback check, instead of the element itself.
19774     *
19775     * If a property name is provided for `callback` the created "_.pluck" style
19776     * callback will return the property value of the given element.
19777     *
19778     * If an object is provided for `callback` the created "_.where" style callback
19779     * will return `true` for elements that have the properties of the given object,
19780     * else `false`.
19781     *
19782     * @static
19783     * @memberOf _
19784     * @category Objects
19785     * @param {Object} object The object to search.
19786     * @param {Function|Object|string} [callback=identity] The function called per
19787     *  iteration. If a property name or object is provided it will be used to
19788     *  create a "_.pluck" or "_.where" style callback, respectively.
19789     * @param {*} [thisArg] The `this` binding of `callback`.
19790     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
19791     * @example
19792     *
19793     * var characters = {
19794     *   'barney': {  'age': 36, 'blocked': false },
19795     *   'fred': {    'age': 40, 'blocked': true },
19796     *   'pebbles': { 'age': 1,  'blocked': false }
19797     * };
19798     *
19799     * _.findKey(characters, function(chr) {
19800     *   return chr.age < 40;
19801     * });
19802     * // => 'barney' (property order is not guaranteed across environments)
19803     *
19804     * // using "_.where" callback shorthand
19805     * _.findKey(characters, { 'age': 1 });
19806     * // => 'pebbles'
19807     *
19808     * // using "_.pluck" callback shorthand
19809     * _.findKey(characters, 'blocked');
19810     * // => 'fred'
19811     */
19812    function findKey(object, callback, thisArg) {
19813      var result;
19814      callback = lodash.createCallback(callback, thisArg, 3);
19815      forOwn(object, function(value, key, object) {
19816        if (callback(value, key, object)) {
19817          result = key;
19818          return false;
19819        }
19820      });
19821      return result;
19822    }
19823
19824    /**
19825     * This method is like `_.findKey` except that it iterates over elements
19826     * of a `collection` in the opposite order.
19827     *
19828     * If a property name is provided for `callback` the created "_.pluck" style
19829     * callback will return the property value of the given element.
19830     *
19831     * If an object is provided for `callback` the created "_.where" style callback
19832     * will return `true` for elements that have the properties of the given object,
19833     * else `false`.
19834     *
19835     * @static
19836     * @memberOf _
19837     * @category Objects
19838     * @param {Object} object The object to search.
19839     * @param {Function|Object|string} [callback=identity] The function called per
19840     *  iteration. If a property name or object is provided it will be used to
19841     *  create a "_.pluck" or "_.where" style callback, respectively.
19842     * @param {*} [thisArg] The `this` binding of `callback`.
19843     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
19844     * @example
19845     *
19846     * var characters = {
19847     *   'barney': {  'age': 36, 'blocked': true },
19848     *   'fred': {    'age': 40, 'blocked': false },
19849     *   'pebbles': { 'age': 1,  'blocked': true }
19850     * };
19851     *
19852     * _.findLastKey(characters, function(chr) {
19853     *   return chr.age < 40;
19854     * });
19855     * // => returns `pebbles`, assuming `_.findKey` returns `barney`
19856     *
19857     * // using "_.where" callback shorthand
19858     * _.findLastKey(characters, { 'age': 40 });
19859     * // => 'fred'
19860     *
19861     * // using "_.pluck" callback shorthand
19862     * _.findLastKey(characters, 'blocked');
19863     * // => 'pebbles'
19864     */
19865    function findLastKey(object, callback, thisArg) {
19866      var result;
19867      callback = lodash.createCallback(callback, thisArg, 3);
19868      forOwnRight(object, function(value, key, object) {
19869        if (callback(value, key, object)) {
19870          result = key;
19871          return false;
19872        }
19873      });
19874      return result;
19875    }
19876
19877    /**
19878     * Iterates over own and inherited enumerable properties of an object,
19879     * executing the callback for each property. The callback is bound to `thisArg`
19880     * and invoked with three arguments; (value, key, object). Callbacks may exit
19881     * iteration early by explicitly returning `false`.
19882     *
19883     * @static
19884     * @memberOf _
19885     * @type Function
19886     * @category Objects
19887     * @param {Object} object The object to iterate over.
19888     * @param {Function} [callback=identity] The function called per iteration.
19889     * @param {*} [thisArg] The `this` binding of `callback`.
19890     * @returns {Object} Returns `object`.
19891     * @example
19892     *
19893     * function Shape() {
19894     *   this.x = 0;
19895     *   this.y = 0;
19896     * }
19897     *
19898     * Shape.prototype.move = function(x, y) {
19899     *   this.x += x;
19900     *   this.y += y;
19901     * };
19902     *
19903     * _.forIn(new Shape, function(value, key) {
19904     *   console.log(key);
19905     * });
19906     * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
19907     */
19908    var forIn = function(collection, callback, thisArg) {
19909      var index, iterable = collection, result = iterable;
19910      if (!iterable) return result;
19911      if (!objectTypes[typeof iterable]) return result;
19912      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
19913        for (index in iterable) {
19914          if (callback(iterable[index], index, collection) === false) return result;
19915        }
19916      return result
19917    };
19918
19919    /**
19920     * This method is like `_.forIn` except that it iterates over elements
19921     * of a `collection` in the opposite order.
19922     *
19923     * @static
19924     * @memberOf _
19925     * @category Objects
19926     * @param {Object} object The object to iterate over.
19927     * @param {Function} [callback=identity] The function called per iteration.
19928     * @param {*} [thisArg] The `this` binding of `callback`.
19929     * @returns {Object} Returns `object`.
19930     * @example
19931     *
19932     * function Shape() {
19933     *   this.x = 0;
19934     *   this.y = 0;
19935     * }
19936     *
19937     * Shape.prototype.move = function(x, y) {
19938     *   this.x += x;
19939     *   this.y += y;
19940     * };
19941     *
19942     * _.forInRight(new Shape, function(value, key) {
19943     *   console.log(key);
19944     * });
19945     * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'
19946     */
19947    function forInRight(object, callback, thisArg) {
19948      var pairs = [];
19949
19950      forIn(object, function(value, key) {
19951        pairs.push(key, value);
19952      });
19953
19954      var length = pairs.length;
19955      callback = baseCreateCallback(callback, thisArg, 3);
19956      while (length--) {
19957        if (callback(pairs[length--], pairs[length], object) === false) {
19958          break;
19959        }
19960      }
19961      return object;
19962    }
19963
19964    /**
19965     * Iterates over own enumerable properties of an object, executing the callback
19966     * for each property. The callback is bound to `thisArg` and invoked with three
19967     * arguments; (value, key, object). Callbacks may exit iteration early by
19968     * explicitly returning `false`.
19969     *
19970     * @static
19971     * @memberOf _
19972     * @type Function
19973     * @category Objects
19974     * @param {Object} object The object to iterate over.
19975     * @param {Function} [callback=identity] The function called per iteration.
19976     * @param {*} [thisArg] The `this` binding of `callback`.
19977     * @returns {Object} Returns `object`.
19978     * @example
19979     *
19980     * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
19981     *   console.log(key);
19982     * });
19983     * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
19984     */
19985    var forOwn = function(collection, callback, thisArg) {
19986      var index, iterable = collection, result = iterable;
19987      if (!iterable) return result;
19988      if (!objectTypes[typeof iterable]) return result;
19989      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
19990        var ownIndex = -1,
19991            ownProps = objectTypes[typeof iterable] && keys(iterable),
19992            length = ownProps ? ownProps.length : 0;
19993
19994        while (++ownIndex < length) {
19995          index = ownProps[ownIndex];
19996          if (callback(iterable[index], index, collection) === false) return result;
19997        }
19998      return result
19999    };
20000
20001    /**
20002     * This method is like `_.forOwn` except that it iterates over elements
20003     * of a `collection` in the opposite order.
20004     *
20005     * @static
20006     * @memberOf _
20007     * @category Objects
20008     * @param {Object} object The object to iterate over.
20009     * @param {Function} [callback=identity] The function called per iteration.
20010     * @param {*} [thisArg] The `this` binding of `callback`.
20011     * @returns {Object} Returns `object`.
20012     * @example
20013     *
20014     * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
20015     *   console.log(key);
20016     * });
20017     * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'
20018     */
20019    function forOwnRight(object, callback, thisArg) {
20020      var props = keys(object),
20021          length = props.length;
20022
20023      callback = baseCreateCallback(callback, thisArg, 3);
20024      while (length--) {
20025        var key = props[length];
20026        if (callback(object[key], key, object) === false) {
20027          break;
20028        }
20029      }
20030      return object;
20031    }
20032
20033    /**
20034     * Creates a sorted array of property names of all enumerable properties,
20035     * own and inherited, of `object` that have function values.
20036     *
20037     * @static
20038     * @memberOf _
20039     * @alias methods
20040     * @category Objects
20041     * @param {Object} object The object to inspect.
20042     * @returns {Array} Returns an array of property names that have function values.
20043     * @example
20044     *
20045     * _.functions(_);
20046     * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
20047     */
20048    function functions(object) {
20049      var result = [];
20050      forIn(object, function(value, key) {
20051        if (isFunction(value)) {
20052          result.push(key);
20053        }
20054      });
20055      return result.sort();
20056    }
20057
20058    /**
20059     * Checks if the specified property name exists as a direct property of `object`,
20060     * instead of an inherited property.
20061     *
20062     * @static
20063     * @memberOf _
20064     * @category Objects
20065     * @param {Object} object The object to inspect.
20066     * @param {string} key The name of the property to check.
20067     * @returns {boolean} Returns `true` if key is a direct property, else `false`.
20068     * @example
20069     *
20070     * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
20071     * // => true
20072     */
20073    function has(object, key) {
20074      return object ? hasOwnProperty.call(object, key) : false;
20075    }
20076
20077    /**
20078     * Creates an object composed of the inverted keys and values of the given object.
20079     *
20080     * @static
20081     * @memberOf _
20082     * @category Objects
20083     * @param {Object} object The object to invert.
20084     * @returns {Object} Returns the created inverted object.
20085     * @example
20086     *
20087     * _.invert({ 'first': 'fred', 'second': 'barney' });
20088     * // => { 'fred': 'first', 'barney': 'second' }
20089     */
20090    function invert(object) {
20091      var index = -1,
20092          props = keys(object),
20093          length = props.length,
20094          result = {};
20095
20096      while (++index < length) {
20097        var key = props[index];
20098        result[object[key]] = key;
20099      }
20100      return result;
20101    }
20102
20103    /**
20104     * Checks if `value` is a boolean value.
20105     *
20106     * @static
20107     * @memberOf _
20108     * @category Objects
20109     * @param {*} value The value to check.
20110     * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
20111     * @example
20112     *
20113     * _.isBoolean(null);
20114     * // => false
20115     */
20116    function isBoolean(value) {
20117      return value === true || value === false ||
20118        value && typeof value == 'object' && toString.call(value) == boolClass || false;
20119    }
20120
20121    /**
20122     * Checks if `value` is a date.
20123     *
20124     * @static
20125     * @memberOf _
20126     * @category Objects
20127     * @param {*} value The value to check.
20128     * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
20129     * @example
20130     *
20131     * _.isDate(new Date);
20132     * // => true
20133     */
20134    function isDate(value) {
20135      return value && typeof value == 'object' && toString.call(value) == dateClass || false;
20136    }
20137
20138    /**
20139     * Checks if `value` is a DOM element.
20140     *
20141     * @static
20142     * @memberOf _
20143     * @category Objects
20144     * @param {*} value The value to check.
20145     * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
20146     * @example
20147     *
20148     * _.isElement(document.body);
20149     * // => true
20150     */
20151    function isElement(value) {
20152      return value && value.nodeType === 1 || false;
20153    }
20154
20155    /**
20156     * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
20157     * length of `0` and objects with no own enumerable properties are considered
20158     * "empty".
20159     *
20160     * @static
20161     * @memberOf _
20162     * @category Objects
20163     * @param {Array|Object|string} value The value to inspect.
20164     * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
20165     * @example
20166     *
20167     * _.isEmpty([1, 2, 3]);
20168     * // => false
20169     *
20170     * _.isEmpty({});
20171     * // => true
20172     *
20173     * _.isEmpty('');
20174     * // => true
20175     */
20176    function isEmpty(value) {
20177      var result = true;
20178      if (!value) {
20179        return result;
20180      }
20181      var className = toString.call(value),
20182          length = value.length;
20183
20184      if ((className == arrayClass || className == stringClass || className == argsClass ) ||
20185          (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
20186        return !length;
20187      }
20188      forOwn(value, function() {
20189        return (result = false);
20190      });
20191      return result;
20192    }
20193
20194    /**
20195     * Performs a deep comparison between two values to determine if they are
20196     * equivalent to each other. If a callback is provided it will be executed
20197     * to compare values. If the callback returns `undefined` comparisons will
20198     * be handled by the method instead. The callback is bound to `thisArg` and
20199     * invoked with two arguments; (a, b).
20200     *
20201     * @static
20202     * @memberOf _
20203     * @category Objects
20204     * @param {*} a The value to compare.
20205     * @param {*} b The other value to compare.
20206     * @param {Function} [callback] The function to customize comparing values.
20207     * @param {*} [thisArg] The `this` binding of `callback`.
20208     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
20209     * @example
20210     *
20211     * var object = { 'name': 'fred' };
20212     * var copy = { 'name': 'fred' };
20213     *
20214     * object == copy;
20215     * // => false
20216     *
20217     * _.isEqual(object, copy);
20218     * // => true
20219     *
20220     * var words = ['hello', 'goodbye'];
20221     * var otherWords = ['hi', 'goodbye'];
20222     *
20223     * _.isEqual(words, otherWords, function(a, b) {
20224     *   var reGreet = /^(?:hello|hi)$/i,
20225     *       aGreet = _.isString(a) && reGreet.test(a),
20226     *       bGreet = _.isString(b) && reGreet.test(b);
20227     *
20228     *   return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
20229     * });
20230     * // => true
20231     */
20232    function isEqual(a, b, callback, thisArg) {
20233      return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));
20234    }
20235
20236    /**
20237     * Checks if `value` is, or can be coerced to, a finite number.
20238     *
20239     * Note: This is not the same as native `isFinite` which will return true for
20240     * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
20241     *
20242     * @static
20243     * @memberOf _
20244     * @category Objects
20245     * @param {*} value The value to check.
20246     * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
20247     * @example
20248     *
20249     * _.isFinite(-101);
20250     * // => true
20251     *
20252     * _.isFinite('10');
20253     * // => true
20254     *
20255     * _.isFinite(true);
20256     * // => false
20257     *
20258     * _.isFinite('');
20259     * // => false
20260     *
20261     * _.isFinite(Infinity);
20262     * // => false
20263     */
20264    function isFinite(value) {
20265      return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
20266    }
20267
20268    /**
20269     * Checks if `value` is a function.
20270     *
20271     * @static
20272     * @memberOf _
20273     * @category Objects
20274     * @param {*} value The value to check.
20275     * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
20276     * @example
20277     *
20278     * _.isFunction(_);
20279     * // => true
20280     */
20281    function isFunction(value) {
20282      return typeof value == 'function';
20283    }
20284
20285    /**
20286     * Checks if `value` is the language type of Object.
20287     * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
20288     *
20289     * @static
20290     * @memberOf _
20291     * @category Objects
20292     * @param {*} value The value to check.
20293     * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
20294     * @example
20295     *
20296     * _.isObject({});
20297     * // => true
20298     *
20299     * _.isObject([1, 2, 3]);
20300     * // => true
20301     *
20302     * _.isObject(1);
20303     * // => false
20304     */
20305    function isObject(value) {
20306      // check if the value is the ECMAScript language type of Object
20307      // http://es5.github.io/#x8
20308      // and avoid a V8 bug
20309      // http://code.google.com/p/v8/issues/detail?id=2291
20310      return !!(value && objectTypes[typeof value]);
20311    }
20312
20313    /**
20314     * Checks if `value` is `NaN`.
20315     *
20316     * Note: This is not the same as native `isNaN` which will return `true` for
20317     * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
20318     *
20319     * @static
20320     * @memberOf _
20321     * @category Objects
20322     * @param {*} value The value to check.
20323     * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
20324     * @example
20325     *
20326     * _.isNaN(NaN);
20327     * // => true
20328     *
20329     * _.isNaN(new Number(NaN));
20330     * // => true
20331     *
20332     * isNaN(undefined);
20333     * // => true
20334     *
20335     * _.isNaN(undefined);
20336     * // => false
20337     */
20338    function isNaN(value) {
20339      // `NaN` as a primitive is the only value that is not equal to itself
20340      // (perform the [[Class]] check first to avoid errors with some host objects in IE)
20341      return isNumber(value) && value != +value;
20342    }
20343
20344    /**
20345     * Checks if `value` is `null`.
20346     *
20347     * @static
20348     * @memberOf _
20349     * @category Objects
20350     * @param {*} value The value to check.
20351     * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
20352     * @example
20353     *
20354     * _.isNull(null);
20355     * // => true
20356     *
20357     * _.isNull(undefined);
20358     * // => false
20359     */
20360    function isNull(value) {
20361      return value === null;
20362    }
20363
20364    /**
20365     * Checks if `value` is a number.
20366     *
20367     * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
20368     *
20369     * @static
20370     * @memberOf _
20371     * @category Objects
20372     * @param {*} value The value to check.
20373     * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
20374     * @example
20375     *
20376     * _.isNumber(8.4 * 5);
20377     * // => true
20378     */
20379    function isNumber(value) {
20380      return typeof value == 'number' ||
20381        value && typeof value == 'object' && toString.call(value) == numberClass || false;
20382    }
20383
20384    /**
20385     * Checks if `value` is an object created by the `Object` constructor.
20386     *
20387     * @static
20388     * @memberOf _
20389     * @category Objects
20390     * @param {*} value The value to check.
20391     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
20392     * @example
20393     *
20394     * function Shape() {
20395     *   this.x = 0;
20396     *   this.y = 0;
20397     * }
20398     *
20399     * _.isPlainObject(new Shape);
20400     * // => false
20401     *
20402     * _.isPlainObject([1, 2, 3]);
20403     * // => false
20404     *
20405     * _.isPlainObject({ 'x': 0, 'y': 0 });
20406     * // => true
20407     */
20408    var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
20409      if (!(value && toString.call(value) == objectClass)) {
20410        return false;
20411      }
20412      var valueOf = value.valueOf,
20413          objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);
20414
20415      return objProto
20416        ? (value == objProto || getPrototypeOf(value) == objProto)
20417        : shimIsPlainObject(value);
20418    };
20419
20420    /**
20421     * Checks if `value` is a regular expression.
20422     *
20423     * @static
20424     * @memberOf _
20425     * @category Objects
20426     * @param {*} value The value to check.
20427     * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
20428     * @example
20429     *
20430     * _.isRegExp(/fred/);
20431     * // => true
20432     */
20433    function isRegExp(value) {
20434      return value && typeof value == 'object' && toString.call(value) == regexpClass || false;
20435    }
20436
20437    /**
20438     * Checks if `value` is a string.
20439     *
20440     * @static
20441     * @memberOf _
20442     * @category Objects
20443     * @param {*} value The value to check.
20444     * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
20445     * @example
20446     *
20447     * _.isString('fred');
20448     * // => true
20449     */
20450    function isString(value) {
20451      return typeof value == 'string' ||
20452        value && typeof value == 'object' && toString.call(value) == stringClass || false;
20453    }
20454
20455    /**
20456     * Checks if `value` is `undefined`.
20457     *
20458     * @static
20459     * @memberOf _
20460     * @category Objects
20461     * @param {*} value The value to check.
20462     * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
20463     * @example
20464     *
20465     * _.isUndefined(void 0);
20466     * // => true
20467     */
20468    function isUndefined(value) {
20469      return typeof value == 'undefined';
20470    }
20471
20472    /**
20473     * Creates an object with the same keys as `object` and values generated by
20474     * running each own enumerable property of `object` through the callback.
20475     * The callback is bound to `thisArg` and invoked with three arguments;
20476     * (value, key, object).
20477     *
20478     * If a property name is provided for `callback` the created "_.pluck" style
20479     * callback will return the property value of the given element.
20480     *
20481     * If an object is provided for `callback` the created "_.where" style callback
20482     * will return `true` for elements that have the properties of the given object,
20483     * else `false`.
20484     *
20485     * @static
20486     * @memberOf _
20487     * @category Objects
20488     * @param {Object} object The object to iterate over.
20489     * @param {Function|Object|string} [callback=identity] The function called
20490     *  per iteration. If a property name or object is provided it will be used
20491     *  to create a "_.pluck" or "_.where" style callback, respectively.
20492     * @param {*} [thisArg] The `this` binding of `callback`.
20493     * @returns {Array} Returns a new object with values of the results of each `callback` execution.
20494     * @example
20495     *
20496     * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
20497     * // => { 'a': 3, 'b': 6, 'c': 9 }
20498     *
20499     * var characters = {
20500     *   'fred': { 'name': 'fred', 'age': 40 },
20501     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
20502     * };
20503     *
20504     * // using "_.pluck" callback shorthand
20505     * _.mapValues(characters, 'age');
20506     * // => { 'fred': 40, 'pebbles': 1 }
20507     */
20508    function mapValues(object, callback, thisArg) {
20509      var result = {};
20510      callback = lodash.createCallback(callback, thisArg, 3);
20511
20512      forOwn(object, function(value, key, object) {
20513        result[key] = callback(value, key, object);
20514      });
20515      return result;
20516    }
20517
20518    /**
20519     * Recursively merges own enumerable properties of the source object(s), that
20520     * don't resolve to `undefined` into the destination object. Subsequent sources
20521     * will overwrite property assignments of previous sources. If a callback is
20522     * provided it will be executed to produce the merged values of the destination
20523     * and source properties. If the callback returns `undefined` merging will
20524     * be handled by the method instead. The callback is bound to `thisArg` and
20525     * invoked with two arguments; (objectValue, sourceValue).
20526     *
20527     * @static
20528     * @memberOf _
20529     * @category Objects
20530     * @param {Object} object The destination object.
20531     * @param {...Object} [source] The source objects.
20532     * @param {Function} [callback] The function to customize merging properties.
20533     * @param {*} [thisArg] The `this` binding of `callback`.
20534     * @returns {Object} Returns the destination object.
20535     * @example
20536     *
20537     * var names = {
20538     *   'characters': [
20539     *     { 'name': 'barney' },
20540     *     { 'name': 'fred' }
20541     *   ]
20542     * };
20543     *
20544     * var ages = {
20545     *   'characters': [
20546     *     { 'age': 36 },
20547     *     { 'age': 40 }
20548     *   ]
20549     * };
20550     *
20551     * _.merge(names, ages);
20552     * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }
20553     *
20554     * var food = {
20555     *   'fruits': ['apple'],
20556     *   'vegetables': ['beet']
20557     * };
20558     *
20559     * var otherFood = {
20560     *   'fruits': ['banana'],
20561     *   'vegetables': ['carrot']
20562     * };
20563     *
20564     * _.merge(food, otherFood, function(a, b) {
20565     *   return _.isArray(a) ? a.concat(b) : undefined;
20566     * });
20567     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }
20568     */
20569    function merge(object) {
20570      var args = arguments,
20571          length = 2;
20572
20573      if (!isObject(object)) {
20574        return object;
20575      }
20576      // allows working with `_.reduce` and `_.reduceRight` without using
20577      // their `index` and `collection` arguments
20578      if (typeof args[2] != 'number') {
20579        length = args.length;
20580      }
20581      if (length > 3 && typeof args[length - 2] == 'function') {
20582        var callback = baseCreateCallback(args[--length - 1], args[length--], 2);
20583      } else if (length > 2 && typeof args[length - 1] == 'function') {
20584        callback = args[--length];
20585      }
20586      var sources = slice(arguments, 1, length),
20587          index = -1,
20588          stackA = getArray(),
20589          stackB = getArray();
20590
20591      while (++index < length) {
20592        baseMerge(object, sources[index], callback, stackA, stackB);
20593      }
20594      releaseArray(stackA);
20595      releaseArray(stackB);
20596      return object;
20597    }
20598
20599    /**
20600     * Creates a shallow clone of `object` excluding the specified properties.
20601     * Property names may be specified as individual arguments or as arrays of
20602     * property names. If a callback is provided it will be executed for each
20603     * property of `object` omitting the properties the callback returns truey
20604     * for. The callback is bound to `thisArg` and invoked with three arguments;
20605     * (value, key, object).
20606     *
20607     * @static
20608     * @memberOf _
20609     * @category Objects
20610     * @param {Object} object The source object.
20611     * @param {Function|...string|string[]} [callback] The properties to omit or the
20612     *  function called per iteration.
20613     * @param {*} [thisArg] The `this` binding of `callback`.
20614     * @returns {Object} Returns an object without the omitted properties.
20615     * @example
20616     *
20617     * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
20618     * // => { 'name': 'fred' }
20619     *
20620     * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
20621     *   return typeof value == 'number';
20622     * });
20623     * // => { 'name': 'fred' }
20624     */
20625    function omit(object, callback, thisArg) {
20626      var result = {};
20627      if (typeof callback != 'function') {
20628        var props = [];
20629        forIn(object, function(value, key) {
20630          props.push(key);
20631        });
20632        props = baseDifference(props, baseFlatten(arguments, true, false, 1));
20633
20634        var index = -1,
20635            length = props.length;
20636
20637        while (++index < length) {
20638          var key = props[index];
20639          result[key] = object[key];
20640        }
20641      } else {
20642        callback = lodash.createCallback(callback, thisArg, 3);
20643        forIn(object, function(value, key, object) {
20644          if (!callback(value, key, object)) {
20645            result[key] = value;
20646          }
20647        });
20648      }
20649      return result;
20650    }
20651
20652    /**
20653     * Creates a two dimensional array of an object's key-value pairs,
20654     * i.e. `[[key1, value1], [key2, value2]]`.
20655     *
20656     * @static
20657     * @memberOf _
20658     * @category Objects
20659     * @param {Object} object The object to inspect.
20660     * @returns {Array} Returns new array of key-value pairs.
20661     * @example
20662     *
20663     * _.pairs({ 'barney': 36, 'fred': 40 });
20664     * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
20665     */
20666    function pairs(object) {
20667      var index = -1,
20668          props = keys(object),
20669          length = props.length,
20670          result = Array(length);
20671
20672      while (++index < length) {
20673        var key = props[index];
20674        result[index] = [key, object[key]];
20675      }
20676      return result;
20677    }
20678
20679    /**
20680     * Creates a shallow clone of `object` composed of the specified properties.
20681     * Property names may be specified as individual arguments or as arrays of
20682     * property names. If a callback is provided it will be executed for each
20683     * property of `object` picking the properties the callback returns truey
20684     * for. The callback is bound to `thisArg` and invoked with three arguments;
20685     * (value, key, object).
20686     *
20687     * @static
20688     * @memberOf _
20689     * @category Objects
20690     * @param {Object} object The source object.
20691     * @param {Function|...string|string[]} [callback] The function called per
20692     *  iteration or property names to pick, specified as individual property
20693     *  names or arrays of property names.
20694     * @param {*} [thisArg] The `this` binding of `callback`.
20695     * @returns {Object}
20695 Returns an object composed of the picked properties.
20696     * @example
20697     *
20698     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
20699     * // => { 'name': 'fred' }
20700     *
20701     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
20702     *   return key.charAt(0) != '_';
20703     * });
20704     * // => { 'name': 'fred' }
20705     */
20706    function pick(object, callback, thisArg) {
20707      var result = {};
20708      if (typeof callback != 'function') {
20709        var index = -1,
20710            props = baseFlatten(arguments, true, false, 1),
20711            length = isObject(object) ? props.length : 0;
20712
20713        while (++index < length) {
20714          var key = props[index];
20715          if (key in object) {
20716            result[key] = object[key];
20717          }
20718        }
20719      } else {
20720        callback = lodash.createCallback(callback, thisArg, 3);
20721        forIn(object, function(value, key, object) {
20722          if (callback(value, key, object)) {
20723            result[key] = value;
20724          }
20725        });
20726      }
20727      return result;
20728    }
20729
20730    /**
20731     * An alternative to `_.reduce` this method transforms `object` to a new
20732     * `accumulator` object which is the result of running each of its own
20733     * enumerable properties through a callback, with each callback execution
20734     * potentially mutating the `accumulator` object. The callback is bound to
20735     * `thisArg` and invoked with four arguments; (accumulator, value, key, object).
20736     * Callbacks may exit iteration early by explicitly returning `false`.
20737     *
20738     * @static
20739     * @memberOf _
20740     * @category Objects
20741     * @param {Array|Object} object The object to iterate over.
20742     * @param {Function} [callback=identity] The function called per iteration.
20743     * @param {*} [accumulator] The custom accumulator value.
20744     * @param {*} [thisArg] The `this` binding of `callback`.
20745     * @returns {*} Returns the accumulated value.
20746     * @example
20747     *
20748     * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {
20749     *   num *= num;
20750     *   if (num % 2) {
20751     *     return result.push(num) < 3;
20752     *   }
20753     * });
20754     * // => [1, 9, 25]
20755     *
20756     * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
20757     *   result[key] = num * 3;
20758     * });
20759     * // => { 'a': 3, 'b': 6, 'c': 9 }
20760     */
20761    function transform(object, callback, accumulator, thisArg) {
20762      var isArr = isArray(object);
20763      if (accumulator == null) {
20764        if (isArr) {
20765          accumulator = [];
20766        } else {
20767          var ctor = object && object.constructor,
20768              proto = ctor && ctor.prototype;
20769
20770          accumulator = baseCreate(proto);
20771        }
20772      }
20773      if (callback) {
20774        callback = lodash.createCallback(callback, thisArg, 4);
20775        (isArr ? forEach : forOwn)(object, function(value, index, object) {
20776          return callback(accumulator, value, index, object);
20777        });
20778      }
20779      return accumulator;
20780    }
20781
20782    /**
20783     * Creates an array composed of the own enumerable property values of `object`.
20784     *
20785     * @static
20786     * @memberOf _
20787     * @category Objects
20788     * @param {Object} object The object to inspect.
20789     * @returns {Array} Returns an array of property values.
20790     * @example
20791     *
20792     * _.values({ 'one': 1, 'two': 2, 'three': 3 });
20793     * // => [1, 2, 3] (property order is not guaranteed across environments)
20794     */
20795    function values(object) {
20796      var index = -1,
20797          props = keys(object),
20798          length = props.length,
20799          result = Array(length);
20800
20801      while (++index < length) {
20802        result[index] = object[props[index]];
20803      }
20804      return result;
20805    }
20806
20807    /*--------------------------------------------------------------------------*/
20808
20809    /**
20810     * Creates an array of elements from the specified indexes, or keys, of the
20811     * `collection`. Indexes may be specified as individual arguments or as arrays
20812     * of indexes.
20813     *
20814     * @static
20815     * @memberOf _
20816     * @category Collections
20817     * @param {Array|Object|string} collection The collection to iterate over.
20818     * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`
20819     *   to retrieve, specified as individual indexes or arrays of indexes.
20820     * @returns {Array} Returns a new array of elements corresponding to the
20821     *  provided indexes.
20822     * @example
20823     *
20824     * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);
20825     * // => ['a', 'c', 'e']
20826     *
20827     * _.at(['fred', 'barney', 'pebbles'], 0, 2);
20828     * // => ['fred', 'pebbles']
20829     */
20830    function at(collection) {
20831      var args = arguments,
20832          index = -1,
20833          props = baseFlatten(args, true, false, 1),
20834          length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,
20835          result = Array(length);
20836
20837      while(++index < length) {
20838        result[index] = collection[props[index]];
20839      }
20840      return result;
20841    }
20842
20843    /**
20844     * Checks if a given value is present in a collection using strict equality
20845     * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
20846     * offset from the end of the collection.
20847     *
20848     * @static
20849     * @memberOf _
20850     * @alias include
20851     * @category Collections
20852     * @param {Array|Object|string} collection The collection to iterate over.
20853     * @param {*} target The value to check for.
20854     * @param {number} [fromIndex=0] The index to search from.
20855     * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
20856     * @example
20857     *
20858     * _.contains([1, 2, 3], 1);
20859     * // => true
20860     *
20861     * _.contains([1, 2, 3], 1, 2);
20862     * // => false
20863     *
20864     * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
20865     * // => true
20866     *
20867     * _.contains('pebbles', 'eb');
20868     * // => true
20869     */
20870    function contains(collection, target, fromIndex) {
20871      var index = -1,
20872          indexOf = getIndexOf(),
20873          length = collection ? collection.length : 0,
20874          result = false;
20875
20876      fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
20877      if (isArray(collection)) {
20878        result = indexOf(collection, target, fromIndex) > -1;
20879      } else if (typeof length == 'number') {
20880        result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;
20881      } else {
20882        forOwn(collection, function(value) {
20883          if (++index >= fromIndex) {
20884            return !(result = value === target);
20885          }
20886        });
20887      }
20888      return result;
20889    }
20890
20891    /**
20892     * Creates an object composed of keys generated from the results of running
20893     * each element of `collection` through the callback. The corresponding value
20894     * of each key is the number of times the key was returned by the callback.
20895     * The callback is bound to `thisArg` and invoked with three arguments;
20896     * (value, index|key, collection).
20897     *
20898     * If a property name is provided for `callback` the created "_.pluck" style
20899     * callback will return the property value of the given element.
20900     *
20901     * If an object is provided for `callback` the created "_.where" style callback
20902     * will return `true` for elements that have the properties of the given object,
20903     * else `false`.
20904     *
20905     * @static
20906     * @memberOf _
20907     * @category Collections
20908     * @param {Array|Object|string} collection The collection to iterate over.
20909     * @param {Function|Object|string} [callback=identity] The function called
20910     *  per iteration. If a property name or object is provided it will be used
20911     *  to create a "_.pluck" or "_.where" style callback, respectively.
20912     * @param {*} [thisArg] The `this` binding of `callback`.
20913     * @returns {Object} Returns the composed aggregate object.
20914     * @example
20915     *
20916     * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
20917     * // => { '4': 1, '6': 2 }
20918     *
20919     * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
20920     * // => { '4': 1, '6': 2 }
20921     *
20922     * _.countBy(['one', 'two', 'three'], 'length');
20923     * // => { '3': 2, '5': 1 }
20924     */
20925    var countBy = createAggregator(function(result, value, key) {
20926      (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
20927    });
20928
20929    /**
20930     * Checks if the given callback returns truey value for **all** elements of
20931     * a collection. The callback is bound to `thisArg` and invoked with three
20932     * arguments; (value, index|key, collection).
20933     *
20934     * If a property name is provided for `callback` the created "_.pluck" style
20935     * callback will return the property value of the given element.
20936     *
20937     * If an object is provided for `callback` the created "_.where" style callback
20938     * will return `true` for elements that have the properties of the given object,
20939     * else `false`.
20940     *
20941     * @static
20942     * @memberOf _
20943     * @alias all
20944     * @category Collections
20945     * @param {Array|Object|string} collection The collection to iterate over.
20946     * @param {Function|Object|string} [callback=identity] The function called
20947     *  per iteration. If a property name or object is provided it will be used
20948     *  to create a "_.pluck" or "_.where" style callback, respectively.
20949     * @param {*} [thisArg] The `this` binding of `callback`.
20950     * @returns {boolean} Returns `true` if all elements passed the callback check,
20951     *  else `false`.
20952     * @example
20953     *
20954     * _.every([true, 1, null, 'yes']);
20955     * // => false
20956     *
20957     * var characters = [
20958     *   { 'name': 'barney', 'age': 36 },
20959     *   { 'name': 'fred',   'age': 40 }
20960     * ];
20961     *
20962     * // using "_.pluck" callback shorthand
20963     * _.every(characters, 'age');
20964     * // => true
20965     *
20966     * // using "_.where" callback shorthand
20967     * _.every(characters, { 'age': 36 });
20968     * // => false
20969     */
20970    function every(collection, callback, thisArg) {
20971      var result = true;
20972      callback = lodash.createCallback(callback, thisArg, 3);
20973
20974      var index = -1,
20975          length = collection ? collection.length : 0;
20976
20977      if (typeof length == 'number') {
20978        while (++index < length) {
20979          if (!(result = !!callback(collection[index], index, collection))) {
20980            break;
20981          }
20982        }
20983      } else {
20984        forOwn(collection, function(value, index, collection) {
20985          return (result = !!callback(value, index, collection));
20986        });
20987      }
20988      return result;
20989    }
20990
20991    /**
20992     * Iterates over elements of a collection, returning an array of all elements
20993     * the callback returns truey for. The callback is bound to `thisArg` and
20994     * invoked with three arguments; (value, index|key, collection).
20995     *
20996     * If a property name is provided for `callback` the created "_.pluck" style
20997     * callback will return the property value of the given element.
20998     *
20999     * If an object is provided for `callback` the created "_.where" style callback
21000     * will return `true` for elements that have the properties of the given object,
21001     * else `false`.
21002     *
21003     * @static
21004     * @memberOf _
21005     * @alias select
21006     * @category Collections
21007     * @param {Array|Object|string} collection The collection to iterate over.
21008     * @param {Function|Object|string} [callback=identity] The function called
21009     *  per iteration. If a property name or object is provided it will be used
21010     *  to create a "_.pluck" or "_.where" style callback, respectively.
21011     * @param {*} [thisArg] The `this` binding of `callback`.
21012     * @returns {Array} Returns a new array of elements that passed the callback check.
21013     * @example
21014     *
21015     * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
21016     * // => [2, 4, 6]
21017     *
21018     * var characters = [
21019     *   { 'name': 'barney', 'age': 36, 'blocked': false },
21020     *   { 'name': 'fred',   'age': 40, 'blocked': true }
21021     * ];
21022     *
21023     * // using "_.pluck" callback shorthand
21024     * _.filter(characters, 'blocked');
21025     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
21026     *
21027     * // using "_.where" callback shorthand
21028     * _.filter(characters, { 'age': 36 });
21029     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
21030     */
21031    function filter(collection, callback, thisArg) {
21032      var result = [];
21033      callback = lodash.createCallback(callback, thisArg, 3);
21034
21035      var index = -1,
21036          length = collection ? collection.length : 0;
21037
21038      if (typeof length == 'number') {
21039        while (++index < length) {
21040          var value = collection[index];
21041          if (callback(value, index, collection)) {
21042            result.push(value);
21043          }
21044        }
21045      } else {
21046        forOwn(collection, function(value, index, collection) {
21047          if (callback(value, index, collection)) {
21048            result.push(value);
21049          }
21050        });
21051      }
21052      return result;
21053    }
21054
21055    /**
21056     * Iterates over elements of a collection, returning the first element that
21057     * the callback returns truey for. The callback is bound to `thisArg` and
21058     * invoked with three arguments; (value, index|key, collection).
21059     *
21060     * If a property name is provided for `callback` the created "_.pluck" style
21061     * callback will return the property value of the given element.
21062     *
21063     * If an object is provided for `callback` the created "_.where" style callback
21064     * will return `true` for elements that have the properties of the given object,
21065     * else `false`.
21066     *
21067     * @static
21068     * @memberOf _
21069     * @alias detect, findWhere
21070     * @category Collections
21071     * @param {Array|Object|string} collection The collection to iterate over.
21072     * @param {Function|Object|string} [callback=identity] The function called
21073     *  per iteration. If a property name or object is provided it will be used
21074     *  to create a "_.pluck" or "_.where" style callback, respectively.
21075     * @param {*} [thisArg] The `this` binding of `callback`.
21076     * @returns {*} Returns the found element, else `undefined`.
21077     * @example
21078     *
21079     * var characters = [
21080     *   { 'name': 'barney',  'age': 36, 'blocked': false },
21081     *   { 'name': 'fred',    'age': 40, 'blocked': true },
21082     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
21083     * ];
21084     *
21085     * _.find(characters, function(chr) {
21086     *   return chr.age < 40;
21087     * });
21088     * // => { 'name': 'barney', 'age': 36, 'blocked': false }
21089     *
21090     * // using "_.where" callback shorthand
21091     * _.find(characters, { 'age': 1 });
21092     * // =>  { 'name': 'pebbles', 'age': 1, 'blocked': false }
21093     *
21094     * // using "_.pluck" callback shorthand
21095     * _.find(characters, 'blocked');
21096     * // => { 'name': 'fred', 'age': 40, 'blocked': true }
21097     */
21098    function find(collection, callback, thisArg) {
21099      callback = lodash.createCallback(callback, thisArg, 3);
21100
21101      var index = -1,
21102          length = collection ? collection.length : 0;
21103
21104      if (typeof length == 'number') {
21105        while (++index < length) {
21106          var value = collection[index];
21107          if (callback(value, index, collection)) {
21108            return value;
21109          }
21110        }
21111      } else {
21112        var result;
21113        forOwn(collection, function(value, index, collection) {
21114          if (callback(value, index, collection)) {
21115            result = value;
21116            return false;
21117          }
21118        });
21119        return result;
21120      }
21121    }
21122
21123    /**
21124     * This method is like `_.find` except that it iterates over elements
21125     * of a `collection` from right to left.
21126     *
21127     * @static
21128     * @memberOf _
21129     * @category Collections
21130     * @param {Array|Object|string} collection The collection to iterate over.
21131     * @param {Function|Object|string} [callback=identity] The function called
21132     *  per iteration. If a property name or object is provided it will be used
21133     *  to create a "_.pluck" or "_.where" style callback, respectively.
21134     * @param {*} [thisArg] The `this` binding of `callback`.
21135     * @returns {*} Returns the found element, else `undefined`.
21136     * @example
21137     *
21138     * _.findLast([1, 2, 3, 4], function(num) {
21139     *   return num % 2 == 1;
21140     * });
21141     * // => 3
21142     */
21143    function findLast(collection, callback, thisArg) {
21144      var result;
21145      callback = lodash.createCallback(callback, thisArg, 3);
21146      forEachRight(collection, function(value, index, collection) {
21147        if (callback(value, index, collection)) {
21148          result = value;
21149          return false;
21150        }
21151      });
21152      return result;
21153    }
21154
21155    /**
21156     * Iterates over elements of a collection, executing the callback for each
21157     * element. The callback is bound to `thisArg` and invoked with three arguments;
21158     * (value, index|key, collection). Callbacks may exit iteration early by
21159     * explicitly returning `false`.
21160     *
21161     * Note: As with other "Collections" methods, objects with a `length` property
21162     * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
21163     * may be used for object iteration.
21164     *
21165     * @static
21166     * @memberOf _
21167     * @alias each
21168     * @category Collections
21169     * @param {Array|Object|string} collection The collection to iterate over.
21170     * @param {Function} [callback=identity] The function called per iteration.
21171     * @param {*} [thisArg] The `this` binding of `callback`.
21172     * @returns {Array|Object|string} Returns `collection`.
21173     * @example
21174     *
21175     * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
21176     * // => logs each number and returns '1,2,3'
21177     *
21178     * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
21179     * // => logs each number and returns the object (property order is not guaranteed across environments)
21180     */
21181    function forEach(collection, callback, thisArg) {
21182      var index = -1,
21183          length = collection ? collection.length : 0;
21184
21185      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
21186      if (typeof length == 'number') {
21187        while (++index < length) {
21188          if (callback(collection[index], index, collection) === false) {
21189            break;
21190          }
21191        }
21192      } else {
21193        forOwn(collection, callback);
21194      }
21195      return collection;
21196    }
21197
21198    /**
21199     * This method is like `_.forEach` except that it iterates over elements
21200     * of a `collection` from right to left.
21201     *
21202     * @static
21203     * @memberOf _
21204     * @alias eachRight
21205     * @category Collections
21206     * @param {Array|Object|string} collection The collection to iterate over.
21207     * @param {Function} [callback=identity] The function called per iteration.
21208     * @param {*} [thisArg] The `this` binding of `callback`.
21209     * @returns {Array|Object|string} Returns `collection`.
21210     * @example
21211     *
21212     * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
21213     * // => logs each number from right to left and returns '3,2,1'
21214     */
21215    function forEachRight(collection, callback, thisArg) {
21216      var length = collection ? collection.length : 0;
21217      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
21218      if (typeof length == 'number') {
21219        while (length--) {
21220          if (callback(collection[length], length, collection) === false) {
21221            break;
21222          }
21223        }
21224      } else {
21225        var props = keys(collection);
21226        length = props.length;
21227        forOwn(collection, function(value, key, collection) {
21228          key = props ? props[--length] : --length;
21229          return callback(collection[key], key, collection);
21230        });
21231      }
21232      return collection;
21233    }
21234
21235    /**
21236     * Creates an object composed of keys generated from the results of running
21237     * each element of a collection through the callback. The corresponding value
21238     * of each key is an array of the elements responsible for generating the key.
21239     * The callback is bound to `thisArg` and invoked with three arguments;
21240     * (value, index|key, collection).
21241     *
21242     * If a property name is provided for `callback` the created "_.pluck" style
21243     * callback will return the property value of the given element.
21244     *
21245     * If an object is provided for `callback` the created "_.where" style callback
21246     * will return `true` for elements that have the properties of the given object,
21247     * else `false`
21248     *
21249     * @static
21250     * @memberOf _
21251     * @category Collections
21252     * @param {Array|Object|string} collection The collection to iterate over.
21253     * @param {Function|Object|string} [callback=identity] The function called
21254     *  per iteration. If a property name or object is provided it will be used
21255     *  to create a "_.pluck" or "_.where" style callback, respectively.
21256     * @param {*} [thisArg] The `this` binding of `callback`.
21257     * @returns {Object} Returns the composed aggregate object.
21258     * @example
21259     *
21260     * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
21261     * // => { '4': [4.2], '6': [6.1, 6.4] }
21262     *
21263     * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
21264     * // => { '4': [4.2], '6': [6.1, 6.4] }
21265     *
21266     * // using "_.pluck" callback shorthand
21267     * _.groupBy(['one', 'two', 'three'], 'length');
21268     * // => { '3': ['one', 'two'], '5': ['three'] }
21269     */
21270    var groupBy = createAggregator(function(result, value, key) {
21271      (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
21272    });
21273
21274    /**
21275     * Creates an object composed of keys generated from the results of running
21276     * each element of the collection through the given callback. The corresponding
21277     * value of each key is the last element responsible for generating the key.
21278     * The callback is bound to `thisArg` and invoked with three arguments;
21279     * (value, index|key, collection).
21280     *
21281     * If a property name is provided for `callback` the created "_.pluck" style
21282     * callback will return the property value of the given element.
21283     *
21284     * If an object is provided for `callback` the created "_.where" style callback
21285     * will return `true` for elements that have the properties of the given object,
21286     * else `false`.
21287     *
21288     * @static
21289     * @memberOf _
21290     * @category Collections
21291     * @param {Array|Object|string} collection The collection to iterate over.
21292     * @param {Function|Object|string} [callback=identity] The function called
21293     *  per iteration. If a property name or object is provided it will be used
21294     *  to create a "_.pluck" or "_.where" style callback, respectively.
21295     * @param {*} [thisArg] The `this` binding of `callback`.
21296     * @returns {Object} Returns the composed aggregate object.
21297     * @example
21298     *
21299     * var keys = [
21300     *   { 'dir': 'left', 'code': 97 },
21301     *   { 'dir': 'right', 'code': 100 }
21302     * ];
21303     *
21304     * _.indexBy(keys, 'dir');
21305     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
21306     *
21307     * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
21308     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
21309     *
21310     * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
21311     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
21312     */
21313    var indexBy = createAggregator(function(result, value, key) {
21314      result[key] = value;
21315    });
21316
21317    /**
21318     * Invokes the method named by `methodName` on each element in the `collection`
21319     * returning an array of the results of each invoked method. Additional arguments
21320     * will be provided to each invoked method. If `methodName` is a function it
21321     * will be invoked for, and `this` bound to, each element in the `collection`.
21322     *
21323     * @static
21324     * @memberOf _
21325     * @category Collections
21326     * @param {Array|Object|string} collection The collection to iterate over.
21327     * @param {Function|string} methodName The name of the method to invoke or
21328     *  the function invoked per iteration.
21329     * @param {...*} [arg] Arguments to invoke the method with.
21330     * @returns {Array} Returns a new array of the results of each invoked method.
21331     * @example
21332     *
21333     * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
21334     * // => [[1, 5, 7], [1, 2, 3]]
21335     *
21336     * _.invoke([123, 456], String.prototype.split, '');
21337     * // => [['1', '2', '3'], ['4', '5', '6']]
21338     */
21339    function invoke(collection, methodName) {
21340      var args = slice(arguments, 2),
21341          index = -1,
21342          isFunc = typeof methodName == 'function',
21343          length = collection ? collection.length : 0,
21344          result = Array(typeof length == 'number' ? length : 0);
21345
21346      forEach(collection, function(value) {
21347        result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
21348      });
21349      return result;
21350    }
21351
21352    /**
21353     * Creates an array of values by running each element in the collection
21354     * through the callback. The callback is bound to `thisArg` and invoked with
21355     * three arguments; (value, index|key, collection).
21356     *
21357     * If a property name is provided for `callback` the created "_.pluck" style
21358     * callback will return the property value of the given element.
21359     *
21360     * If an object is provided for `callback` the created "_.where" style callback
21361     * will return `true` for elements that have the properties of the given object,
21362     * else `false`.
21363     *
21364     * @static
21365     * @memberOf _
21366     * @alias collect
21367     * @category Collections
21368     * @param {Array|Object|string} collection The collection to iterate over.
21369     * @param {Function|Object|string} [callback=identity] The function called
21370     *  per iteration. If a property name or object is provided it will be used
21371     *  to create a "_.pluck" or "_.where" style callback, respectively.
21372     * @param {*} [thisArg] The `this` binding of `callback`.
21373     * @returns {Array} Returns a new array of the results of each `callback` execution.
21374     * @example
21375     *
21376     * _.map([1, 2, 3], function(num) { return num * 3; });
21377     * // => [3, 6, 9]
21378     *
21379     * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
21380     * // => [3, 6, 9] (property order is not guaranteed across environments)
21381     *
21382     * var characters = [
21383     *   { 'name': 'barney', 'age': 36 },
21384     *   { 'name': 'fred',   'age': 40 }
21385     * ];
21386     *
21387     * // using "_.pluck" callback shorthand
21388     * _.map(characters, 'name');
21389     * // => ['barney', 'fred']
21390     */
21391    function map(collection, callback, thisArg) {
21392      var index = -1,
21393          length = collection ? collection.length : 0;
21394
21395      callback = lodash.createCallback(callback, thisArg, 3);
21396      if (typeof length == 'number') {
21397        var result = Array(length);
21398        while (++index < length) {
21399          result[index] = callback(collection[index], index, collection);
21400        }
21401      } else {
21402        result = [];
21403        forOwn(collection, function(value, key, collection) {
21404          result[++index] = callback(value, key, collection);
21405        });
21406      }
21407      return result;
21408    }
21409
21410    /**
21411     * Retrieves the maximum value of a collection. If the collection is empty or
21412     * falsey `-Infinity` is returned. If a callback is provided it will be executed
21413     * for each value in the collection to generate the criterion by which the value
21414     * is ranked. The callback is bound to `thisArg` and invoked with three
21415     * arguments; (value, index, collection).
21416     *
21417     * If a property name is provided for `callback` the created "_.pluck" style
21418     * callback will return the property value of the given element.
21419     *
21420     * If an object is provided for `callback` the created "_.where" style callback
21421     * will return `true` for elements that have the properties of the given object,
21422     * else `false`.
21423     *
21424     * @static
21425     * @memberOf _
21426     * @category Collections
21427     * @param {Array|Object|string} collection The collection to iterate over.
21428     * @param {Function|Object|string} [callback=identity] The function called
21429     *  per iteration. If a property name or object is provided it will be used
21430     *  to create a "_.pluck" or "_.where" style callback, respectively.
21431     * @param {*} [thisArg] The `this` binding of `callback`.
21432     * @returns {*} Returns the maximum value.
21433     * @example
21434     *
21435     * _.max([4, 2, 8, 6]);
21436     * // => 8
21437     *
21438     * var characters = [
21439     *   { 'name': 'barney', 'age': 36 },
21440     *   { 'name': 'fred',   'age': 40 }
21441     * ];
21442     *
21443     * _.max(characters, function(chr) { return chr.age; });
21444     * // => { 'name': 'fred', 'age': 40 };
21445     *
21446     * // using "_.pluck" callback shorthand
21447     * _.max(characters, 'age');
21448     * // => { 'name': 'fred', 'age': 40 };
21449     */
21450    function max(collection, callback, thisArg) {
21451      var computed = -Infinity,
21452          result = computed;
21453
21454      // allows working with functions like `_.map` without using
21455      // their `index` argument as a callback
21456      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
21457        callback = null;
21458      }
21459      if (callback == null && isArray(collection)) {
21460        var index = -1,
21461            length = collection.length;
21462
21463        while (++index < length) {
21464          var value = collection[index];
21465          if (value > result) {
21466            result = value;
21467          }
21468        }
21469      } else {
21470        callback = (callback == null && isString(collection))
21471          ? charAtCallback
21472          : lodash.createCallback(callback, thisArg, 3);
21473
21474        forEach(collection, function(value, index, collection) {
21475          var current = callback(value, index, collection);
21476          if (current > computed) {
21477            computed = current;
21478            result = value;
21479          }
21480        });
21481      }
21482      return result;
21483    }
21484
21485    /**
21486     * Retrieves the minimum value of a collection. If the collection is empty or
21487     * falsey `Infinity` is returned. If a callback is provided it will be executed
21488     * for each value in the collection to generate the criterion by which the value
21489     * is ranked. The callback is bound to `thisArg` and invoked with three
21490     * arguments; (value, index, collection).
21491     *
21492     * If a property name is provided for `callback` the created "_.pluck" style
21493     * callback will return the property value of the given element.
21494     *
21495     * If an object is provided for `callback` the created "_.where" style callback
21496     * will return `true` for elements that have the properties of the given object,
21497     * else `false`.
21498     *
21499     * @static
21500     * @memberOf _
21501     * @category Collections
21502     * @param {Array|Object|string} collection The collection to iterate over.
21503     * @param {Function|Object|string} [callback=identity] The function called
21504     *  per iteration. If a property name or object is provided it will be used
21505     *  to create a "_.pluck" or "_.where" style callback, respectively.
21506     * @param {*} [thisArg] The `this` binding of `callback`.
21507     * @returns {*} Returns the minimum value.
21508     * @example
21509     *
21510     * _.min([4, 2, 8, 6]);
21511     * // => 2
21512     *
21513     * var characters = [
21514     *   { 'name': 'barney', 'age': 36 },
21515     *   { 'name': 'fred',   'age': 40 }
21516     * ];
21517     *
21518     * _.min(characters, function(chr) { return chr.age; });
21519     * // => { 'name': 'barney', 'age': 36 };
21520     *
21521     * // using "_.pluck" callback shorthand
21522     * _.min(characters, 'age');
21523     * // => { 'name': 'barney', 'age': 36 };
21524     */
21525    function min(collection, callback, thisArg) {
21526      var computed = Infinity,
21527          result = computed;
21528
21529      // allows working with functions like `_.map` without using
21530      // their `index` argument as a callback
21531      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
21532        callback = null;
21533      }
21534      if (callback == null && isArray(collection)) {
21535        var index = -1,
21536            length = collection.length;
21537
21538        while (++index < length) {
21539          var value = collection[index];
21540          if (value < result) {
21541            result = value;
21542          }
21543        }
21544      } else {
21545        callback = (callback == null && isString(collection))
21546          ? charAtCallback
21547          : lodash.createCallback(callback, thisArg, 3);
21548
21549        forEach(collection, function(value, index, collection) {
21550          var current = callback(value, index, collection);
21551          if (current < computed) {
21552            computed = current;
21553            result = value;
21554          }
21555        });
21556      }
21557      return result;
21558    }
21559
21560    /**
21561     * Retrieves the value of a specified property from all elements in the collection.
21562     *
21563     * @static
21564     * @memberOf _
21565     * @type Function
21566     * @category Collections
21567     * @param {Array|Object|string} collection The collection to iterate over.
21568     * @param {string} property The name of the property to pluck.
21569     * @returns {Array} Returns a new array of property values.
21570     * @example
21571     *
21572     * var characters = [
21573     *   { 'name': 'barney', 'age': 36 },
21574     *   { 'name': 'fred',   'age': 40 }
21575     * ];
21576     *
21577     * _.pluck(characters, 'name');
21578     * // => ['barney', 'fred']
21579     */
21580    var pluck = map;
21581
21582    /**
21583     * Reduces a collection to a value which is the accumulated result of running
21584     * each element in the collection through the callback, where each successive
21585     * callback execution consumes the return value of the previous execution. If
21586     * `accumulator` is not provided the first element of the collection will be
21587     * used as the initial `accumulator` value. The callback is bound to `thisArg`
21588     * and invoked with four arguments; (accumulator, value, index|key, collection).
21589     *
21590     * @static
21591     * @memberOf _
21592     * @alias foldl, inject
21593     * @category Collections
21594     * @param {Array|Object|string} collection The collection to iterate over.
21595     * @param {Function} [callback=identity] The function called per iteration.
21596     * @param {*} [accumulator] Initial value of the accumulator.
21597     * @param {*} [thisArg] The `this` binding of `callback`.
21598     * @returns {*} Returns the accumulated value.
21599     * @example
21600     *
21601     * var sum = _.reduce([1, 2, 3], function(sum, num) {
21602     *   return sum + num;
21603     * });
21604     * // => 6
21605     *
21606     * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
21607     *   result[key] = num * 3;
21608     *   return result;
21609     * }, {});
21610     * // => { 'a': 3, 'b': 6, 'c': 9 }
21611     */
21612    function reduce(collection, callback, accumulator, thisArg) {
21613      if (!collection) return accumulator;
21614      var noaccum = arguments.length < 3;
21615      callback = lodash.createCallback(callback, thisArg, 4);
21616
21617      var index = -1,
21618          length = collection.length;
21619
21620      if (typeof length == 'number') {
21621        if (noaccum) {
21622          accumulator = collection[++index];
21623        }
21624        while (++index < length) {
21625          accumulator = callback(accumulator, collection[index], index, collection);
21626        }
21627      } else {
21628        forOwn(collection, function(value, index, collection) {
21629          accumulator = noaccum
21630            ? (noaccum = false, value)
21631            : callback(accumulator, value, index, collection)
21632        });
21633      }
21634      return accumulator;
21635    }
21636
21637    /**
21638     * This method is like `_.reduce` except that it iterates over elements
21639     * of a `collection` from right to left.
21640     *
21641     * @static
21642     * @memberOf _
21643     * @alias foldr
21644     * @category Collections
21645     * @param {Array|Object|string} collection The collection to iterate over.
21646     * @param {Function} [callback=identity] The function called per iteration.
21647     * @param {*} [accumulator] Initial value of the accumulator.
21648     * @param {*} [thisArg] The `this` binding of `callback`.
21649     * @returns {*} Returns the accumulated value.
21650     * @example
21651     *
21652     * var list = [[0, 1], [2, 3], [4, 5]];
21653     * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
21654     * // => [4, 5, 2, 3, 0, 1]
21655     */
21656    function reduceRight(collection, callback, accumulator, thisArg) {
21657      var noaccum = arguments.length < 3;
21658      callback = lodash.createCallback(callback, thisArg, 4);
21659      forEachRight(collection, function(value, index, collection) {
21660        accumulator = noaccum
21661          ? (noaccum = false, value)
21662          : callback(accumulator, value, index, collection);
21663      });
21664      return accumulator;
21665    }
21666
21667    /**
21668     * The opposite of `_.filter` this method returns the elements of a
21669     * collection that the callback does **not** return truey for.
21670     *
21671     * If a property name is provided for `callback` the created "_.pluck" style
21672     * callback will return the property value of the given element.
21673     *
21674     * If an object is provided for `callback` the created "_.where" style callback
21675     * will return `true` for elements that have the properties of the given object,
21676     * else `false`.
21677     *
21678     * @static
21679     * @memberOf _
21680     * @category Collections
21681     * @param {Array|Object|string} collection The collection to iterate over.
21682     * @param {Function|Object|string} [callback=identity] The function called
21683     *  per iteration. If a property name or object is provided it will be used
21684     *  to create a "_.pluck" or "_.where" style callback, respectively.
21685     * @param {*} [thisArg] The `this` binding of `callback`.
21686     * @returns {Array} Returns a new array of elements that failed the callback check.
21687     * @example
21688     *
21689     * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
21690     * // => [1, 3, 5]
21691     *
21692     * var characters = [
21693     *   { 'name': 'barney', 'age': 36, 'blocked': false },
21694     *   { 'name': 'fred',   'age': 40, 'blocked': true }
21695     * ];
21696     *
21697     * // using "_.pluck" callback shorthand
21698     * _.reject(characters, 'blocked');
21699     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
21700     *
21701     * // using "_.where" callback shorthand
21702     * _.reject(characters, { 'age': 36 });
21703     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
21704     */
21705    function reject(collection, callback, thisArg) {
21706      callback = lodash.createCallback(callback, thisArg, 3);
21707      return filter(collection, function(value, index, collection) {
21708        return !callback(value, index, collection);
21709      });
21710    }
21711
21712    /**
21713     * Retrieves a random element or `n` random elements from a collection.
21714     *
21715     * @static
21716     * @memberOf _
21717     * @category Collections
21718     * @param {Array|Object|string} collection The collection to sample.
21719     * @param {number} [n] The number of elements to sample.
21720     * @param- {Object} [guard] Allows working with functions like `_.map`
21721     *  without using their `index` arguments as `n`.
21722     * @returns {Array} Returns the random sample(s) of `collection`.
21723     * @example
21724     *
21725     * _.sample([1, 2, 3, 4]);
21726     * // => 2
21727     *
21728     * _.sample([1, 2, 3, 4], 2);
21729     * // => [3, 1]
21730     */
21731    function sample(collection, n, guard) {
21732      if (collection && typeof collection.length != 'number') {
21733        collection = values(collection);
21734      }
21735      if (n == null || guard) {
21736        return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
21737      }
21738      var result = shuffle(collection);
21739      result.length = nativeMin(nativeMax(0, n), result.length);
21740      return result;
21741    }
21742
21743    /**
21744     * Creates an array of shuffled values, using a version of the Fisher-Yates
21745     * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
21746     *
21747     * @static
21748     * @memberOf _
21749     * @category Collections
21750     * @param {Array|Object|string} collection The collection to shuffle.
21751     * @returns {Array} Returns a new shuffled collection.
21752     * @example
21753     *
21754     * _.shuffle([1, 2, 3, 4, 5, 6]);
21755     * // => [4, 1, 6, 3, 5, 2]
21756     */
21757    function shuffle(collection) {
21758      var index = -1,
21759          length = collection ? collection.length : 0,
21760          result = Array(typeof length == 'number' ? length : 0);
21761
21762      forEach(collection, function(value) {
21763        var rand = baseRandom(0, ++index);
21764        result[index] = result[rand];
21765        result[rand] = value;
21766      });
21767      return result;
21768    }
21769
21770    /**
21771     * Gets the size of the `collection` by returning `collection.length` for arrays
21772     * and array-like objects or the number of own enumerable properties for objects.
21773     *
21774     * @static
21775     * @memberOf _
21776     * @category Collections
21777     * @param {Array|Object|string} collection The collection to inspect.
21778     * @returns {number} Returns `collection.length` or number of own enumerable properties.
21779     * @example
21780     *
21781     * _.size([1, 2]);
21782     * // => 2
21783     *
21784     * _.size({ 'one': 1, 'two': 2, 'three': 3 });
21785     * // => 3
21786     *
21787     * _.size('pebbles');
21788     * // => 7
21789     */
21790    function size(collection) {
21791      var length = collection ? collection.length : 0;
21792      return typeof length == 'number' ? length : keys(collection).length;
21793    }
21794
21795    /**
21796     * Checks if the callback returns a truey value for **any** element of a
21797     * collection. The function returns as soon as it finds a passing value and
21798     * does not iterate over the entire collection. The callback is bound to
21799     * `thisArg` and invoked with three arguments; (value, index|key, collection).
21800     *
21801     * If a property name is provided for `callback` the created "_.pluck" style
21802     * callback will return the property value of the given element.
21803     *
21804     * If an object is provided for `callback` the created "_.where" style callback
21805     * will return `true` for elements that have the properties of the given object,
21806     * else `false`.
21807     *
21808     * @static
21809     * @memberOf _
21810     * @alias any
21811     * @category Collections
21812     * @param {Array|Object|string} collection The collection to iterate over.
21813     * @param {Function|Object|string} [callback=identity] The function called
21814     *  per iteration. If a property name or object is provided it will be used
21815     *  to create a "_.pluck" or "_.where" style callback, respectively.
21816     * @param {*} [thisArg] The `this` binding of `callback`.
21817     * @returns {boolean} Returns `true` if any element passed the callback check,
21818     *  else `false`.
21819     * @example
21820     *
21821     * _.some([null, 0, 'yes', false], Boolean);
21822     * // => true
21823     *
21824     * var characters = [
21825     *   { 'name': 'barney', 'age': 36, 'blocked': false },
21826     *   { 'name': 'fred',   'age': 40, 'blocked': true }
21827     * ];
21828     *
21829     * // using "_.pluck" callback shorthand
21830     * _.some(characters, 'blocked');
21831     * // => true
21832     *
21833     * // using "_.where" callback shorthand
21834     * _.some(characters, { 'age': 1 });
21835     * // => false
21836     */
21837    function some(collection, callback, thisArg) {
21838      var result;
21839      callback = lodash.createCallback(callback, thisArg, 3);
21840
21841      var index = -1,
21842          length = collection ? collection.length : 0;
21843
21844      if (typeof length == 'number') {
21845        while (++index < length) {
21846          if ((result = callback(collection[index], index, collection))) {
21847            break;
21848          }
21849        }
21850      } else {
21851        forOwn(collection, function(value, index, collection) {
21852          return !(result = callback(value, index, collection));
21853        });
21854      }
21855      return !!result;
21856    }
21857
21858    /**
21859     * Creates an array of elements, sorted in ascending order by the results of
21860     * running each element in a collection through the callback. This method
21861     * performs a stable sort, that is, it will preserve the original sort order
21862     * of equal elements. The callback is bound to `thisArg` and invoked with
21863     * three arguments; (value, index|key, collection).
21864     *
21865     * If a property name is provided for `callback` the created "_.pluck" style
21866     * callback will return the property value of the given element.
21867     *
21868     * If an array of property names is provided for `callback` the collection
21869     * will be sorted by each property value.
21870     *
21871     * If an object is provided for `callback` the created "_.where" style callback
21872     * will return `true` for elements that have the properties of the given object,
21873     * else `false`.
21874     *
21875     * @static
21876     * @memberOf _
21877     * @category Collections
21878     * @param {Array|Object|string} collection The collection to iterate over.
21879     * @param {Array|Function|Object|string} [callback=identity] The function called
21880     *  per iteration. If a property name or object is provided it will be used
21881     *  to create a "_.pluck" or "_.where" style callback, respectively.
21882     * @param {*} [thisArg] The `this` binding of `callback`.
21883     * @returns {Array} Returns a new array of sorted elements.
21884     * @example
21885     *
21886     * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
21887     * // => [3, 1, 2]
21888     *
21889     * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
21890     * // => [3, 1, 2]
21891     *
21892     * var characters = [
21893     *   { 'name': 'barney',  'age': 36 },
21894     *   { 'name': 'fred',    'age': 40 },
21895     *   { 'name': 'barney',  'age': 26 },
21896     *   { 'name': 'fred',    'age': 30 }
21897     * ];
21898     *
21899     * // using "_.pluck" callback shorthand
21900     * _.map(_.sortBy(characters, 'age'), _.values);
21901     * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
21902     *
21903     * // sorting by multiple properties
21904     * _.map(_.sortBy(characters, ['name', 'age']), _.values);
21905     * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
21906     */
21907    function sortBy(collection, callback, thisArg) {
21908      var index = -1,
21909          isArr = isArray(callback),
21910          length = collection ? collection.length : 0,
21911          result = Array(typeof length == 'number' ? length : 0);
21912
21913      if (!isArr) {
21914        callback = lodash.createCallback(callback, thisArg, 3);
21915      }
21916      forEach(collection, function(value, key, collection) {
21917        var object = result[++index] = getObject();
21918        if (isArr) {
21919          object.criteria = map(callback, function(key) { return value[key]; });
21920        } else {
21921          (object.criteria = getArray())[0] = callback(value, key, collection);
21922        }
21923        object.index = index;
21924        object.value = value;
21925      });
21926
21927      length = result.length;
21928      result.sort(compareAscending);
21929      while (length--) {
21930        var object = result[length];
21931        result[length] = object.value;
21932        if (!isArr) {
21933          releaseArray(object.criteria);
21934        }
21935        releaseObject(object);
21936      }
21937      return result;
21938    }
21939
21940    /**
21941     * Converts the `collection` to an array.
21942     *
21943     * @static
21944     * @memberOf _
21945     * @category Collections
21946     * @param {Array|Object|string} collection The collection to convert.
21947     * @returns {Array} Returns the new converted array.
21948     * @example
21949     *
21950     * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
21951     * // => [2, 3, 4]
21952     */
21953    function toArray(collection) {
21954      if (collection && typeof collection.length == 'number') {
21955        return slice(collection);
21956      }
21957      return values(collection);
21958    }
21959
21960    /**
21961     * Performs a deep comparison of each element in a `collection` to the given
21962     * `properties` object, returning an array of all elements that have equivalent
21963     * property values.
21964     *
21965     * @static
21966     * @memberOf _
21967     * @type Function
21968     * @category Collections
21969     * @param {Array|Object|string} collection The collection to iterate over.
21970     * @param {Object} props The object of property values to filter by.
21971     * @returns {Array} Returns a new array of elements that have the given properties.
21972     * @example
21973     *
21974     * var characters = [
21975     *   { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
21976     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
21977     * ];
21978     *
21979     * _.where(characters, { 'age': 36 });
21980     * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
21981     *
21982     * _.where(characters, { 'pets': ['dino'] });
21983     * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
21984     */
21985    var where = filter;
21986
21987    /*--------------------------------------------------------------------------*/
21988
21989    /**
21990     * Creates an array with all falsey values removed. The values `false`, `null`,
21991     * `0`, `""`, `undefined`, and `NaN` are all falsey.
21992     *
21993     * @static
21994     * @memberOf _
21995     * @category Arrays
21996     * @param {Array} array The array to compact.
21997     * @returns {Array} Returns a new array of filtered values.
21998     * @example
21999     *
22000     * _.compact([0, 1, false, 2, '', 3]);
22001     * // => [1, 2, 3]
22002     */
22003    function compact(array) {
22004      var index = -1,
22005          length = array ? array.length : 0,
22006          result = [];
22007
22008      while (++index < length) {
22009        var value = array[index];
22010        if (value) {
22011          result.push(value);
22012        }
22013      }
22014      return result;
22015    }
22016
22017    /**
22018     * Creates an array excluding all values of the provided arrays using strict
22019     * equality for comparisons, i.e. `===`.
22020     *
22021     * @static
22022     * @memberOf _
22023     * @category Arrays
22024     * @param {Array} array The array to process.
22025     * @param {...Array} [values] The arrays of values to exclude.
22026     * @returns {Array} Returns a new array of filtered values.
22027     * @example
22028     *
22029     * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
22030     * // => [1, 3, 4]
22031     */
22032    function difference(array) {
22033      return baseDifference(array, baseFlatten(arguments, true, true, 1));
22034    }
22035
22036    /**
22037     * This method is like `_.find` except that it returns the index of the first
22038     * element that passes the callback check, instead of the element itself.
22039     *
22040     * If a property name is provided for `callback` the created "_.pluck" style
22041     * callback will return the property value of the given element.
22042     *
22043     * If an object is provided for `callback` the created "_.where" style callback
22044     * will return `true` for elements that have the properties of the given object,
22045     * else `false`.
22046     *
22047     * @static
22048     * @memberOf _
22049     * @category Arrays
22050     * @param {Array} array The array to search.
22051     * @param {Function|Object|string} [callback=identity] The function called
22052     *  per iteration. If a property name or object is provided it will be used
22053     *  to create a "_.pluck" or "_.where" style callback, respectively.
22054     * @param {*} [thisArg] The `this` binding of `callback`.
22055     * @returns {number} Returns the index of the found element, else `-1`.
22056     * @example
22057     *
22058     * var characters = [
22059     *   { 'name': 'barney',  'age': 36, 'blocked': false },
22060     *   { 'name': 'fred',    'age': 40, 'blocked': true },
22061     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
22062     * ];
22063     *
22064     * _.findIndex(characters, function(chr) {
22065     *   return chr.age < 20;
22066     * });
22067     * // => 2
22068     *
22069     * // using "_.where" callback shorthand
22070     * _.findIndex(characters, { 'age': 36 });
22071     * // => 0
22072     *
22073     * // using "_.pluck" callback shorthand
22074     * _.findIndex(characters, 'blocked');
22075     * // => 1
22076     */
22077    function findIndex(array, callback, thisArg) {
22078      var index = -1,
22079          length = array ? array.length : 0;
22080
22081      callback = lodash.createCallback(callback, thisArg, 3);
22082      while (++index < length) {
22083        if (callback(array[index], index, array)) {
22084          return index;
22085        }
22086      }
22087      return -1;
22088    }
22089
22090    /**
22091     * This method is like `_.findIndex` except that it iterates over elements
22092     * of a `collection` from right to left.
22093     *
22094     * If a property name is provided for `callback` the created "_.pluck" style
22095     * callback will return the property value of the given element.
22096     *
22097     * If an object is provided for `callback` the created "_.where" style callback
22098     * will return `true` for elements that have the properties of the given object,
22099     * else `false`.
22100     *
22101     * @static
22102     * @memberOf _
22103     * @category Arrays
22104     * @param {Array} array The array to search.
22105     * @param {Function|Object|string} [callback=identity] The function called
22106     *  per iteration. If a property name or object is provided it will be used
22107     *  to create a "_.pluck" or "_.where" style callback, respectively.
22108     * @param {*} [thisArg] The `this` binding of `callback`.
22109     * @returns {number} Returns the index of the found element, else `-1`.
22110     * @example
22111     *
22112     * var characters = [
22113     *   { 'name': 'barney',  'age': 36, 'blocked': true },
22114     *   { 'name': 'fred',    'age': 40, 'blocked': false },
22115     *   { 'name': 'pebbles', 'age': 1,  'blocked': true }
22116     * ];
22117     *
22118     * _.findLastIndex(characters, function(chr) {
22119     *   return chr.age > 30;
22120     * });
22121     * // => 1
22122     *
22123     * // using "_.where" callback shorthand
22124     * _.findLastIndex(characters, { 'age': 36 });
22125     * // => 0
22126     *
22127     * // using "_.pluck" callback shorthand
22128     * _.findLastIndex(characters, 'blocked');
22129     * // => 2
22130     */
22131    function findLastIndex(array, callback, thisArg) {
22132      var length = array ? array.length : 0;
22133      callback = lodash.createCallback(callback, thisArg, 3);
22134      while (length--) {
22135        if (callback(array[length], length, array)) {
22136          return length;
22137        }
22138      }
22139      return -1;
22140    }
22141
22142    /**
22143     * Gets the first element or first `n` elements of an array. If a callback
22144     * is provided elements at the beginning of the array are returned as long
22145     * as the callback returns truey. The callback is bound to `thisArg` and
22146     * invoked with three arguments; (value, index, array).
22147     *
22148     * If a property name is provided for `callback` the created "_.pluck" style
22149     * callback will return the property value of the given element.
22150     *
22151     * If an object is provided for `callback` the created "_.where" style callback
22152     * will return `true` for elements that have the properties of the given object,
22153     * else `false`.
22154     *
22155     * @static
22156     * @memberOf _
22157     * @alias head, take
22158     * @category Arrays
22159     * @param {Array} array The array to query.
22160     * @param {Function|Object|number|string} [callback] The function called
22161     *  per element or the number of elements to return. If a property name or
22162     *  object is provided it will be used to create a "_.pluck" or "_.where"
22163     *  style callback, respectively.
22164     * @param {*} [thisArg] The `this` binding of `callback`.
22165     * @returns {*}
22165 Returns the first element(s) of `array`.
22166     * @example
22167     *
22168     * _.first([1, 2, 3]);
22169     * // => 1
22170     *
22171     * _.first([1, 2, 3], 2);
22172     * // => [1, 2]
22173     *
22174     * _.first([1, 2, 3], function(num) {
22175     *   return num < 3;
22176     * });
22177     * // => [1, 2]
22178     *
22179     * var characters = [
22180     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
22181     *   { 'name': 'fred',    'blocked': false, 'employer': 'slate' },
22182     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
22183     * ];
22184     *
22185     * // using "_.pluck" callback shorthand
22186     * _.first(characters, 'blocked');
22187     * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
22188     *
22189     * // using "_.where" callback shorthand
22190     * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
22191     * // => ['barney', 'fred']
22192     */
22193    function first(array, callback, thisArg) {
22194      var n = 0,
22195          length = array ? array.length : 0;
22196
22197      if (typeof callback != 'number' && callback != null) {
22198        var index = -1;
22199        callback = lodash.createCallback(callback, thisArg, 3);
22200        while (++index < length && callback(array[index], index, array)) {
22201          n++;
22202        }
22203      } else {
22204        n = callback;
22205        if (n == null || thisArg) {
22206          return array ? array[0] : undefined;
22207        }
22208      }
22209      return slice(array, 0, nativeMin(nativeMax(0, n), length));
22210    }
22211
22212    /**
22213     * Flattens a nested array (the nesting can be to any depth). If `isShallow`
22214     * is truey, the array will only be flattened a single level. If a callback
22215     * is provided each element of the array is passed through the callback before
22216     * flattening. The callback is bound to `thisArg` and invoked with three
22217     * arguments; (value, index, array).
22218     *
22219     * If a property name is provided for `callback` the created "_.pluck" style
22220     * callback will return the property value of the given element.
22221     *
22222     * If an object is provided for `callback` the created "_.where" style callback
22223     * will return `true` for elements that have the properties of the given object,
22224     * else `false`.
22225     *
22226     * @static
22227     * @memberOf _
22228     * @category Arrays
22229     * @param {Array} array The array to flatten.
22230     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
22231     * @param {Function|Object|string} [callback=identity] The function called
22232     *  per iteration. If a property name or object is provided it will be used
22233     *  to create a "_.pluck" or "_.where" style callback, respectively.
22234     * @param {*} [thisArg] The `this` binding of `callback`.
22235     * @returns {Array} Returns a new flattened array.
22236     * @example
22237     *
22238     * _.flatten([1, [2], [3, [[4]]]]);
22239     * // => [1, 2, 3, 4];
22240     *
22241     * _.flatten([1, [2], [3, [[4]]]], true);
22242     * // => [1, 2, 3, [[4]]];
22243     *
22244     * var characters = [
22245     *   { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
22246     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
22247     * ];
22248     *
22249     * // using "_.pluck" callback shorthand
22250     * _.flatten(characters, 'pets');
22251     * // => ['hoppy', 'baby puss', 'dino']
22252     */
22253    function flatten(array, isShallow, callback, thisArg) {
22254      // juggle arguments
22255      if (typeof isShallow != 'boolean' && isShallow != null) {
22256        thisArg = callback;
22257        callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;
22258        isShallow = false;
22259      }
22260      if (callback != null) {
22261        array = map(array, callback, thisArg);
22262      }
22263      return baseFlatten(array, isShallow);
22264    }
22265
22266    /**
22267     * Gets the index at which the first occurrence of `value` is found using
22268     * strict equality for comparisons, i.e. `===`. If the array is already sorted
22269     * providing `true` for `fromIndex` will run a faster binary search.
22270     *
22271     * @static
22272     * @memberOf _
22273     * @category Arrays
22274     * @param {Array} array The array to search.
22275     * @param {*} value The value to search for.
22276     * @param {boolean|number} [fromIndex=0] The index to search from or `true`
22277     *  to perform a binary search on a sorted array.
22278     * @returns {number} Returns the index of the matched value or `-1`.
22279     * @example
22280     *
22281     * _.indexOf([1, 2, 3, 1, 2, 3], 2);
22282     * // => 1
22283     *
22284     * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
22285     * // => 4
22286     *
22287     * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
22288     * // => 2
22289     */
22290    function indexOf(array, value, fromIndex) {
22291      if (typeof fromIndex == 'number') {
22292        var length = array ? array.length : 0;
22293        fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
22294      } else if (fromIndex) {
22295        var index = sortedIndex(array, value);
22296        return array[index] === value ? index : -1;
22297      }
22298      return baseIndexOf(array, value, fromIndex);
22299    }
22300
22301    /**
22302     * Gets all but the last element or last `n` elements of an array. If a
22303     * callback is provided elements at the end of the array are excluded from
22304     * the result as long as the callback returns truey. The callback is bound
22305     * to `thisArg` and invoked with three arguments; (value, index, array).
22306     *
22307     * If a property name is provided for `callback` the created "_.pluck" style
22308     * callback will return the property value of the given element.
22309     *
22310     * If an object is provided for `callback` the created "_.where" style callback
22311     * will return `true` for elements that have the properties of the given object,
22312     * else `false`.
22313     *
22314     * @static
22315     * @memberOf _
22316     * @category Arrays
22317     * @param {Array} array The array to query.
22318     * @param {Function|Object|number|string} [callback=1] The function called
22319     *  per element or the number of elements to exclude. If a property name or
22320     *  object is provided it will be used to create a "_.pluck" or "_.where"
22321     *  style callback, respectively.
22322     * @param {*} [thisArg] The `this` binding of `callback`.
22323     * @returns {Array} Returns a slice of `array`.
22324     * @example
22325     *
22326     * _.initial([1, 2, 3]);
22327     * // => [1, 2]
22328     *
22329     * _.initial([1, 2, 3], 2);
22330     * // => [1]
22331     *
22332     * _.initial([1, 2, 3], function(num) {
22333     *   return num > 1;
22334     * });
22335     * // => [1]
22336     *
22337     * var characters = [
22338     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
22339     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
22340     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
22341     * ];
22342     *
22343     * // using "_.pluck" callback shorthand
22344     * _.initial(characters, 'blocked');
22345     * // => [{ 'name': 'barney',  'blocked': false, 'employer': 'slate' }]
22346     *
22347     * // using "_.where" callback shorthand
22348     * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
22349     * // => ['barney', 'fred']
22350     */
22351    function initial(array, callback, thisArg) {
22352      var n = 0,
22353          length = array ? array.length : 0;
22354
22355      if (typeof callback != 'number' && callback != null) {
22356        var index = length;
22357        callback = lodash.createCallback(callback, thisArg, 3);
22358        while (index-- && callback(array[index], index, array)) {
22359          n++;
22360        }
22361      } else {
22362        n = (callback == null || thisArg) ? 1 : callback || n;
22363      }
22364      return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
22365    }
22366
22367    /**
22368     * Creates an array of unique values present in all provided arrays using
22369     * strict equality for comparisons, i.e. `===`.
22370     *
22371     * @static
22372     * @memberOf _
22373     * @category Arrays
22374     * @param {...Array} [array] The arrays to inspect.
22375     * @returns {Array} Returns an array of shared values.
22376     * @example
22377     *
22378     * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
22379     * // => [1, 2]
22380     */
22381    function intersection() {
22382      var args = [],
22383          argsIndex = -1,
22384          argsLength = arguments.length,
22385          caches = getArray(),
22386          indexOf = getIndexOf(),
22387          trustIndexOf = indexOf === baseIndexOf,
22388          seen = getArray();
22389
22390      while (++argsIndex < argsLength) {
22391        var value = arguments[argsIndex];
22392        if (isArray(value) || isArguments(value)) {
22393          args.push(value);
22394          caches.push(trustIndexOf && value.length >= largeArraySize &&
22395            createCache(argsIndex ? args[argsIndex] : seen));
22396        }
22397      }
22398      var array = args[0],
22399          index = -1,
22400          length = array ? array.length : 0,
22401          result = [];
22402
22403      outer:
22404      while (++index < length) {
22405        var cache = caches[0];
22406        value = array[index];
22407
22408        if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
22409          argsIndex = argsLength;
22410          (cache || seen).push(value);
22411          while (--argsIndex) {
22412            cache = caches[argsIndex];
22413            if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
22414              continue outer;
22415            }
22416          }
22417          result.push(value);
22418        }
22419      }
22420      while (argsLength--) {
22421        cache = caches[argsLength];
22422        if (cache) {
22423          releaseObject(cache);
22424        }
22425      }
22426      releaseArray(caches);
22427      releaseArray(seen);
22428      return result;
22429    }
22430
22431    /**
22432     * Gets the last element or last `n` elements of an array. If a callback is
22433     * provided elements at the end of the array are returned as long as the
22434     * callback returns truey. The callback is bound to `thisArg` and invoked
22435     * with three arguments; (value, index, array).
22436     *
22437     * If a property name is provided for `callback` the created "_.pluck" style
22438     * callback will return the property value of the given element.
22439     *
22440     * If an object is provided for `callback` the created "_.where" style callback
22441     * will return `true` for elements that have the properties of the given object,
22442     * else `false`.
22443     *
22444     * @static
22445     * @memberOf _
22446     * @category Arrays
22447     * @param {Array} array The array to query.
22448     * @param {Function|Object|number|string} [callback] The function called
22449     *  per element or the number of elements to return. If a property name or
22450     *  object is provided it will be used to create a "_.pluck" or "_.where"
22451     *  style callback, respectively.
22452     * @param {*} [thisArg] The `this` binding of `callback`.
22453     * @returns {*}
22453 Returns the last element(s) of `array`.
22454     * @example
22455     *
22456     * _.last([1, 2, 3]);
22457     * // => 3
22458     *
22459     * _.last([1, 2, 3], 2);
22460     * // => [2, 3]
22461     *
22462     * _.last([1, 2, 3], function(num) {
22463     *   return num > 1;
22464     * });
22465     * // => [2, 3]
22466     *
22467     * var characters = [
22468     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
22469     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
22470     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
22471     * ];
22472     *
22473     * // using "_.pluck" callback shorthand
22474     * _.pluck(_.last(characters, 'blocked'), 'name');
22475     * // => ['fred', 'pebbles']
22476     *
22477     * // using "_.where" callback shorthand
22478     * _.last(characters, { 'employer': 'na' });
22479     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
22480     */
22481    function last(array, callback, thisArg) {
22482      var n = 0,
22483          length = array ? array.length : 0;
22484
22485      if (typeof callback != 'number' && callback != null) {
22486        var index = length;
22487        callback = lodash.createCallback(callback, thisArg, 3);
22488        while (index-- && callback(array[index], index, array)) {
22489          n++;
22490        }
22491      } else {
22492        n = callback;
22493        if (n == null || thisArg) {
22494          return array ? array[length - 1] : undefined;
22495        }
22496      }
22497      return slice(array, nativeMax(0, length - n));
22498    }
22499
22500    /**
22501     * Gets the index at which the last occurrence of `value` is found using strict
22502     * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
22503     * as the offset from the end of the collection.
22504     *
22505     * If a property name is provided for `callback` the created "_.pluck" style
22506     * callback will return the property value of the given element.
22507     *
22508     * If an object is provided for `callback` the created "_.where" style callback
22509     * will return `true` for elements that have the properties of the given object,
22510     * else `false`.
22511     *
22512     * @static
22513     * @memberOf _
22514     * @category Arrays
22515     * @param {Array} array The array to search.
22516     * @param {*} value The value to search for.
22517     * @param {number} [fromIndex=array.length-1] The index to search from.
22518     * @returns {number} Returns the index of the matched value or `-1`.
22519     * @example
22520     *
22521     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
22522     * // => 4
22523     *
22524     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
22525     * // => 1
22526     */
22527    function lastIndexOf(array, value, fromIndex) {
22528      var index = array ? array.length : 0;
22529      if (typeof fromIndex == 'number') {
22530        index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
22531      }
22532      while (index--) {
22533        if (array[index] === value) {
22534          return index;
22535        }
22536      }
22537      return -1;
22538    }
22539
22540    /**
22541     * Removes all provided values from the given array using strict equality for
22542     * comparisons, i.e. `===`.
22543     *
22544     * @static
22545     * @memberOf _
22546     * @category Arrays
22547     * @param {Array} array The array to modify.
22548     * @param {...*} [value] The values to remove.
22549     * @returns {Array} Returns `array`.
22550     * @example
22551     *
22552     * var array = [1, 2, 3, 1, 2, 3];
22553     * _.pull(array, 2, 3);
22554     * console.log(array);
22555     * // => [1, 1]
22556     */
22557    function pull(array) {
22558      var args = arguments,
22559          argsIndex = 0,
22560          argsLength = args.length,
22561          length = array ? array.length : 0;
22562
22563      while (++argsIndex < argsLength) {
22564        var index = -1,
22565            value = args[argsIndex];
22566        while (++index < length) {
22567          if (array[index] === value) {
22568            splice.call(array, index--, 1);
22569            length--;
22570          }
22571        }
22572      }
22573      return array;
22574    }
22575
22576    /**
22577     * Creates an array of numbers (positive and/or negative) progressing from
22578     * `start` up to but not including `end`. If `start` is less than `stop` a
22579     * zero-length range is created unless a negative `step` is specified.
22580     *
22581     * @static
22582     * @memberOf _
22583     * @category Arrays
22584     * @param {number} [start=0] The start of the range.
22585     * @param {number} end The end of the range.
22586     * @param {number} [step=1] The value to increment or decrement by.
22587     * @returns {Array} Returns a new range array.
22588     * @example
22589     *
22590     * _.range(4);
22591     * // => [0, 1, 2, 3]
22592     *
22593     * _.range(1, 5);
22594     * // => [1, 2, 3, 4]
22595     *
22596     * _.range(0, 20, 5);
22597     * // => [0, 5, 10, 15]
22598     *
22599     * _.range(0, -4, -1);
22600     * // => [0, -1, -2, -3]
22601     *
22602     * _.range(1, 4, 0);
22603     * // => [1, 1, 1]
22604     *
22605     * _.range(0);
22606     * // => []
22607     */
22608    function range(start, end, step) {
22609      start = +start || 0;
22610      step = typeof step == 'number' ? step : (+step || 1);
22611
22612      if (end == null) {
22613        end = start;
22614        start = 0;
22615      }
22616      // use `Array(length)` so engines like Chakra and V8 avoid slower modes
22617      // http://youtu.be/XAqIpGU8ZZk#t=17m25s
22618      var index = -1,
22619          length = nativeMax(0, ceil((end - start) / (step || 1))),
22620          result = Array(length);
22621
22622      while (++index < length) {
22623        result[index] = start;
22624        start += step;
22625      }
22626      return result;
22627    }
22628
22629    /**
22630     * Removes all elements from an array that the callback returns truey for
22631     * and returns an array of removed elements. The callback is bound to `thisArg`
22632     * and invoked with three arguments; (value, index, array).
22633     *
22634     * If a property name is provided for `callback` the created "_.pluck" style
22635     * callback will return the property value of the given element.
22636     *
22637     * If an object is provided for `callback` the created "_.where" style callback
22638     * will return `true` for elements that have the properties of the given object,
22639     * else `false`.
22640     *
22641     * @static
22642     * @memberOf _
22643     * @category Arrays
22644     * @param {Array} array The array to modify.
22645     * @param {Function|Object|string} [callback=identity] The function called
22646     *  per iteration. If a property name or object is provided it will be used
22647     *  to create a "_.pluck" or "_.where" style callback, respectively.
22648     * @param {*} [thisArg] The `this` binding of `callback`.
22649     * @returns {Array} Returns a new array of removed elements.
22650     * @example
22651     *
22652     * var array = [1, 2, 3, 4, 5, 6];
22653     * var evens = _.remove(array, function(num) { return num % 2 == 0; });
22654     *
22655     * console.log(array);
22656     * // => [1, 3, 5]
22657     *
22658     * console.log(evens);
22659     * // => [2, 4, 6]
22660     */
22661    function remove(array, callback, thisArg) {
22662      var index = -1,
22663          length = array ? array.length : 0,
22664          result = [];
22665
22666      callback = lodash.createCallback(callback, thisArg, 3);
22667      while (++index < length) {
22668        var value = array[index];
22669        if (callback(value, index, array)) {
22670          result.push(value);
22671          splice.call(array, index--, 1);
22672          length--;
22673        }
22674      }
22675      return result;
22676    }
22677
22678    /**
22679     * The opposite of `_.initial` this method gets all but the first element or
22680     * first `n` elements of an array. If a callback function is provided elements
22681     * at the beginning of the array are excluded from the result as long as the
22682     * callback returns truey. The callback is bound to `thisArg` and invoked
22683     * with three arguments; (value, index, array).
22684     *
22685     * If a property name is provided for `callback` the created "_.pluck" style
22686     * callback will return the property value of the given element.
22687     *
22688     * If an object is provided for `callback` the created "_.where" style callback
22689     * will return `true` for elements that have the properties of the given object,
22690     * else `false`.
22691     *
22692     * @static
22693     * @memberOf _
22694     * @alias drop, tail
22695     * @category Arrays
22696     * @param {Array} array The array to query.
22697     * @param {Function|Object|number|string} [callback=1] The function called
22698     *  per element or the number of elements to exclude. If a property name or
22699     *  object is provided it will be used to create a "_.pluck" or "_.where"
22700     *  style callback, respectively.
22701     * @param {*} [thisArg] The `this` binding of `callback`.
22702     * @returns {Array} Returns a slice of `array`.
22703     * @example
22704     *
22705     * _.rest([1, 2, 3]);
22706     * // => [2, 3]
22707     *
22708     * _.rest([1, 2, 3], 2);
22709     * // => [3]
22710     *
22711     * _.rest([1, 2, 3], function(num) {
22712     *   return num < 3;
22713     * });
22714     * // => [3]
22715     *
22716     * var characters = [
22717     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
22718     *   { 'name': 'fred',    'blocked': false,  'employer': 'slate' },
22719     *   { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
22720     * ];
22721     *
22722     * // using "_.pluck" callback shorthand
22723     * _.pluck(_.rest(characters, 'blocked'), 'name');
22724     * // => ['fred', 'pebbles']
22725     *
22726     * // using "_.where" callback shorthand
22727     * _.rest(characters, { 'employer': 'slate' });
22728     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
22729     */
22730    function rest(array, callback, thisArg) {
22731      if (typeof callback != 'number' && callback != null) {
22732        var n = 0,
22733            index = -1,
22734            length = array ? array.length : 0;
22735
22736        callback = lodash.createCallback(callback, thisArg, 3);
22737        while (++index < length && callback(array[index], index, array)) {
22738          n++;
22739        }
22740      } else {
22741        n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
22742      }
22743      return slice(array, n);
22744    }
22745
22746    /**
22747     * Uses a binary search to determine the smallest index at which a value
22748     * should be inserted into a given sorted array in order to maintain the sort
22749     * order of the array. If a callback is provided it will be executed for
22750     * `value` and each element of `array` to compute their sort ranking. The
22751     * callback is bound to `thisArg` and invoked with one argument; (value).
22752     *
22753     * If a property name is provided for `callback` the created "_.pluck" style
22754     * callback will return the property value of the given element.
22755     *
22756     * If an object is provided for `callback` the created "_.where" style callback
22757     * will return `true` for elements that have the properties of the given object,
22758     * else `false`.
22759     *
22760     * @static
22761     * @memberOf _
22762     * @category Arrays
22763     * @param {Array} array The array to inspect.
22764     * @param {*} value The value to evaluate.
22765     * @param {Function|Object|string} [callback=identity] The function called
22766     *  per iteration. If a property name or object is provided it will be used
22767     *  to create a "_.pluck" or "_.where" style callback, respectively.
22768     * @param {*} [thisArg] The `this` binding of `callback`.
22769     * @returns {number} Returns the index at which `value` should be inserted
22770     *  into `array`.
22771     * @example
22772     *
22773     * _.sortedIndex([20, 30, 50], 40);
22774     * // => 2
22775     *
22776     * // using "_.pluck" callback shorthand
22777     * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
22778     * // => 2
22779     *
22780     * var dict = {
22781     *   'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
22782     * };
22783     *
22784     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
22785     *   return dict.wordToNumber[word];
22786     * });
22787     * // => 2
22788     *
22789     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
22790     *   return this.wordToNumber[word];
22791     * }, dict);
22792     * // => 2
22793     */
22794    function sortedIndex(array, value, callback, thisArg) {
22795      var low = 0,
22796          high = array ? array.length : low;
22797
22798      // explicitly reference `identity` for better inlining in Firefox
22799      callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
22800      value = callback(value);
22801
22802      while (low < high) {
22803        var mid = (low + high) >>> 1;
22804        (callback(array[mid]) < value)
22805          ? low = mid + 1
22806          : high = mid;
22807      }
22808      return low;
22809    }
22810
22811    /**
22812     * Creates an array of unique values, in order, of the provided arrays using
22813     * strict equality for comparisons, i.e. `===`.
22814     *
22815     * @static
22816     * @memberOf _
22817     * @category Arrays
22818     * @param {...Array} [array] The arrays to inspect.
22819     * @returns {Array} Returns an array of combined values.
22820     * @example
22821     *
22822     * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
22823     * // => [1, 2, 3, 5, 4]
22824     */
22825    function union() {
22826      return baseUniq(baseFlatten(arguments, true, true));
22827    }
22828
22829    /**
22830     * Creates a duplicate-value-free version of an array using strict equality
22831     * for comparisons, i.e. `===`. If the array is sorted, providing
22832     * `true` for `isSorted` will use a faster algorithm. If a callback is provided
22833     * each element of `array` is passed through the callback before uniqueness
22834     * is computed. The callback is bound to `thisArg` and invoked with three
22835     * arguments; (value, index, array).
22836     *
22837     * If a property name is provided for `callback` the created "_.pluck" style
22838     * callback will return the property value of the given element.
22839     *
22840     * If an object is provided for `callback` the created "_.where" style callback
22841     * will return `true` for elements that have the properties of the given object,
22842     * else `false`.
22843     *
22844     * @static
22845     * @memberOf _
22846     * @alias unique
22847     * @category Arrays
22848     * @param {Array} array The array to process.
22849     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
22850     * @param {Function|Object|string} [callback=identity] The function called
22851     *  per iteration. If a property name or object is provided it will be used
22852     *  to create a "_.pluck" or "_.where" style callback, respectively.
22853     * @param {*} [thisArg] The `this` binding of `callback`.
22854     * @returns {Array} Returns a duplicate-value-free array.
22855     * @example
22856     *
22857     * _.uniq([1, 2, 1, 3, 1]);
22858     * // => [1, 2, 3]
22859     *
22860     * _.uniq([1, 1, 2, 2, 3], true);
22861     * // => [1, 2, 3]
22862     *
22863     * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
22864     * // => ['A', 'b', 'C']
22865     *
22866     * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
22867     * // => [1, 2.5, 3]
22868     *
22869     * // using "_.pluck" callback shorthand
22870     * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
22871     * // => [{ 'x': 1 }, { 'x': 2 }]
22872     */
22873    function uniq(array, isSorted, callback, thisArg) {
22874      // juggle arguments
22875      if (typeof isSorted != 'boolean' && isSorted != null) {
22876        thisArg = callback;
22877        callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
22878        isSorted = false;
22879      }
22880      if (callback != null) {
22881        callback = lodash.createCallback(callback, thisArg, 3);
22882      }
22883      return baseUniq(array, isSorted, callback);
22884    }
22885
22886    /**
22887     * Creates an array excluding all provided values using strict equality for
22888     * comparisons, i.e. `===`.
22889     *
22890     * @static
22891     * @memberOf _
22892     * @category Arrays
22893     * @param {Array} array The array to filter.
22894     * @param {...*} [value] The values to exclude.
22895     * @returns {Array} Returns a new array of filtered values.
22896     * @example
22897     *
22898     * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
22899     * // => [2, 3, 4]
22900     */
22901    function without(array) {
22902      return baseDifference(array, slice(arguments, 1));
22903    }
22904
22905    /**
22906     * Creates an array that is the symmetric difference of the provided arrays.
22907     * See http://en.wikipedia.org/wiki/Symmetric_difference.
22908     *
22909     * @static
22910     * @memberOf _
22911     * @category Arrays
22912     * @param {...Array} [array] The arrays to inspect.
22913     * @returns {Array} Returns an array of values.
22914     * @example
22915     *
22916     * _.xor([1, 2, 3], [5, 2, 1, 4]);
22917     * // => [3, 5, 4]
22918     *
22919     * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);
22920     * // => [1, 4, 5]
22921     */
22922    function xor() {
22923      var index = -1,
22924          length = arguments.length;
22925
22926      while (++index < length) {
22927        var array = arguments[index];
22928        if (isArray(array) || isArguments(array)) {
22929          var result = result
22930            ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
22931            : array;
22932        }
22933      }
22934      return result || [];
22935    }
22936
22937    /**
22938     * Creates an array of grouped elements, the first of which contains the first
22939     * elements of the given arrays, the second of which contains the second
22940     * elements of the given arrays, and so on.
22941     *
22942     * @static
22943     * @memberOf _
22944     * @alias unzip
22945     * @category Arrays
22946     * @param {...Array} [array] Arrays to process.
22947     * @returns {Array} Returns a new array of grouped elements.
22948     * @example
22949     *
22950     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
22951     * // => [['fred', 30, true], ['barney', 40, false]]
22952     */
22953    function zip() {
22954      var array = arguments.length > 1 ? arguments : arguments[0],
22955          index = -1,
22956          length = array ? max(pluck(array, 'length')) : 0,
22957          result = Array(length < 0 ? 0 : length);
22958
22959      while (++index < length) {
22960        result[index] = pluck(array, index);
22961      }
22962      return result;
22963    }
22964
22965    /**
22966     * Creates an object composed from arrays of `keys` and `values`. Provide
22967     * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
22968     * or two arrays, one of `keys` and one of corresponding `values`.
22969     *
22970     * @static
22971     * @memberOf _
22972     * @alias object
22973     * @category Arrays
22974     * @param {Array} keys The array of keys.
22975     * @param {Array} [values=[]] The array of values.
22976     * @returns {Object} Returns an object composed of the given keys and
22977     *  corresponding values.
22978     * @example
22979     *
22980     * _.zipObject(['fred', 'barney'], [30, 40]);
22981     * // => { 'fred': 30, 'barney': 40 }
22982     */
22983    function zipObject(keys, values) {
22984      var index = -1,
22985          length = keys ? keys.length : 0,
22986          result = {};
22987
22988      if (!values && length && !isArray(keys[0])) {
22989        values = [];
22990      }
22991      while (++index < length) {
22992        var key = keys[index];
22993        if (values) {
22994          result[key] = values[index];
22995        } else if (key) {
22996          result[key[0]] = key[1];
22997        }
22998      }
22999      return result;
23000    }
23001
23002    /*--------------------------------------------------------------------------*/
23003
23004    /**
23005     * Creates a function that executes `func`, with  the `this` binding and
23006     * arguments of the created function, only after being called `n` times.
23007     *
23008     * @static
23009     * @memberOf _
23010     * @category Functions
23011     * @param {number} n The number of times the function must be called before
23012     *  `func` is executed.
23013     * @param {Function} func The function to restrict.
23014     * @returns {Function} Returns the new restricted function.
23015     * @example
23016     *
23017     * var saves = ['profile', 'settings'];
23018     *
23019     * var done = _.after(saves.length, function() {
23020     *   console.log('Done saving!');
23021     * });
23022     *
23023     * _.forEach(saves, function(type) {
23024     *   asyncSave({ 'type': type, 'complete': done });
23025     * });
23026     * // => logs 'Done saving!', after all saves have completed
23027     */
23028    function after(n, func) {
23029      if (!isFunction(func)) {
23030        throw new TypeError;
23031      }
23032      return function() {
23033        if (--n < 1) {
23034          return func.apply(this, arguments);
23035        }
23036      };
23037    }
23038
23039    /**
23040     * Creates a function that, when called, invokes `func` with the `this`
23041     * binding of `thisArg` and prepends any additional `bind` arguments to those
23042     * provided to the bound function.
23043     *
23044     * @static
23045     * @memberOf _
23046     * @category Functions
23047     * @param {Function} func The function to bind.
23048     * @param {*} [thisArg] The `this` binding of `func`.
23049     * @param {...*} [arg] Arguments to be partially applied.
23050     * @returns {Function} Returns the new bound function.
23051     * @example
23052     *
23053     * var func = function(greeting) {
23054     *   return greeting + ' ' + this.name;
23055     * };
23056     *
23057     * func = _.bind(func, { 'name': 'fred' }, 'hi');
23058     * func();
23059     * // => 'hi fred'
23060     */
23061    function bind(func, thisArg) {
23062      return arguments.length > 2
23063        ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
23064        : createWrapper(func, 1, null, null, thisArg);
23065    }
23066
23067    /**
23068     * Binds methods of an object to the object itself, overwriting the existing
23069     * method. Method names may be specified as individual arguments or as arrays
23070     * of method names. If no method names are provided all the function properties
23071     * of `object` will be bound.
23072     *
23073     * @static
23074     * @memberOf _
23075     * @category Functions
23076     * @param {Object} object The object to bind and assign the bound methods to.
23077     * @param {...string} [methodName] The object method names to
23078     *  bind, specified as individual method names or arrays of method names.
23079     * @returns {Object} Returns `object`.
23080     * @example
23081     *
23082     * var view = {
23083     *   'label': 'docs',
23084     *   'onClick': function() { console.log('clicked ' + this.label); }
23085     * };
23086     *
23087     * _.bindAll(view);
23088     * jQuery('#docs').on('click', view.onClick);
23089     * // => logs 'clicked docs', when the button is clicked
23090     */
23091    function bindAll(object) {
23092      var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
23093          index = -1,
23094          length = funcs.length;
23095
23096      while (++index < length) {
23097        var key = funcs[index];
23098        object[key] = createWrapper(object[key], 1, null, null, object);
23099      }
23100      return object;
23101    }
23102
23103    /**
23104     * Creates a function that, when called, invokes the method at `object[key]`
23105     * and prepends any additional `bindKey` arguments to those provided to the bound
23106     * function. This method differs from `_.bind` by allowing bound functions to
23107     * reference methods that will be redefined or don't yet exist.
23108     * See http://michaux.ca/articles/lazy-function-definition-pattern.
23109     *
23110     * @static
23111     * @memberOf _
23112     * @category Functions
23113     * @param {Object} object The object the method belongs to.
23114     * @param {string} key The key of the method.
23115     * @param {...*} [arg] Arguments to be partially applied.
23116     * @returns {Function} Returns the new bound function.
23117     * @example
23118     *
23119     * var object = {
23120     *   'name': 'fred',
23121     *   'greet': function(greeting) {
23122     *     return greeting + ' ' + this.name;
23123     *   }
23124     * };
23125     *
23126     * var func = _.bindKey(object, 'greet', 'hi');
23127     * func();
23128     * // => 'hi fred'
23129     *
23130     * object.greet = function(greeting) {
23131     *   return greeting + 'ya ' + this.name + '!';
23132     * };
23133     *
23134     * func();
23135     * // => 'hiya fred!'
23136     */
23137    function bindKey(object, key) {
23138      return arguments.length > 2
23139        ? createWrapper(key, 19, slice(arguments, 2), null, object)
23140        : createWrapper(key, 3, null, null, object);
23141    }
23142
23143    /**
23144     * Creates a function that is the composition of the provided functions,
23145     * where each function consumes the return value of the function that follows.
23146     * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
23147     * Each function is executed with the `this` binding of the composed function.
23148     *
23149     * @static
23150     * @memberOf _
23151     * @category Functions
23152     * @param {...Function} [func] Functions to compose.
23153     * @returns {Function} Returns the new composed function.
23154     * @example
23155     *
23156     * var realNameMap = {
23157     *   'pebbles': 'penelope'
23158     * };
23159     *
23160     * var format = function(name) {
23161     *   name = realNameMap[name.toLowerCase()] || name;
23162     *   return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
23163     * };
23164     *
23165     * var greet = function(formatted) {
23166     *   return 'Hiya ' + formatted + '!';
23167     * };
23168     *
23169     * var welcome = _.compose(greet, format);
23170     * welcome('pebbles');
23171     * // => 'Hiya Penelope!'
23172     */
23173    function compose() {
23174      var funcs = arguments,
23175          length = funcs.length;
23176
23177      while (length--) {
23178        if (!isFunction(funcs[length])) {
23179          throw new TypeError;
23180        }
23181      }
23182      return function() {
23183        var args = arguments,
23184            length = funcs.length;
23185
23186        while (length--) {
23187          args = [funcs[length].apply(this, args)];
23188        }
23189        return args[0];
23190      };
23191    }
23192
23193    /**
23194     * Creates a function which accepts one or more arguments of `func` that when
23195     * invoked either executes `func` returning its result, if all `func` arguments
23196     * have been provided, or returns a function that accepts one or more of the
23197     * remaining `func` arguments, and so on. The arity of `func` can be specified
23198     * if `func.length` is not sufficient.
23199     *
23200     * @static
23201     * @memberOf _
23202     * @category Functions
23203     * @param {Function} func The function to curry.
23204     * @param {number} [arity=func.length] The arity of `func`.
23205     * @returns {Function} Returns the new curried function.
23206     * @example
23207     *
23208     * var curried = _.curry(function(a, b, c) {
23209     *   console.log(a + b + c);
23210     * });
23211     *
23212     * curried(1)(2)(3);
23213     * // => 6
23214     *
23215     * curried(1, 2)(3);
23216     * // => 6
23217     *
23218     * curried(1, 2, 3);
23219     * // => 6
23220     */
23221    function curry(func, arity) {
23222      arity = typeof arity == 'number' ? arity : (+arity || func.length);
23223      return createWrapper(func, 4, null, null, null, arity);
23224    }
23225
23226    /**
23227     * Creates a function that will delay the execution of `func` until after
23228     * `wait` milliseconds have elapsed since the last time it was invoked.
23229     * Provide an options object to indicate that `func` should be invoked on
23230     * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
23231     * to the debounced function will return the result of the last `func` call.
23232     *
23233     * Note: If `leading` and `trailing` options are `true` `func` will be called
23234     * on the trailing edge of the timeout only if the the debounced function is
23235     * invoked more than once during the `wait` timeout.
23236     *
23237     * @static
23238     * @memberOf _
23239     * @category Functions
23240     * @param {Function} func The function to debounce.
23241     * @param {number} wait The number of milliseconds to delay.
23242     * @param {Object} [options] The options object.
23243     * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
23244     * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
23245     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
23246     * @returns {Function} Returns the new debounced function.
23247     * @example
23248     *
23249     * // avoid costly calculations while the window size is in flux
23250     * var lazyLayout = _.debounce(calculateLayout, 150);
23251     * jQuery(window).on('resize', lazyLayout);
23252     *
23253     * // execute `sendMail` when the click event is fired, debouncing subsequent calls
23254     * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
23255     *   'leading': true,
23256     *   'trailing': false
23257     * });
23258     *
23259     * // ensure `batchLog` is executed once after 1 second of debounced calls
23260     * var source = new EventSource('/stream');
23261     * source.addEventListener('message', _.debounce(batchLog, 250, {
23262     *   'maxWait': 1000
23263     * }, false);
23264     */
23265    function debounce(func, wait, options) {
23266      var args,
23267          maxTimeoutId,
23268          result,
23269          stamp,
23270          thisArg,
23271          timeoutId,
23272          trailingCall,
23273          lastCalled = 0,
23274          maxWait = false,
23275          trailing = true;
23276
23277      if (!isFunction(func)) {
23278        throw new TypeError;
23279      }
23280      wait = nativeMax(0, wait) || 0;
23281      if (options === true) {
23282        var leading = true;
23283        trailing = false;
23284      } else if (isObject(options)) {
23285        leading = options.leading;
23286        maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
23287        trailing = 'trailing' in options ? options.trailing : trailing;
23288      }
23289      var delayed = function() {
23290        var remaining = wait - (now() - stamp);
23291        if (remaining <= 0) {
23292          if (maxTimeoutId) {
23293            clearTimeout(maxTimeoutId);
23294          }
23295          var isCalled = trailingCall;
23296          maxTimeoutId = timeoutId = trailingCall = undefined;
23297          if (isCalled) {
23298            lastCalled = now();
23299            result = func.apply(thisArg, args);
23300            if (!timeoutId && !maxTimeoutId) {
23301              args = thisArg = null;
23302            }
23303          }
23304        } else {
23305          timeoutId = setTimeout(delayed, remaining);
23306        }
23307      };
23308
23309      var maxDelayed = function() {
23310        if (timeoutId) {
23311          clearTimeout(timeoutId);
23312        }
23313        maxTimeoutId = timeoutId = trailingCall = undefined;
23314        if (trailing || (maxWait !== wait)) {
23315          lastCalled = now();
23316          result = func.apply(thisArg, args);
23317          if (!timeoutId && !maxTimeoutId) {
23318            args = thisArg = null;
23319          }
23320        }
23321      };
23322
23323      return function() {
23324        args = arguments;
23325        stamp = now();
23326        thisArg = this;
23327        trailingCall = trailing && (timeoutId || !leading);
23328
23329        if (maxWait === false) {
23330          var leadingCall = leading && !timeoutId;
23331        } else {
23332          if (!maxTimeoutId && !leading) {
23333            lastCalled = stamp;
23334          }
23335          var remaining = maxWait - (stamp - lastCalled),
23336              isCalled = remaining <= 0;
23337
23338          if (isCalled) {
23339            if (maxTimeoutId) {
23340              maxTimeoutId = clearTimeout(maxTimeoutId);
23341            }
23342            lastCalled = stamp;
23343            result = func.apply(thisArg, args);
23344          }
23345          else if (!maxTimeoutId) {
23346            maxTimeoutId = setTimeout(maxDelayed, remaining);
23347          }
23348        }
23349        if (isCalled && timeoutId) {
23350          timeoutId = clearTimeout(timeoutId);
23351        }
23352        else if (!timeoutId && wait !== maxWait) {
23353          timeoutId = setTimeout(delayed, wait);
23354        }
23355        if (leadingCall) {
23356          isCalled = true;
23357          result = func.apply(thisArg, args);
23358        }
23359        if (isCalled && !timeoutId && !maxTimeoutId) {
23360          args = thisArg = null;
23361        }
23362        return result;
23363      };
23364    }
23365
23366    /**
23367     * Defers executing the `func` function until the current call stack has cleared.
23368     * Additional arguments will be provided to `func` when it is invoked.
23369     *
23370     * @static
23371     * @memberOf _
23372     * @category Functions
23373     * @param {Function} func The function to defer.
23374     * @param {...*} [arg] Arguments to invoke the function with.
23375     * @returns {number} Returns the timer id.
23376     * @example
23377     *
23378     * _.defer(function(text) { console.log(text); }, 'deferred');
23379     * // logs 'deferred' after one or more milliseconds
23380     */
23381    function defer(func) {
23382      if (!isFunction(func)) {
23383        throw new TypeError;
23384      }
23385      var args = slice(arguments, 1);
23386      return setTimeout(function() { func.apply(undefined, args); }, 1);
23387    }
23388
23389    /**
23390     * Executes the `func` function after `wait` milliseconds. Additional arguments
23391     * will be provided to `func` when it is invoked.
23392     *
23393     * @static
23394     * @memberOf _
23395     * @category Functions
23396     * @param {Function} func The function to delay.
23397     * @param {number} wait The number of milliseconds to delay execution.
23398     * @param {...*} [arg] Arguments to invoke the function with.
23399     * @returns {number} Returns the timer id.
23400     * @example
23401     *
23402     * _.delay(function(text) { console.log(text); }, 1000, 'later');
23403     * // => logs 'later' after one second
23404     */
23405    function delay(func, wait) {
23406      if (!isFunction(func)) {
23407        throw new TypeError;
23408      }
23409      var args = slice(arguments, 2);
23410      return setTimeout(function() { func.apply(undefined, args); }, wait);
23411    }
23412
23413    /**
23414     * Creates a function that memoizes the result of `func`. If `resolver` is
23415     * provided it will be used to determine the cache key for storing the result
23416     * based on the arguments provided to the memoized function. By default, the
23417     * first argument provided to the memoized function is used as the cache key.
23418     * The `func` is executed with the `this` binding of the memoized function.
23419     * The result cache is exposed as the `cache` property on the memoized function.
23420     *
23421     * @static
23422     * @memberOf _
23423     * @category Functions
23424     * @param {Function} func The function to have its output memoized.
23425     * @param {Function} [resolver] A function used to resolve the cache key.
23426     * @returns {Function} Returns the new memoizing function.
23427     * @example
23428     *
23429     * var fibonacci = _.memoize(function(n) {
23430     *   return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
23431     * });
23432     *
23433     * fibonacci(9)
23434     * // => 34
23435     *
23436     * var data = {
23437     *   'fred': { 'name': 'fred', 'age': 40 },
23438     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
23439     * };
23440     *
23441     * // modifying the result cache
23442     * var get = _.memoize(function(name) { return data[name]; }, _.identity);
23443     * get('pebbles');
23444     * // => { 'name': 'pebbles', 'age': 1 }
23445     *
23446     * get.cache.pebbles.name = 'penelope';
23447     * get('pebbles');
23448     * // => { 'name': 'penelope', 'age': 1 }
23449     */
23450    function memoize(func, resolver) {
23451      if (!isFunction(func)) {
23452        throw new TypeError;
23453      }
23454      var memoized = function() {
23455        var cache = memoized.cache,
23456            key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
23457
23458        return hasOwnProperty.call(cache, key)
23459          ? cache[key]
23460          : (cache[key] = func.apply(this, arguments));
23461      }
23462      memoized.cache = {};
23463      return memoized;
23464    }
23465
23466    /**
23467     * Creates a function that is restricted to execute `func` once. Repeat calls to
23468     * the function will return the value of the first call. The `func` is executed
23469     * with the `this` binding of the created function.
23470     *
23471     * @static
23472     * @memberOf _
23473     * @category Functions
23474     * @param {Function} func The function to restrict.
23475     * @returns {Function} Returns the new restricted function.
23476     * @example
23477     *
23478     * var initialize = _.once(createApplication);
23479     * initialize();
23480     * initialize();
23481     * // `initialize` executes `createApplication` once
23482     */
23483    function once(func) {
23484      var ran,
23485          result;
23486
23487      if (!isFunction(func)) {
23488        throw new TypeError;
23489      }
23490      return function() {
23491        if (ran) {
23492          return result;
23493        }
23494        ran = true;
23495        result = func.apply(this, arguments);
23496
23497        // clear the `func` variable so the function may be garbage collected
23498        func = null;
23499        return result;
23500      };
23501    }
23502
23503    /**
23504     * Creates a function that, when called, invokes `func` with any additional
23505     * `partial` arguments prepended to those provided to the new function. This
23506     * method is similar to `_.bind` except it does **not** alter the `this` binding.
23507     *
23508     * @static
23509     * @memberOf _
23510     * @category Functions
23511     * @param {Function} func The function to partially apply arguments to.
23512     * @param {...*} [arg] Arguments to be partially applied.
23513     * @returns {Function} Returns the new partially applied function.
23514     * @example
23515     *
23516     * var greet = function(greeting, name) { return greeting + ' ' + name; };
23517     * var hi = _.partial(greet, 'hi');
23518     * hi('fred');
23519     * // => 'hi fred'
23520     */
23521    function partial(func) {
23522      return createWrapper(func, 16, slice(arguments, 1));
23523    }
23524
23525    /**
23526     * This method is like `_.partial` except that `partial` arguments are
23527     * appended to those provided to the new function.
23528     *
23529     * @static
23530     * @memberOf _
23531     * @category Functions
23532     * @param {Function} func The function to partially apply arguments to.
23533     * @param {...*} [arg] Arguments to be partially applied.
23534     * @returns {Function} Returns the new partially applied function.
23535     * @example
23536     *
23537     * var defaultsDeep = _.partialRight(_.merge, _.defaults);
23538     *
23539     * var options = {
23540     *   'variable': 'data',
23541     *   'imports': { 'jq': $ }
23542     * };
23543     *
23544     * defaultsDeep(options, _.templateSettings);
23545     *
23546     * options.variable
23547     * // => 'data'
23548     *
23549     * options.imports
23550     * // => { '_': _, 'jq': $ }
23551     */
23552    function partialRight(func) {
23553      return createWrapper(func, 32, null, slice(arguments, 1));
23554    }
23555
23556    /**
23557     * Creates a function that, when executed, will only call the `func` function
23558     * at most once per every `wait` milliseconds. Provide an options object to
23559     * indicate that `func` should be invoked on the leading and/or trailing edge
23560     * of the `wait` timeout. Subsequent calls to the throttled function will
23561     * return the result of the last `func` call.
23562     *
23563     * Note: If `leading` and `trailing` options are `true` `func` will be called
23564     * on the trailing edge of the timeout only if the the throttled function is
23565     * invoked more than once during the `wait` timeout.
23566     *
23567     * @static
23568     * @memberOf _
23569     * @category Functions
23570     * @param {Function} func The function to throttle.
23571     * @param {number} wait The number of milliseconds to throttle executions to.
23572     * @param {Object} [options] The options object.
23573     * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
23574     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
23575     * @returns {Function} Returns the new throttled function.
23576     * @example
23577     *
23578     * // avoid excessively updating the position while scrolling
23579     * var throttled = _.throttle(updatePosition, 100);
23580     * jQuery(window).on('scroll', throttled);
23581     *
23582     * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
23583     * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
23584     *   'trailing': false
23585     * }));
23586     */
23587    function throttle(func, wait, options) {
23588      var leading = true,
23589          trailing = true;
23590
23591      if (!isFunction(func)) {
23592        throw new TypeError;
23593      }
23594      if (options === false) {
23595        leading = false;
23596      } else if (isObject(options)) {
23597        leading = 'leading' in options ? options.leading : leading;
23598        trailing = 'trailing' in options ? options.trailing : trailing;
23599      }
23600      debounceOptions.leading = leading;
23601      debounceOptions.maxWait = wait;
23602      debounceOptions.trailing = trailing;
23603
23604      return debounce(func, wait, debounceOptions);
23605    }
23606
23607    /**
23608     * Creates a function that provides `value` to the wrapper function as its
23609     * first argument. Additional arguments provided to the function are appended
23610     * to those provided to the wrapper function. The wrapper is executed with
23611     * the `this` binding of the created function.
23612     *
23613     * @static
23614     * @memberOf _
23615     * @category Functions
23616     * @param {*} value The value to wrap.
23617     * @param {Function} wrapper The wrapper function.
23618     * @returns {Function} Returns the new function.
23619     * @example
23620     *
23621     * var p = _.wrap(_.escape, function(func, text) {
23622     *   return '<p>' + func(text) + '</p>';
23623     * });
23624     *
23625     * p('Fred, Wilma, & Pebbles');
23626     * // => '<p>Fred, Wilma, &amp; Pebbles</p>'
23627     */
23628    function wrap(value, wrapper) {
23629      return createWrapper(wrapper, 16, [value]);
23630    }
23631
23632    /*--------------------------------------------------------------------------*/
23633
23634    /**
23635     * Creates a function that returns `value`.
23636     *
23637     * @static
23638     * @memberOf _
23639     * @category Utilities
23640     * @param {*} value The value to return from the new function.
23641     * @returns {Function} Returns the new function.
23642     * @example
23643     *
23644     * var object = { 'name': 'fred' };
23645     * var getter = _.constant(object);
23646     * getter() === object;
23647     * // => true
23648     */
23649    function constant(value) {
23650      return function() {
23651        return value;
23652      };
23653    }
23654
23655    /**
23656     * Produces a callback bound to an optional `thisArg`. If `func` is a property
23657     * name the created callback will return the property value for a given element.
23658     * If `func` is an object the created callback will return `true` for elements
23659     * that contain the equivalent object properties, otherwise it will return `false`.
23660     *
23661     * @static
23662     * @memberOf _
23663     * @category Utilities
23664     * @param {*} [func=identity] The value to convert to a callback.
23665     * @param {*} [thisArg] The `this` binding of the created callback.
23666     * @param {number} [argCount] The number of arguments the callback accepts.
23667     * @returns {Function} Returns a callback function.
23668     * @example
23669     *
23670     * var characters = [
23671     *   { 'name': 'barney', 'age': 36 },
23672     *   { 'name': 'fred',   'age': 40 }
23673     * ];
23674     *
23675     * // wrap to create custom callback shorthands
23676     * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
23677     *   var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
23678     *   return !match ? func(callback, thisArg) : function(object) {
23679     *     return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
23680     *   };
23681     * });
23682     *
23683     * _.filter(characters, 'age__gt38');
23684     * // => [{ 'name': 'fred', 'age': 40 }]
23685     */
23686    function createCallback(func, thisArg, argCount) {
23687      var type = typeof func;
23688      if (func == null || type == 'function') {
23689        return baseCreateCallback(func, thisArg, argCount);
23690      }
23691      // handle "_.pluck" style callback shorthands
23692      if (type != 'object') {
23693        return property(func);
23694      }
23695      var props = keys(func),
23696          key = props[0],
23697          a = func[key];
23698
23699      // handle "_.where" style callback shorthands
23700      if (props.length == 1 && a === a && !isObject(a)) {
23701        // fast path the common case of providing an object with a single
23702        // property containing a primitive value
23703        return function(object) {
23704          var b = object[key];
23705          return a === b && (a !== 0 || (1 / a == 1 / b));
23706        };
23707      }
23708      return function(object) {
23709        var length = props.length,
23710            result = false;
23711
23712        while (length--) {
23713          if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
23714            break;
23715          }
23716        }
23717        return result;
23718      };
23719    }
23720
23721    /**
23722     * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
23723     * corresponding HTML entities.
23724     *
23725     * @static
23726     * @memberOf _
23727     * @category Utilities
23728     * @param {string} string The string to escape.
23729     * @returns {string} Returns the escaped string.
23730     * @example
23731     *
23732     * _.escape('Fred, Wilma, & Pebbles');
23733     * // => 'Fred, Wilma, &amp; Pebbles'
23734     */
23735    function escape(string) {
23736      return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
23737    }
23738
23739    /**
23740     * This method returns the first argument provided to it.
23741     *
23742     * @static
23743     * @memberOf _
23744     * @category Utilities
23745     * @param {*} value Any value.
23746     * @returns {*} Returns `value`.
23747     * @example
23748     *
23749     * var object = { 'name': 'fred' };
23750     * _.identity(object) === object;
23751     * // => true
23752     */
23753    function identity(value) {
23754      return value;
23755    }
23756
23757    /**
23758     * Adds function properties of a source object to the destination object.
23759     * If `object` is a function methods will be added to its prototype as well.
23760     *
23761     * @static
23762     * @memberOf _
23763     * @category Utilities
23764     * @param {Function|Object} [object=lodash] object The destination object.
23765     * @param {Object} source The object of functions to add.
23766     * @param {Object} [options] The options object.
23767     * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
23768     * @example
23769     *
23770     * function capitalize(string) {
23771     *   return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
23772     * }
23773     *
23774     * _.mixin({ 'capitalize': capitalize });
23775     * _.capitalize('fred');
23776     * // => 'Fred'
23777     *
23778     * _('fred').capitalize().value();
23779     * // => 'Fred'
23780     *
23781     * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
23782     * _('fred').capitalize();
23783     * // => 'Fred'
23784     */
23785    function mixin(object, source, options) {
23786      var chain = true,
23787          methodNames = source && functions(source);
23788
23789      if (!source || (!options && !methodNames.length)) {
23790        if (options == null) {
23791          options = source;
23792        }
23793        ctor = lodashWrapper;
23794        source = object;
23795        object = lodash;
23796        methodNames = functions(source);
23797      }
23798      if (options === false) {
23799        chain = false;
23800      } else if (isObject(options) && 'chain' in options) {
23801        chain = options.chain;
23802      }
23803      var ctor = object,
23804          isFunc = isFunction(ctor);
23805
23806      forEach(methodNames, function(methodName) {
23807        var func = object[methodName] = source[methodName];
23808        if (isFunc) {
23809          ctor.prototype[methodName] = function() {
23810            var chainAll = this.__chain__,
23811                value = this.__wrapped__,
23812                args = [value];
23813
23814            push.apply(args, arguments);
23815            var result = func.apply(object, args);
23816            if (chain || chainAll) {
23817              if (value === result && isObject(result)) {
23818                return this;
23819              }
23820              result = new ctor(result);
23821              result.__chain__ = chainAll;
23822            }
23823            return result;
23824          };
23825        }
23826      });
23827    }
23828
23829    /**
23830     * Reverts the '_' variable to its previous value and returns a reference to
23831     * the `lodash` function.
23832     *
23833     * @static
23834     * @memberOf _
23835     * @category Utilities
23836     * @returns {Function} Returns the `lodash` function.
23837     * @example
23838     *
23839     * var lodash = _.noConflict();
23840     */
23841    function noConflict() {
23842      context._ = oldDash;
23843      return this;
23844    }
23845
23846    /**
23847     * A no-operation function.
23848     *
23849     * @static
23850     * @memberOf _
23851     * @category Utilities
23852     * @example
23853     *
23854     * var object = { 'name': 'fred' };
23855     * _.noop(object) === undefined;
23856     * // => true
23857     */
23858    function noop() {
23859      // no operation performed
23860    }
23861
23862    /**
23863     * Gets the number of milliseconds that have elapsed since the Unix epoch
23864     * (1 January 1970 00:00:00 UTC).
23865     *
23866     * @static
23867     * @memberOf _
23868     * @category Utilities
23869     * @example
23870     *
23871     * var stamp = _.now();
23872     * _.defer(function() { console.log(_.now() - stamp); });
23873     * // => logs the number of milliseconds it took for the deferred function to be called
23874     */
23875    var now = isNative(now = Date.now) && now || function() {
23876      return new Date().getTime();
23877    };
23878
23879    /**
23880     * Converts the given value into an integer of the specified radix.
23881     * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the
23882     * `value` is a hexadecimal, in which case a `radix` of `16` is used.
23883     *
23884     * Note: This method avoids differences in native ES3 and ES5 `parseInt`
23885     * implementations. See http://es5.github.io/#E.
23886     *
23887     * @static
23888     * @memberOf _
23889     * @category Utilities
23890     * @param {string} value The value to parse.
23891     * @param {number} [radix] The radix used to interpret the value to parse.
23892     * @returns {number} Returns the new integer value.
23893     * @example
23894     *
23895     * _.parseInt('08');
23896     * // => 8
23897     */
23898    var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) {
23899      // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`
23900      return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);
23901    };
23902
23903    /**
23904     * Creates a "_.pluck" style function, which returns the `key` value of a
23905     * given object.
23906     *
23907     * @static
23908     * @memberOf _
23909     * @category Utilities
23910     * @param {string} key The name of the property to retrieve.
23911     * @returns {Function} Returns the new function.
23912     * @example
23913     *
23914     * var characters = [
23915     *   { 'name': 'fred',   'age': 40 },
23916     *   { 'name': 'barney', 'age': 36 }
23917     * ];
23918     *
23919     * var getName = _.property('name');
23920     *
23921     * _.map(characters, getName);
23922     * // => ['barney', 'fred']
23923     *
23924     * _.sortBy(characters, getName);
23925     * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred',   'age': 40 }]
23926     */
23927    function property(key) {
23928      return function(object) {
23929        return object[key];
23930      };
23931    }
23932
23933    /**
23934     * Produces a random number between `min` and `max` (inclusive). If only one
23935     * argument is provided a number between `0` and the given number will be
23936     * returned. If `floating` is truey or either `min` or `max` are floats a
23937     * floating-point number will be returned instead of an integer.
23938     *
23939     * @static
23940     * @memberOf _
23941     * @category Utilities
23942     * @param {number} [min=0] The minimum possible value.
23943     * @param {number} [max=1] The maximum possible value.
23944     * @param {boolean} [floating=false] Specify returning a floating-point number.
23945     * @returns {number} Returns a random number.
23946     * @example
23947     *
23948     * _.random(0, 5);
23949     * // => an integer between 0 and 5
23950     *
23951     * _.random(5);
23952     * // => also an integer between 0 and 5
23953     *
23954     * _.random(5, true);
23955     * // => a floating-point number between 0 and 5
23956     *
23957     * _.random(1.2, 5.2);
23958     * // => a floating-point number between 1.2 and 5.2
23959     */
23960    function random(min, max, floating) {
23961      var noMin = min == null,
23962          noMax = max == null;
23963
23964      if (floating == null) {
23965        if (typeof min == 'boolean' && noMax) {
23966          floating = min;
23967          min = 1;
23968        }
23969        else if (!noMax && typeof max == 'boolean') {
23970          floating = max;
23971          noMax = true;
23972        }
23973      }
23974      if (noMin && noMax) {
23975        max = 1;
23976      }
23977      min = +min || 0;
23978      if (noMax) {
23979        max = min;
23980        min = 0;
23981      } else {
23982        max = +max || 0;
23983      }
23984      if (floating || min % 1 || max % 1) {
23985        var rand = nativeRandom();
23986        return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max);
23987      }
23988      return baseRandom(min, max);
23989    }
23990
23991    /**
23992     * Resolves the value of property `key` on `object`. If `key` is a function
23993     * it will be invoked with the `this` binding of `object` and its result returned,
23994     * else the property value is returned. If `object` is falsey then `undefined`
23995     * is returned.
23996     *
23997     * @static
23998     * @memberOf _
23999     * @category Utilities
24000     * @param {Object} object The object to inspect.
24001     * @param {string} key The name of the property to resolve.
24002     * @returns {*} Returns the resolved value.
24003     * @example
24004     *
24005     * var object = {
24006     *   'cheese': 'crumpets',
24007     *   'stuff': function() {
24008     *     return 'nonsense';
24009     *   }
24010     * };
24011     *
24012     * _.result(object, 'cheese');
24013     * // => 'crumpets'
24014     *
24015     * _.result(object, 'stuff');
24016     * // => 'nonsense'
24017     */
24018    function result(object, key) {
24019      if (object) {
24020        var value = object[key];
24021        return isFunction(value) ? object[key]() : value;
24022      }
24023    }
24024
24025    /**
24026     * A micro-templating method that handles arbitrary delimiters, preserves
24027     * whitespace, and correctly escapes quotes within interpolated code.
24028     *
24029     * Note: In the development build, `_.template` utilizes sourceURLs for easier
24030     * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
24031     *
24032     * For more information on precompiling templates see:
24033     * http://lodash.com/custom-builds
24034     *
24035     * For more information on Chrome extension sandboxes see:
24036     * http://developer.chrome.com/stable/extensions/sandboxingEval.html
24037     *
24038     * @static
24039     * @memberOf _
24040     * @category Utilities
24041     * @param {string} text The template text.
24042     * @param {Object} data The data object used to populate the text.
24043     * @param {Object} [options] The options object.
24044     * @param {RegExp} [options.escape] The "escape" delimiter.
24045     * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
24046     * @param {Object} [options.imports] An object to import into the template as local variables.
24047     * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
24048     * @param {string} [sourceURL] The sourceURL of the template's compiled source.
24049     * @param {string} [variable] The data object variable name.
24050     * @returns {Function|string} Returns a compiled function when no `data` object
24051     *  is given, else it returns the interpolated text.
24052     * @example
24053     *
24054     * // using the "interpolate" delimiter to create a compiled template
24055     * var compiled = _.template('hello <%= name %>');
24056     * compiled({ 'name': 'fred' });
24057     * // => 'hello fred'
24058     *
24059     * // using the "escape" delimiter to escape HTML in data property values
24060     * _.template('<b><%- value %></b>', { 'value': '<script>' });
24061     * // => '<b>&lt;script&gt;</b>'
24062     *
24063     * // using the "evaluate" delimiter to generate HTML
24064     * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';
24065     * _.template(list, { 'people': ['fred', 'barney'] });
24066     * // => '<li>fred</li><li>barney</li>'
24067     *
24068     * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter
24069     * _.template('hello ${ name }', { 'name': 'pebbles' });
24070     * // => 'hello pebbles'
24071     *
24072     * // using the internal `print` function in "evaluate" delimiters
24073     * _.template('<% print("hello " + name); %>!', { 'name': 'barney' });
24074     * // => 'hello barney!'
24075     *
24076     * // using a custom template delimiters
24077     * _.templateSettings = {
24078     *   'interpolate': /{{([\s\S]+?)}}/g
24079     * };
24080     *
24081     * _.template('hello {{ name }}!', { 'name': 'mustache' });
24082     * // => 'hello mustache!'
24083     *
24084     * // using the `imports` option to import jQuery
24085     * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';
24086     * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });
24087     * // => '<li>fred</li><li>barney</li>'
24088     *
24089     * // using the `sourceURL` option to specify a custom sourceURL for the template
24090     * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });
24091     * compiled(data);
24092     * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
24093     *
24094     * // using the `variable` option to ensure a with-statement isn't used in the compiled template
24095     * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });
24096     * compiled.source;
24097     * // => function(data) {
24098     *   var __t, __p = '', __e = _.escape;
24099     *   __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';
24100     *   return __p;
24101     * }
24102     *
24103     * // using the `source` property to inline compiled templates for meaningful
24104     * // line numbers in error messages and a stack trace
24105     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
24106     *   var JST = {\
24107     *     "main": ' + _.template(mainText).source + '\
24108     *   };\
24109     * ');
24110     */
24111    function template(text, data, options) {
24112      // based on John Resig's `tmpl` implementation
24113      // http://ejohn.org/blog/javascript-micro-templating/
24114      // and Laura Doktorova's doT.js
24115      // https://github.com/olado/doT
24116      var settings = lodash.templateSettings;
24117      text = String(text || '');
24118
24119      // avoid missing dependencies when `iteratorTemplate` is not defined
24120      options = defaults({}, options, settings);
24121
24122      var imports = defaults({}, options.imports, settings.imports),
24123          importsKeys = keys(imports),
24124          importsValues = values(imports);
24125
24126      var isEvaluating,
24127          index = 0,
24128          interpolate = options.interpolate || reNoMatch,
24129          source = "__p += '";
24130
24131      // compile the regexp to match each delimiter
24132      var reDelimiters = RegExp(
24133        (options.escape || reNoMatch).source + '|' +
24134        interpolate.source + '|' +
24135        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
24136        (options.evaluate || reNoMatch).source + '|$'
24137      , 'g');
24138
24139      text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
24140        interpolateValue || (interpolateValue = esTemplateValue);
24141
24142        // escape characters that cannot be included in string literals
24143        source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);
24144
24145        // replace delimiters with snippets
24146        if (escapeValue) {
24147          source += "' +\n__e(" + escapeValue + ") +\n'";
24148        }
24149        if (evaluateValue) {
24150          isEvaluating = true;
24151          source += "';\n" + evaluateValue + ";\n__p += '";
24152        }
24153        if (interpolateValue) {
24154          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
24155        }
24156        index = offset + match.length;
24157
24158        // the JS engine embedded in Adobe products requires returning the `match`
24159        // string in order to produce the correct `offset` value
24160        return match;
24161      });
24162
24163      source += "';\n";
24164
24165      // if `variable` is not specified, wrap a with-statement around the generated
24166      // code to add the data object to the top of the scope chain
24167      var variable = options.variable,
24168          hasVariable = variable;
24169
24170      if (!hasVariable) {
24171        variable = 'obj';
24172        source = 'with (' + variable + ') {\n' + source + '\n}\n';
24173      }
24174      // cleanup code by stripping empty strings
24175      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
24176        .replace(reEmptyStringMiddle, '$1')
24177        .replace(reEmptyStringTrailing, '$1;');
24178
24179      // frame code as the function body
24180      source = 'function(' + variable + ') {\n' +
24181        (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') +
24182        "var __t, __p = '', __e = _.escape" +
24183        (isEvaluating
24184          ? ', __j = Array.prototype.join;\n' +
24185            "function print() { __p += __j.call(arguments, '') }\n"
24186          : ';\n'
24187        ) +
24188        source +
24189        'return __p\n}';
24190
24191      // Use a sourceURL for easier debugging.
24192      // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
24193      var sourceURL = '\n/*\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\n*/';
24194
24195      try {
24196        var result = Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);
24197      } catch(e) {
24198        e.source = source;
24199        throw e;
24200      }
24201      if (data) {
24202        return result(data);
24203      }
24204      // provide the compiled function's source by its `toString` method, in
24205      // supported environments, or the `source` property as a convenience for
24206      // inlining compiled templates during the build process
24207      result.source = source;
24208      return result;
24209    }
24210
24211    /**
24212     * Executes the callback `n` times, returning an array of the results
24213     * of each callback execution. The callback is bound to `thisArg` and invoked
24214     * with one argument; (index).
24215     *
24216     * @static
24217     * @memberOf _
24218     * @category Utilities
24219     * @param {number} n The number of times to execute the callback.
24220     * @param {Function} callback The function called per iteration.
24221     * @param {*} [thisArg] The `this` binding of `callback`.
24222     * @returns {Array} Returns an array of the results of each `callback` execution.
24223     * @example
24224     *
24225     * var diceRolls = _.times(3, _.partial(_.random, 1, 6));
24226     * // => [3, 6, 4]
24227     *
24228     * _.times(3, function(n) { mage.castSpell(n); });
24229     * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively
24230     *
24231     * _.times(3, function(n) { this.cast(n); }, mage);
24232     * // => also calls `mage.castSpell(n)` three times
24233     */
24234    function times(n, callback, thisArg) {
24235      n = (n = +n) > -1 ? n : 0;
24236      var index = -1,
24237          result = Array(n);
24238
24239      callback = baseCreateCallback(callback, thisArg, 1);
24240      while (++index < n) {
24241        result[index] = callback(index);
24242      }
24243      return result;
24244    }
24245
24246    /**
24247     * The inverse of `_.escape` this method converts the HTML entities
24248     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to their
24249     * corresponding characters.
24250     *
24251     * @static
24252     * @memberOf _
24253     * @category Utilities
24254     * @param {string} string The string to unescape.
24255     * @returns {string} Returns the unescaped string.
24256     * @example
24257     *
24258     * _.unescape('Fred, Barney &amp; Pebbles');
24259     * // => 'Fred, Barney & Pebbles'
24260     */
24261    function unescape(string) {
24262      return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
24263    }
24264
24265    /**
24266     * Generates a unique ID. If `prefix` is provided the ID will be appended to it.
24267     *
24268     * @static
24269     * @memberOf _
24270     * @category Utilities
24271     * @param {string} [prefix] The value to prefix the ID with.
24272     * @returns {string} Returns the unique ID.
24273     * @example
24274     *
24275     * _.uniqueId('contact_');
24276     * // => 'contact_104'
24277     *
24278     * _.uniqueId();
24279     * // => '105'
24280     */
24281    function uniqueId(prefix) {
24282      var id = ++idCounter;
24283      return String(prefix == null ? '' : prefix) + id;
24284    }
24285
24286    /*--------------------------------------------------------------------------*/
24287
24288    /**
24289     * Creates a `lodash` object that wraps the given value with explicit
24290     * method chaining enabled.
24291     *
24292     * @static
24293     * @memberOf _
24294     * @category Chaining
24295     * @param {*} value The value to wrap.
24296     * @returns {Object} Returns the wrapper object.
24297     * @example
24298     *
24299     * var characters = [
24300     *   { 'name': 'barney',  'age': 36 },
24301     *   { 'name': 'fred',    'age': 40 },
24302     *   { 'name': 'pebbles', 'age': 1 }
24303     * ];
24304     *
24305     * var youngest = _.chain(characters)
24306     *     .sortBy('age')
24307     *     .map(function(chr) { return chr.name + ' is ' + chr.age; })
24308     *     .first()
24309     *     .value();
24310     * // => 'pebbles is 1'
24311     */
24312    function chain(value) {
24313      value = new lodashWrapper(value);
24314      value.__chain__ = true;
24315      return value;
24316    }
24317
24318    /**
24319     * Invokes `interceptor` with the `value` as the first argument and then
24320     * returns `value`. The purpose of this method is to "tap into" a method
24321     * chain in order to perform operations on intermediate results within
24322     * the chain.
24323     *
24324     * @static
24325     * @memberOf _
24326     * @category Chaining
24327     * @param {*} value The value to provide to `interceptor`.
24328     * @param {Function} interceptor The function to invoke.
24329     * @returns {*} Returns `value`.
24330     * @example
24331     *
24332     * _([1, 2, 3, 4])
24333     *  .tap(function(array) { array.pop(); })
24334     *  .reverse()
24335     *  .value();
24336     * // => [3, 2, 1]
24337     */
24338    function tap(value, interceptor) {
24339      interceptor(value);
24340      return value;
24341    }
24342
24343    /**
24344     * Enables explicit method chaining on the wrapper object.
24345     *
24346     * @name chain
24347     * @memberOf _
24348     * @category Chaining
24349     * @returns {*} Returns the wrapper object.
24350     * @example
24351     *
24352     * var characters = [
24353     *   { 'name': 'barney', 'age': 36 },
24354     *   { 'name': 'fred',   'age': 40 }
24355     * ];
24356     *
24357     * // without explicit chaining
24358     * _(characters).first();
24359     * // => { 'name': 'barney', 'age': 36 }
24360     *
24361     * // with explicit chaining
24362     * _(characters).chain()
24363     *   .first()
24364     *   .pick('age')
24365     *   .value();
24366     * // => { 'age': 36 }
24367     */
24368    function wrapperChain() {
24369      this.__chain__ = true;
24370      return this;
24371    }
24372
24373    /**
24374     * Produces the `toString` result of the wrapped value.
24375     *
24376     * @name toString
24377     * @memberOf _
24378     * @category Chaining
24379     * @returns {string} Returns the string result.
24380     * @example
24381     *
24382     * _([1, 2, 3]).toString();
24383     * // => '1,2,3'
24384     */
24385    function wrapperToString() {
24386      return String(this.__wrapped__);
24387    }
24388
24389    /**
24390     * Extracts the wrapped value.
24391     *
24392     * @name valueOf
24393     * @memberOf _
24394     * @alias value
24395     * @category Chaining
24396     * @returns {*} Returns the wrapped value.
24397     * @example
24398     *
24399     * _([1, 2, 3]).valueOf();
24400     * // => [1, 2, 3]
24401     */
24402    function wrapperValueOf() {
24403      return this.__wrapped__;
24404    }
24405
24406    /*--------------------------------------------------------------------------*/
24407
24408    // add functions that return wrapped values when chaining
24409    lodash.after = after;
24410    lodash.assign = assign;
24411    lodash.at = at;
24412    lodash.bind = bind;
24413    lodash.bindAll = bindAll;
24414    lodash.bindKey = bindKey;
24415    lodash.chain = chain;
24416    lodash.compact = compact;
24417    lodash.compose = compose;
24418    lodash.constant = constant;
24419    lodash.countBy = countBy;
24420    lodash.create = create;
24421    lodash.createCallback = createCallback;
24422    lodash.curry = curry;
24423    lodash.debounce = debounce;
24424    lodash.defaults = defaults;
24425    lodash.defer = defer;
24426    lodash.delay = delay;
24427    lodash.difference = difference;
24428    lodash.filter = filter;
24429    lodash.flatten = flatten;
24430    lodash.forEach = forEach;
24431    lodash.forEachRight = forEachRight;
24432    lodash.forIn = forIn;
24433    lodash.forInRight = forInRight;
24434    lodash.forOwn = forOwn;
24435    lodash.forOwnRight = forOwnRight;
24436    lodash.functions = functions;
24437    lodash.groupBy = groupBy;
24438    lodash.indexBy = indexBy;
24439    lodash.initial = initial;
24440    lodash.intersection = intersection;
24441    lodash.invert = invert;
24442    lodash.invoke = invoke;
24443    lodash.keys = keys;
24444    lodash.map = map;
24445    lodash.mapValues = mapValues;
24446    lodash.max = max;
24447    lodash.memoize = memoize;
24448    lodash.merge = merge;
24449    lodash.min = min;
24450    lodash.omit = omit;
24451    lodash.once = once;
24452    lodash.pairs = pairs;
24453    lodash.partial = partial;
24454    lodash.partialRight = partialRight;
24455    lodash.pick = pick;
24456    lodash.pluck = pluck;
24457    lodash.property = property;
24458    lodash.pull = pull;
24459    lodash.range = range;
24460    lodash.reject = reject;
24461    lodash.remove = remove;
24462    lodash.rest = rest;
24463    lodash.shuffle = shuffle;
24464    lodash.sortBy = sortBy;
24465    lodash.tap = tap;
24466    lodash.throttle = throttle;
24467    lodash.times = times;
24468    lodash.toArray = toArray;
24469    lodash.transform = transform;
24470    lodash.union = union;
24471    lodash.uniq = uniq;
24472    lodash.values = values;
24473    lodash.where = where;
24474    lodash.without = without;
24475    lodash.wrap = wrap;
24476    lodash.xor = xor;
24477    lodash.zip = zip;
24478    lodash.zipObject = zipObject;
24479
24480    // add aliases
24481    lodash.collect = map;
24482    lodash.drop = rest;
24483    lodash.each = forEach;
24484    lodash.eachRight = forEachRight;
24485    lodash.extend = assign;
24486    lodash.methods = functions;
24487    lodash.object = zipObject;
24488    lodash.select = filter;
24489    lodash.tail = rest;
24490    lodash.unique = uniq;
24491    lodash.unzip = zip;
24492
24493    // add functions to `lodash.prototype`
24494    mixin(lodash);
24495
24496    /*--------------------------------------------------------------------------*/
24497
24498    // add functions that return unwrapped values when chaining
24499    lodash.clone = clone;
24500    lodash.cloneDeep = cloneDeep;
24501    lodash.contains = contains;
24502    lodash.escape = escape;
24503    lodash.every = every;
24504    lodash.find = find;
24505    lodash.findIndex = findIndex;
24506    lodash.findKey = findKey;
24507    lodash.findLast = findLast;
24508    lodash.findLastIndex = findLastIndex;
24509    lodash.findLastKey = findLastKey;
24510    lodash.has = has;
24511    lodash.identity = identity;
24512    lodash.indexOf = indexOf;
24513    lodash.isArguments = isArguments;
24514    lodash.isArray = isArray;
24515    lodash.isBoolean = isBoolean;
24516    lodash.isDate = isDate;
24517    lodash.isElement = isElement;
24518    lodash.isEmpty = isEmpty;
24519    lodash.isEqual = isEqual;
24520    lodash.isFinite = isFinite;
24521    lodash.isFunction = isFunction;
24522    lodash.isNaN = isNaN;
24523    lodash.isNull = isNull;
24524    lodash.isNumber = isNumber;
24525    lodash.isObject = isObject;
24526    lodash.isPlainObject = isPlainObject;
24527    lodash.isRegExp = isRegExp;
24528    lodash.isString = isString;
24529    lodash.isUndefined = isUndefined;
24530    lodash.lastIndexOf = lastIndexOf;
24531    lodash.mixin = mixin;
24532    lodash.noConflict = noConflict;
24533    lodash.noop = noop;
24534    lodash.now = now;
24535    lodash.parseInt = parseInt;
24536    lodash.random = random;
24537    lodash.reduce = reduce;
24538    lodash.reduceRight = reduceRight;
24539    lodash.result = result;
24540    lodash.runInContext = runInContext;
24541    lodash.size = size;
24542    lodash.some = some;
24543    lodash.sortedIndex = sortedIndex;
24544    lodash.template = template;
24545    lodash.unescape = unescape;
24546    lodash.uniqueId = uniqueId;
24547
24548    // add aliases
24549    lodash.all = every;
24550    lodash.any = some;
24551    lodash.detect = find;
24552    lodash.findWhere = find;
24553    lodash.foldl = reduce;
24554    lodash.foldr = reduceRight;
24555    lodash.include = contains;
24556    lodash.inject = reduce;
24557
24558    mixin(function() {
24559      var source = {}
24560      forOwn(lodash, function(func, methodName) {
24561        if (!lodash.prototype[methodName]) {
24562          source[methodName] = func;
24563        }
24564      });
24565      return source;
24566    }(), false);
24567
24568    /*--------------------------------------------------------------------------*/
24569
24570    // add functions capable of returning wrapped and unwrapped values when chaining
24571    lodash.first = first;
24572    lodash.last = last;
24573    lodash.sample = sample;
24574
24575    // add aliases
24576    lodash.take = first;
24577    lodash.head = first;
24578
24579    forOwn(lodash, function(func, methodName) {
24580      var callbackable = methodName !== 'sample';
24581      if (!lodash.prototype[methodName]) {
24582        lodash.prototype[methodName]= function(n, guard) {
24583          var chainAll = this.__chain__,
24584              result = func(this.__wrapped__, n, guard);
24585
24586          return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function')))
24587            ? result
24588            : new lodashWrapper(result, chainAll);
24589        };
24590      }
24591    });
24592
24593    /*--------------------------------------------------------------------------*/
24594
24595    /**
24596     * The semantic version number.
24597     *
24598     * @static
24599     * @memberOf _
24600     * @type string
24601     */
24602    lodash.VERSION = '2.4.1';
24603
24604    // add "Chaining" functions to the wrapper
24605    lodash.prototype.chain = wrapperChain;
24606    lodash.prototype.toString = wrapperToString;
24607    lodash.prototype.value = wrapperValueOf;
24608    lodash.prototype.valueOf = wrapperValueOf;
24609
24610    // add `Array` functions that return unwrapped values
24611    forEach(['join', 'pop', 'shift'], function(methodName) {
24612      var func = arrayRef[methodName];
24613      lodash.prototype[methodName] = function() {
24614        var chainAll = this.__chain__,
24615            result = func.apply(this.__wrapped__, arguments);
24616
24617        return chainAll
24618          ? new lodashWrapper(result, chainAll)
24619          : result;
24620      };
24621    });
24622
24623    // add `Array` functions that return the existing wrapped value
24624    forEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) {
24625      var func = arrayRef[methodName];
24626      lodash.prototype[methodName] = function() {
24627        func.apply(this.__wrapped__, arguments);
24628        return this;
24629      };
24630    });
24631
24632    // add `Array` functions that return new wrapped values
24633    forEach(['concat', 'slice', 'splice'], function(methodName) {
24634      var func = arrayRef[methodName];
24635      lodash.prototype[methodName] = function() {
24636        return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__);
24637      };
24638    });
24639
24640    return lodash;
24641  }
24642
24643  /*--------------------------------------------------------------------------*/
24644
24645  // expose Lo-Dash
24646  var _ = runInContext();
24647
24648  // some AMD build optimizers like r.js check for condition patterns like the following:
24649  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
24650    // Expose Lo-Dash to the global object even when an AMD loader is present in
24651    // case Lo-Dash is loaded with a RequireJS shim config.
24652    // See http://requirejs.org/docs/api.html#config-shim
24653    root._ = _;
24654
24655    // define as an anonymous module so, through path mapping, it can be
24656    // referenced as the "underscore" module
24657    define(function() {
24658      return _;
24659    });
24660  }
24661  // check for `exports` after `define` in case a build optimizer adds an `exports` object
24662  else if (freeExports && freeModule) {
24663    // in Node.js or RingoJS
24664    if (moduleExports) {
24665      (freeModule.exports = _)._ = _;
24666    }
24667    // in Narwhal or Rhino -require
24668    else {
24669      freeExports._ = _;
24670    }
24671  }
24672  else {
24673    // in a browser or Rhino
24674    root._ = _;
24675  }
24676}.call(this));
24677/*!
24678Chosen, a Select Box Enhancer for jQuery and Prototype
24679by Patrick Filler for Harvest, http://getharvest.com
24680
24681Version 1.6.2
24682Full source at https://github.com/harvesthq/chosen
24683Copyright (c) 2011-2016 Harvest http://getharvest.com
24684
24685MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
24686This file is generated by `grunt build`, do not edit it by hand.
24687*/
24688
24689
24690(function() {
24691  var $, AbstractChosen, Chosen, SelectParser, _ref,
24692    __hasProp = {}.hasOwnProperty,
24693    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
24694
24695  SelectParser = (function() {
24696    function SelectParser() {
24697      this.options_index = 0;
24698      this.parsed = [];
24699    }
24700
24701    SelectParser.prototype.add_node = function(child) {
24702      if (child.nodeName.toUpperCase() === "OPTGROUP") {
24703        return this.add_group(child);
24704      } else {
24705        return this.add_option(child);
24706      }
24707    };
24708
24709    SelectParser.prototype.add_group = function(group) {
24710      var group_position, option, _i, _len, _ref, _results;
24711      group_position = this.parsed.length;
24712      this.parsed.push({
24713        array_index: group_position,
24714        group: true,
24715        label: this.escapeExpression(group.label),
24716        title: group.title ? group.title : void 0,
24717        children: 0,
24718        disabled: group.disabled,
24719        classes: group.className
24720      });
24721      _ref = group.childNodes;
24722      _results = [];
24723      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
24724        option = _ref[_i];
24725        _results.push(this.add_option(option, group_position, group.disabled));
24726      }
24727      return _results;
24728    };
24729
24730    SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
24731      if (option.nodeName.toUpperCase() === "OPTION") {
24732        if (option.text !== "") {
24733          if (group_position != null) {
24734            this.parsed[group_position].children += 1;
24735          }
24736          this.parsed.push({
24737            array_index: this.parsed.length,
24738            options_index: this.options_index,
24739            value: option.value,
24740            text: option.text,
24741            html: option.innerHTML,
24742            title: option.title ? option.title : void 0,
24743            selected: option.selected,
24744            disabled: group_disabled === true ? group_disabled : option.disabled,
24745            group_array_index: group_position,
24746            group_label: group_position != null ? this.parsed[group_position].label : null,
24747            classes: option.className,
24748            style: option.style.cssText
24749          });
24750        } else {
24751          this.parsed.push({
24752            array_index: this.parsed.length,
24753            options_index: this.options_index,
24754            empty: true
24755          });
24756        }
24757        return this.options_index += 1;
24758      }
24759    };
24760
24761    SelectParser.prototype.escapeExpression = function(text) {
24762      var map, unsafe_chars;
24763      if ((text == null) || text === false) {
24764        return "";
24765      }
24766      if (!/[\&\<\>\"\'\`]/.test(text)) {
24767        return text;
24768      }
24769      map = {
24770        "<": "&lt;",
24771        ">": "&gt;",
24772        '"': "&quot;",
24773        "'": "&#x27;",
24774        "`": "&#x60;"
24775      };
24776      unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
24777      return text.replace(unsafe_chars, function(chr) {
24778        return map[chr] || "&amp;";
24779      });
24780    };
24781
24782    return SelectParser;
24783
24784  })();
24785
24786  SelectParser.select_to_array = function(select) {
24787    var child, parser, _i, _len, _ref;
24788    parser = new SelectParser();
24789    _ref = select.childNodes;
24790    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
24791      child = _ref[_i];
24792      parser.add_node(child);
24793    }
24794    return parser.parsed;
24795  };
24796
24797  AbstractChosen = (function() {
24798    function AbstractChosen(form_field, options) {
24799      this.form_field = form_field;
24800      this.options = options != null ? options : {};
24801      if (!AbstractChosen.browser_is_supported()) {
24802        return;
24803      }
24804      this.is_multiple = this.form_field.multiple;
24805      this.set_default_text();
24806      this.set_default_values();
24807      this.setup();
24808      this.set_up_html();
24809      this.register_observers();
24810      this.on_ready();
24811    }
24812
24813    AbstractChosen.prototype.set_default_values = function() {
24814      var _this = this;
24815      this.click_test_action = function(evt) {
24816        return _this.test_active_click(evt);
24817      };
24818      this.activate_action = function(evt) {
24819        return _this.activate_field(evt);
24820      };
24821      this.active_field = false;
vendor: 8,018 bytes, lines 24822-25035
24822      this.mouse_on_container = false;
24823      this.results_showing = false;
24824      this.result_highlighted = null;
24825      this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
24826      this.disable_search_threshold = this.options.disable_search_threshold || 0;
24827      this.disable_search = this.options.disable_search || false;
24828      this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
24829      this.group_search = this.options.group_search != null ? this.options.group_search : true;
24830      this.search_contains = this.options.search_contains || false;
24831      this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
24832      this.max_selected_options = this.options.max_selected_options || Infinity;
24833      this.inherit_select_classes = this.options.inherit_select_classes || false;
24834      this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
24835      this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
24836      this.include_group_label_in_selected = this.options.include_group_label_in_selected || false;
24837      this.max_shown_results = this.options.max_shown_results || Number.POSITIVE_INFINITY;
24838      return this.case_sensitive_search = this.options.case_sensitive_search || false;
24839    };
24840
24841    AbstractChosen.prototype.set_default_text = function() {
24842      if (this.form_field.getAttribute("data-placeholder")) {
24843        this.default_text = this.form_field.getAttribute("data-placeholder");
24844      } else if (this.is_multiple) {
24845        this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
24846      } else {
24847        this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
24848      }
24849      return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text;
24850    };
24851
24852    AbstractChosen.prototype.choice_label = function(item) {
24853      if (this.include_group_label_in_selected && (item.group_label != null)) {
24854        return "<b class='group-name'>" + item.group_label + "</b>" + item.html;
24855      } else {
24856        return item.html;
24857      }
24858    };
24859
24860    AbstractChosen.prototype.mouse_enter = function() {
24861      return this.mouse_on_container = true;
24862    };
24863
24864    AbstractChosen.prototype.mouse_leave = function() {
24865      return this.mouse_on_container = false;
24866    };
24867
24868    AbstractChosen.prototype.input_focus = function(evt) {
24869      var _this = this;
24870      if (this.is_multiple) {
24871        if (!this.active_field) {
24872          return setTimeout((function() {
24873            return _this.container_mousedown();
24874          }), 50);
24875        }
24876      } else {
24877        if (!this.active_field) {
24878          return this.activate_field();
24879        }
24880      }
24881    };
24882
24883    AbstractChosen.prototype.input_blur = function(evt) {
24884      var _this = this;
24885      if (!this.mouse_on_container) {
24886        this.active_field = false;
24887        return setTimeout((function() {
24888          return _this.blur_test();
24889        }), 100);
24890      }
24891    };
24892
24893    AbstractChosen.prototype.results_option_build = function(options) {
24894      var content, data, data_content, shown_results, _i, _len, _ref;
24895      content = '';
24896      shown_results = 0;
24897      _ref = this.results_data;
24898      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
24899        data = _ref[_i];
24900        data_content = '';
24901        if (data.group) {
24902          data_content = this.result_add_group(data);
24903        } else {
24904          data_content = this.result_add_option(data);
24905        }
24906        if (data_content !== '') {
24907          shown_results++;
24908          content += data_content;
24909        }
24910        if (options != null ? options.first : void 0) {
24911          if (data.selected && this.is_multiple) {
24912            this.choice_build(data);
24913          } else if (data.selected && !this.is_multiple) {
24914            this.single_set_selected_text(this.choice_label(data));
24915          }
24916        }
24917        if (shown_results >= this.max_shown_results) {
24918          break;
24919        }
24920      }
24921      return content;
24922    };
24923
24924    AbstractChosen.prototype.result_add_option = function(option) {
24925      var classes, option_el;
24926      if (!option.search_match) {
24927        return '';
24928      }
24929      if (!this.include_option_in_results(option)) {
24930        return '';
24931      }
24932      classes = [];
24933      if (!option.disabled && !(option.selected && this.is_multiple)) {
24934        classes.push("active-result");
24935      }
24936      if (option.disabled && !(option.selected && this.is_multiple)) {
24937        classes.push("disabled-result");
24938      }
24939      if (option.selected) {
24940        classes.push("result-selected");
24941      }
24942      if (option.group_array_index != null) {
24943        classes.push("group-option");
24944      }
24945      if (option.classes !== "") {
24946        classes.push(option.classes);
24947      }
24948      option_el = document.createElement("li");
24949      option_el.className = classes.join(" ");
24950      option_el.style.cssText = option.style;
24951      option_el.setAttribute("data-option-array-index", option.array_index);
24952      option_el.innerHTML = option.search_text;
24953      if (option.title) {
24954        option_el.title = option.title;
24955      }
24956      return this.outerHTML(option_el);
24957    };
24958
24959    AbstractChosen.prototype.result_add_group = function(group) {
24960      var classes, group_el;
24961      if (!(group.search_match || group.group_match)) {
24962        return '';
24963      }
24964      if (!(group.active_options > 0)) {
24965        return '';
24966      }
24967      classes = [];
24968      classes.push("group-result");
24969      if (group.classes) {
24970        classes.push(group.classes);
24971      }
24972      group_el = document.createElement("li");
24973      group_el.className = classes.join(" ");
24974      group_el.innerHTML = group.search_text;
24975      if (group.title) {
24976        group_el.title = group.title;
24977      }
24978      return this.outerHTML(group_el);
24979    };
24980
24981    AbstractChosen.prototype.results_update_field = function() {
24982      this.set_default_text();
24983      if (!this.is_multiple) {
24984        this.results_reset_cleanup();
24985      }
24986      this.result_clear_highlight();
24987      this.results_build();
24988      if (this.results_showing) {
24989        return this.winnow_results();
24990      }
24991    };
24992
24993    AbstractChosen.prototype.reset_single_select_options = function() {
24994      var result, _i, _len, _ref, _results;
24995      _ref = this.results_data;
24996      _results = [];
24997      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
24998        result = _ref[_i];
24999        if (result.selected) {
25000          _results.push(result.selected = false);
25001        } else {
25002          _results.push(void 0);
25003        }
25004      }
25005      return _results;
25006    };
25007
25008    AbstractChosen.prototype.results_toggle = function() {
25009      if (this.results_showing) {
25010        return this.results_hide();
25011      } else {
25012        return this.results_show();
25013      }
25014    };
25015
25016    AbstractChosen.prototype.results_search = function(evt) {
25017      if (this.results_showing) {
25018        return this.winnow_results();
25019      } else {
25020        return this.results_show();
25021      }
25022    };
25023
25024    AbstractChosen.prototype.winnow_results = function() {
25025      var escapedSearchText, option, regex, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref;
25026      this.no_results_clear();
25027      results = 0;
25028      searchText = this.get_search_text();
25029      escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
25030      zregex = new RegExp(escapedSearchText, 'i');
25031      regex = this.get_search_regex(escapedSearchText);
25032      _ref = this.results_data;
25033      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
25034        option = _ref[_i];
25035        option.search_match = false;
25036        results_group = null;
25037        if (this.include_option_in_results(option)) {
25038          if (option.group) {
25039            option.group_match = false;
25040            option.active_options = 0;
25041          }
25042          if ((option.group_array_index != null) && this.results_data[option.group_array_index]) {
25043            results_group = this.results_data[option.group_array_index];
25044            if (results_group.active_options === 0 && results_group.search_match) {
25045              results += 1;
25046            }
25047            results_group.active_options += 1;
25048          }
25049          option.search_text = option.group ? option.label : option.html;
25050          if (!(option.group && !this.group_search)) {
25051            option.search_match = this.search_string_match(option.search_text, regex);
25052            if (option.search_match && !option.group) {
25053              results += 1;
25054            }
25055            if (option.search_match) {
25056              if (searchText.length) {
25057                startpos = option.search_text.search(zregex);
25058                text = option.search_text.substr(0, startpos + searchText.length) + '</em>' + option.search_text.substr(startpos + searchText.length);
25059                option.search_text = text.substr(0, startpos) + '<em>' + text.substr(startpos);
25060              }
25061              if (results_group != null) {
25062                results_group.group_match = true;
25063              }
25064            } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) {
25065              option.search_match = true;
25066            }
25067          }
25068        }
25069      }
25070      this.result_clear_highlight();
25071      if (results < 1 && searchText.length) {
25072        this.update_results_content("");
25073        return this.no_results(searchText);
25074      } else {
25075        this.update_results_content(this.results_option_build());
25076        return this.winnow_results_set_highlight();
25077      }
25078    };
25079
25080    AbstractChosen.prototype.get_search_regex = function(escaped_search_string) {
25081      var regex_anchor, regex_flag;
25082      regex_anchor = this.search_contains ? "" : "^";
25083      regex_flag = this.case_sensitive_search ? "" : "i";
25084      return new RegExp(regex_anchor + escaped_search_string, regex_flag);
25085    };
25086
25087    AbstractChosen.prototype.search_string_match = function(search_string, regex) {
25088      var part, parts, _i, _len;
25089      if (regex.test(search_string)) {
25090        return true;
25091      } else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) {
25092        parts = search_string.replace(/\[|\]/g, "").split(" ");
25093        if (parts.length) {
25094          for (_i = 0, _len = parts.length; _i < _len; _i++) {
25095            part = parts[_i];
25096            if (regex.test(part)) {
25097              return true;
25098            }
25099          }
25100        }
25101      }
25102    };
25103
25104    AbstractChosen.prototype.choices_count = function() {
25105      var option, _i, _len, _ref;
25106      if (this.selected_option_count != null) {
25107        return this.selected_option_count;
25108      }
25109      this.selected_option_count = 0;
25110      _ref = this.form_field.options;
25111      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
25112        option = _ref[_i];
25113        if (option.selected) {
25114          this.selected_option_count += 1;
25115        }
25116      }
25117      return this.selected_option_count;
25118    };
25119
25120    AbstractChosen.prototype.choices_click = function(evt) {
25121      evt.preventDefault();
25122      if (!(this.results_showing || this.is_disabled)) {
25123        return this.results_show();
25124      }
25125    };
25126
25127    AbstractChosen.prototype.keyup_checker = function(evt) {
25128      var stroke, _ref;
25129      stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
25130      this.search_field_scale();
25131      switch (stroke) {
25132        case 8:
25133          if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) {
25134            return this.keydown_backstroke();
25135          } else if (!this.pending_backstroke) {
25136            this.result_clear_highlight();
25137            return this.results_search();
25138          }
25139          break;
25140        case 13:
25141          evt.preventDefault();
25142          if (this.results_showing) {
25143            return this.result_select(evt);
25144          }
25145          break;
25146        case 27:
25147          if (this.results_showing) {
25148            this.results_hide();
25149          }
25150          return true;
25151        case 9:
25152        case 38:
25153        case 40:
25154        case 16:
25155        case 91:
25156        case 17:
25157        case 18:
25158          break;
25159        default:
25160          return this.results_search();
25161      }
25162    };
25163
25164    AbstractChosen.prototype.clipboard_event_checker = function(evt) {
25165      var _this = this;
25166      return setTimeout((function() {
25167        return _this.results_search();
25168      }), 50);
25169    };
25170
25171    AbstractChosen.prototype.container_width = function() {
25172      if (this.options.width != null) {
25173        return this.options.width;
25174      } else {
25175        return "" + this.form_field.offsetWidth + "px";
25176      }
25177    };
25178
25179    AbstractChosen.prototype.include_option_in_results = function(option) {
25180      if (this.is_multiple && (!this.display_selected_options && option.selected)) {
25181        return false;
25182      }
25183      if (!this.display_disabled_options && option.disabled) {
25184        return false;
25185      }
25186      if (option.empty) {
25187        return false;
25188      }
25189      return true;
25190    };
25191
25192    AbstractChosen.prototype.search_results_touchstart = function(evt) {
25193      this.touch_started = true;
25194      return this.search_results_mouseover(evt);
25195    };
25196
25197    AbstractChosen.prototype.search_results_touchmove = function(evt) {
25198      this.touch_started = false;
25199      return this.search_results_mouseout(evt);
25200    };
25201
25202    AbstractChosen.prototype.search_results_touchend = function(evt) {
25203      if (this.touch_started) {
25204        return this.search_results_mouseup(evt);
25205      }
25206    };
25207
25208    AbstractChosen.prototype.outerHTML = function(element) {
25209      var tmp;
25210      if (element.outerHTML) {
25211        return element.outerHTML;
25212      }
25213      tmp = document.createElement("div");
25214      tmp.appendChild(element);
25215      return tmp.innerHTML;
25216    };
25217
25218    AbstractChosen.browser_is_supported = function() {
25219      if ("Microsoft Internet Explorer" === window.navigator.appName) {
25220        return document.documentMode >= 8;
25221      }
25222      return true;
25223    };
25224
25225    AbstractChosen.default_multiple_text = "Select Some Options";
25226
25227    AbstractChosen.default_single_text = "Select an Option";
25228
25229    AbstractChosen.default_no_result_text = "No results match";
25230
25231    return AbstractChosen;
25232
25233  })();
25234
25235  $ = jQuery;
25236
25237  $.fn.extend({
25238    chosen: function(options) {
25239      if (!AbstractChosen.browser_is_supported()) {
25240        return this;
25241      }
25242      return this.each(function(input_field) {
25243        var $this, chosen;
25244        $this = $(this);
25245        chosen = $this.data('chosen');
25246        if (options === 'destroy') {
25247          if (chosen instanceof Chosen) {
25248            chosen.destroy();
25249          }
25250          return;
25251        }
25252        if (!(chosen instanceof Chosen)) {
25253          $this.data('chosen', new Chosen(this, options));
25254        }
25255      });
25256    }
25257  });
25258
25259  Chosen = (function(_super) {
25260    __extends(Chosen, _super);
25261
25262    function Chosen() {
25263      _ref = Chosen.__super__.constructor.apply(this, arguments);
25264      return _ref;
25265    }
25266
25267    Chosen.prototype.setup = function() {
25268      this.form_field_jq = $(this.form_field);
25269      this.current_selectedIndex = this.form_field.selectedIndex;
25270      return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl");
25271    };
25272
25273    Chosen.prototype.set_up_html = function() {
25274      var container_classes, container_props;
25275      container_classes = ["chosen-container"];
25276      container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
25277      if (this.inherit_select_classes && this.form_field.className) {
25278        container_classes.push(this.form_field.className);
25279      }
25280      if (this.is_rtl) {
25281        container_classes.push("chosen-rtl");
25282      }
25283      container_props = {
25284        'class': container_classes.join(' '),
25285        'style': "width: " + (this.container_width()) + ";",
25286        'title': this.form_field.title
25287      };
25288      if (this.form_field.id.length) {
25289        container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
25290      }
25291      this.container = $("<div />", container_props);
25292      if (this.is_multiple) {
25293        this.container.html('<ul class="chosen-choices"><li class="search-field"><input type="text" value="' + this.default_text + '" class="default" autocomplete="off" style="width:25px;" /></li></ul><div class="chosen-drop"><ul class="chosen-results"></ul></div>');
25294      } else {
25295        this.container.html('<a class="chosen-single chosen-default"><span>' + this.default_text + '</span><div><b></b></div></a><div class="chosen-drop"><div class="chosen-search"><input type="text" autocomplete="off" /></div><ul class="chosen-results"></ul></div>');
25296      }
25297      this.form_field_jq.hide().after(this.container);
25298      this.dropdown = this.container.find('div.chosen-drop').first();
25299      this.search_field = this.container.find('input').first();
25300      this.search_results = this.container.find('ul.chosen-results').first();
25301      this.search_field_scale();
25302      this.search_no_results = this.container.find('li.no-results').first();
25303      if (this.is_multiple) {
25304        this.search_choices = this.container.find('ul.chosen-choices').first();
25305        this.search_container = this.container.find('li.search-field').first();
25306      } else {
25307        this.search_container = this.container.find('div.chosen-search').first();
25308        this.selected_item = this.container.find('.chosen-single').first();
25309      }
25310      this.results_build();
25311      this.set_tab_index();
25312      return this.set_label_behavior();
25313    };
25314
25315    Chosen.prototype.on_ready = function() {
25316      return this.form_field_jq.trigger("chosen:ready", {
25317        chosen: this
25318      });
25319    };
25320
25321    Chosen.prototype.register_observers = function() {
25322      var _this = this;
25323      this.container.bind('touchstart.chosen', function(evt) {
25324        _this.container_mousedown(evt);
25325        return evt.preventDefault();
25326      });
25327      this.container.bind('touchend.chosen', function(evt) {
25328        _this.container_mouseup(evt);
25329        return evt.preventDefault();
25330      });
25331      this.container.bind('mousedown.chosen', function(evt) {
25332        _this.container_mousedown(evt);
25333      });
25334      this.container.bind('mouseup.chosen', function(evt) {
25335        _this.container_mouseup(evt);
25336      });
25337      this.container.bind('mouseenter.chosen', function(evt) {
25338        _this.mouse_enter(evt);
25339      });
25340      this.container.bind('mouseleave.chosen', function(evt) {
25341        _this.mouse_leave(evt);
25342      });
25343      this.search_results.bind('mouseup.chosen', function(evt) {
25344        _this.search_results_mouseup(evt);
25345      });
25346      this.search_results.bind('mouseover.chosen', function(evt) {
25347        _this.search_results_mouseover(evt);
25348      });
25349      this.search_results.bind('mouseout.chosen', function(evt) {
25350        _this.search_results_mouseout(evt);
25351      });
25352      this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) {
25353        _this.search_results_mousewheel(evt);
25354      });
25355      this.search_results.bind('touchstart.chosen', function(evt) {
25356        _this.search_results_touchstart(evt);
25357      });
25358      this.search_results.bind('touchmove.chosen', function(evt) {
25359        _this.search_results_touchmove(evt);
25360      });
25361      this.search_results.bind('touchend.chosen', function(evt) {
25362        _this.search_results_touchend(evt);
25363      });
25364      this.form_field_jq.bind("chosen:updated.chosen", function(evt) {
25365        _this.results_update_field(evt);
25366      });
25367      this.form_field_jq.bind("chosen:activate.chosen", function(evt) {
25368        _this.activate_field(evt);
25369      });
25370      this.form_field_jq.bind("chosen:open.chosen", function(evt) {
25371        _this.container_mousedown(evt);
25372      });
25373      this.form_field_jq.bind("chosen:close.chosen", function(evt) {
25374        _this.input_blur(evt);
25375      });
25376      this.search_field.bind('blur.chosen', function(evt) {
25377        _this.input_blur(evt);
25378      });
25379      this.search_field.bind('keyup.chosen', function(evt) {
25380        _this.keyup_checker(evt);
25381      });
25382      this.search_field.bind('keydown.chosen', function(evt) {
25383        _this.keydown_checker(evt);
25384      });
25385      this.search_field.bind('focus.chosen', function(evt) {
25386        _this.input_focus(evt);
25387      });
25388      this.search_field.bind('cut.chosen', function(evt) {
25389        _this.clipboard_event_checker(evt);
25390      });
25391      this.search_field.bind('paste.chosen', function(evt) {
25392        _this.clipboard_event_checker(evt);
25393      });
25394      if (this.is_multiple) {
25395        return this.search_choices.bind('click.chosen', function(evt) {
25396          _this.choices_click(evt);
25397        });
25398      } else {
25399        return this.container.bind('click.chosen', function(evt) {
25400          evt.preventDefault();
25401        });
25402      }
25403    };
25404
25405    Chosen.prototype.destroy = function() {
25406      $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
25407      if (this.search_field[0].tabIndex) {
25408        this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex;
25409      }
25410      this.container.remove();
25411      this.form_field_jq.removeData('chosen');
25412      return this.form_field_jq.show();
25413    };
25414
25415    Chosen.prototype.search_field_disabled = function() {
25416      this.is_disabled = this.form_field_jq[0].disabled;
25417      if (this.is_disabled) {
25418        this.container.addClass('chosen-disabled');
25419        this.search_field[0].disabled = true;
25420        if (!this.is_multiple) {
25421          this.selected_item.unbind("focus.chosen", this.activate_action);
25422        }
25423        return this.close_field();
25424      } else {
25425        this.container.removeClass('chosen-disabled');
25426        this.search_field[0].disabled = false;
vendor: 5,123 bytes, lines 25427-25563
25427        if (!this.is_multiple) {
25428          return this.selected_item.bind("focus.chosen", this.activate_action);
25429        }
25430      }
25431    };
25432
25433    Chosen.prototype.container_mousedown = function(evt) {
25434      if (!this.is_disabled) {
25435        if (evt && evt.type === "mousedown" && !this.results_showing) {
25436          evt.preventDefault();
25437        }
25438        if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) {
25439          if (!this.active_field) {
25440            if (this.is_multiple) {
25441              this.search_field.val("");
25442            }
25443            $(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action);
25444            this.results_show();
25445          } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) {
25446            evt.preventDefault();
25447            this.results_toggle();
25448          }
25449          return this.activate_field();
25450        }
25451      }
25452    };
25453
25454    Chosen.prototype.container_mouseup = function(evt) {
25455      if (evt.target.nodeName === "ABBR" && !this.is_disabled) {
25456        return this.results_reset(evt);
25457      }
25458    };
25459
25460    Chosen.prototype.search_results_mousewheel = function(evt) {
25461      var delta;
25462      if (evt.originalEvent) {
25463        delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
25464      }
25465      if (delta != null) {
25466        evt.preventDefault();
25467        if (evt.type === 'DOMMouseScroll') {
25468          delta = delta * 40;
25469        }
25470        return this.search_results.scrollTop(delta + this.search_results.scrollTop());
25471      }
25472    };
25473
25474    Chosen.prototype.blur_test = function(evt) {
25475      if (!this.active_field && this.container.hasClass("chosen-container-active")) {
25476        return this.close_field();
25477      }
25478    };
25479
25480    Chosen.prototype.close_field = function() {
25481      $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
25482      this.active_field = false;
25483      this.results_hide();
25484      this.container.removeClass("chosen-container-active");
25485      this.clear_backstroke();
25486      this.show_search_field_default();
25487      return this.search_field_scale();
25488    };
25489
25490    Chosen.prototype.activate_field = function() {
25491      this.container.addClass("chosen-container-active");
25492      this.active_field = true;
25493      this.search_field.val(this.search_field.val());
25494      return this.search_field.focus();
25495    };
25496
25497    Chosen.prototype.test_active_click = function(evt) {
25498      var active_container;
25499      active_container = $(evt.target).closest('.chosen-container');
25500      if (active_container.length && this.container[0] === active_container[0]) {
25501        return this.active_field = true;
25502      } else {
25503        return this.close_field();
25504      }
25505    };
25506
25507    Chosen.prototype.results_build = function() {
25508      this.parsing = true;
25509      this.selected_option_count = null;
25510      this.results_data = SelectParser.select_to_array(this.form_field);
25511      if (this.is_multiple) {
25512        this.search_choices.find("li.search-choice").remove();
25513      } else if (!this.is_multiple) {
25514        this.single_set_selected_text();
25515        if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
25516          this.search_field[0].readOnly = true;
25517          this.container.addClass("chosen-container-single-nosearch");
25518        } else {
25519          this.search_field[0].readOnly = false;
25520          this.container.removeClass("chosen-container-single-nosearch");
25521        }
25522      }
25523      this.update_results_content(this.results_option_build({
25524        first: true
25525      }));
25526      this.search_field_disabled();
25527      this.show_search_field_default();
25528      this.search_field_scale();
25529      return this.parsing = false;
25530    };
25531
25532    Chosen.prototype.result_do_highlight = function(el) {
25533      var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
25534      if (el.length) {
25535        this.result_clear_highlight();
25536        this.result_highlight = el;
25537        this.result_highlight.addClass("highlighted");
25538        maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
25539        visible_top = this.search_results.scrollTop();
25540        visible_bottom = maxHeight + visible_top;
25541        high_top = this.result_highlight.position().top + this.search_results.scrollTop();
25542        high_bottom = high_top + this.result_highlight.outerHeight();
25543        if (high_bottom >= visible_bottom) {
25544          return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
25545        } else if (high_top < visible_top) {
25546          return this.search_results.scrollTop(high_top);
25547        }
25548      }
25549    };
25550
25551    Chosen.prototype.result_clear_highlight = function() {
25552      if (this.result_highlight) {
25553        this.result_highlight.removeClass("highlighted");
25554      }
25555      return this.result_highlight = null;
25556    };
25557
25558    Chosen.prototype.results_show = function() {
25559      if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
25560        this.form_field_jq.trigger("chosen:maxselected", {
25561          chosen: this
25562        });
25563        return false;
25564      }
25565      this.container.addClass("chosen-with-drop");
25566      this.results_showing = true;
25567      this.search_field.focus();
25568      this.search_field.val(this.search_field.val());
25569      this.winnow_results();
25570      return this.form_field_jq.trigger("chosen:showing_dropdown", {
25571        chosen: this
25572      });
25573    };
25574
25575    Chosen.prototype.update_results_content = function(content) {
25576      return this.search_results.html(content);
25577    };
25578
25579    Chosen.prototype.results_hide = function() {
25580      if (this.results_showing) {
25581        this.result_clear_highlight();
25582        this.container.removeClass("chosen-with-drop");
25583        this.form_field_jq.trigger("chosen:hiding_dropdown", {
25584          chosen: this
25585        });
25586      }
25587      return this.results_showing = false;
25588    };
25589
25590    Chosen.prototype.set_tab_index = function(el) {
25591      var ti;
25592      if (this.form_field.tabIndex) {
25593        ti = this.form_field.tabIndex;
25594        this.form_field.tabIndex = -1;
25595        return this.search_field[0].tabIndex = ti;
25596      }
25597    };
25598
25599    Chosen.prototype.set_label_behavior = function() {
25600      var _this = this;
25601      this.form_field_label = this.form_field_jq.parents("label");
25602      if (!this.form_field_label.length && this.form_field.id.length) {
25603        this.form_field_label = $("label[for='" + this.form_field.id + "']");
25604      }
25605      if (this.form_field_label.length > 0) {
25606        return this.form_field_label.bind('click.chosen', function(evt) {
25607          if (_this.is_multiple) {
25608            return _this.container_mousedown(evt);
25609          } else {
25610            return _this.activate_field();
25611          }
25612        });
25613      }
25614    };
25615
25616    Chosen.prototype.show_search_field_default = function() {
25617      if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
25618        this.search_field.val(this.default_text);
25619        return this.search_field.addClass("default");
25620      } else {
25621        this.search_field.val("");
25622        return this.search_field.removeClass("default");
25623      }
25624    };
25625
25626    Chosen.prototype.search_results_mouseup = function(evt) {
25627      var target;
25628      target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
25629      if (target.length) {
25630        this.result_highlight = target;
25631        this.result_select(evt);
25632        return this.search_field.focus();
25633      }
25634    };
25635
25636    Chosen.prototype.search_results_mouseover = function(evt) {
25637      var target;
25638      target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
25639      if (target) {
25640        return this.result_do_highlight(target);
25641      }
25642    };
25643
25644    Chosen.prototype.search_results_mouseout = function(evt) {
25645      if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) {
25646        return this.result_clear_highlight();
25647      }
25648    };
25649
25650    Chosen.prototype.choice_build = function(item) {
25651      var choice, close_link,
25652        _this = this;
25653      choice = $('<li />', {
25654        "class": "search-choice"
25655      }).html("<span>" + (this.choice_label(item)) + "</span>");
25656      if (item.disabled) {
25657        choice.addClass('search-choice-disabled');
25658      } else {
25659        close_link = $('<a />', {
25660          "class": 'search-choice-close',
25661          'data-option-array-index': item.array_index
25662        });
25663        close_link.bind('click.chosen', function(evt) {
25664          return _this.choice_destroy_link_click(evt);
25665        });
25666        choice.append(close_link);
25667      }
25668      return this.search_container.before(choice);
25669    };
25670
25671    Chosen.prototype.choice_destroy_link_click = function(evt) {
25672      evt.preventDefault();
25673      evt.stopPropagation();
25674      if (!this.is_disabled) {
25675        return this.choice_destroy($(evt.target));
25676      }
25677    };
25678
25679    Chosen.prototype.choice_destroy = function(link) {
25680      if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) {
25681        this.show_search_field_default();
25682        if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) {
25683          this.results_hide();
25684        }
25685        link.parents('li').first().remove();
25686        return this.search_field_scale();
25687      }
25688    };
25689
25690    Chosen.prototype.results_reset = function() {
25691      this.reset_single_select_options();
25692      this.form_field.options[0].selected = true;
25693      this.single_set_selected_text();
25694      this.show_search_field_default();
25695      this.results_reset_cleanup();
25696      this.form_field_jq.trigger("change");
25697      if (this.active_field) {
25698        return this.results_hide();
25699      }
25700    };
25701
25702    Chosen.prototype.results_reset_cleanup = function() {
25703      this.current_selectedIndex = this.form_field.selectedIndex;
25704      return this.selected_item.find("abbr").remove();
25705    };
25706
25707    Chosen.prototype.result_select = function(evt) {
25708      var high, item;
25709      if (this.result_highlight) {
25710        high = this.result_highlight;
25711        this.result_clear_highlight();
25712        if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
25713          this.form_field_jq.trigger("chosen:maxselected", {
25714            chosen: this
25715          });
25716          return false;
vendor: 6,155 bytes, lines 25717-25888
25717        }
25718        if (this.is_multiple) {
25719          high.removeClass("active-result");
25720        } else {
25721          this.reset_single_select_options();
25722        }
25723        high.addClass("result-selected");
25724        item = this.results_data[high[0].getAttribute("data-option-array-index")];
25725        item.selected = true;
25726        this.form_field.options[item.options_index].selected = true;
25727        this.selected_option_count = null;
25728        if (this.is_multiple) {
25729          this.choice_build(item);
25730        } else {
25731          this.single_set_selected_text(this.choice_label(item));
25732        }
25733        if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) {
25734          this.results_hide();
25735        }
25736        this.show_search_field_default();
25737        if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) {
25738          this.form_field_jq.trigger("change", {
25739            'selected': this.form_field.options[item.options_index].value
25740          });
25741        }
25742        this.current_selectedIndex = this.form_field.selectedIndex;
25743        evt.preventDefault();
25744        return this.search_field_scale();
25745      }
25746    };
25747
25748    Chosen.prototype.single_set_selected_text = function(text) {
25749      if (text == null) {
25750        text = this.default_text;
25751      }
25752      if (text === this.default_text) {
25753        this.selected_item.addClass("chosen-default");
25754      } else {
25755        this.single_deselect_control_build();
25756        this.selected_item.removeClass("chosen-default");
25757      }
25758      return this.selected_item.find("span").html(text);
25759    };
25760
25761    Chosen.prototype.result_deselect = function(pos) {
25762      var result_data;
25763      result_data = this.results_data[pos];
25764      if (!this.form_field.options[result_data.options_index].disabled) {
25765        result_data.selected = false;
25766        this.form_field.options[result_data.options_index].selected = false;
25767        this.selected_option_count = null;
25768        this.result_clear_highlight();
25769        if (this.results_showing) {
25770          this.winnow_results();
25771        }
25772        this.form_field_jq.trigger("change", {
25773          deselected: this.form_field.options[result_data.options_index].value
25774        });
25775        this.search_field_scale();
25776        return true;
25777      } else {
25778        return false;
25779      }
25780    };
25781
25782    Chosen.prototype.single_deselect_control_build = function() {
25783      if (!this.allow_single_deselect) {
25784        return;
25785      }
25786      if (!this.selected_item.find("abbr").length) {
25787        this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
25788      }
25789      return this.selected_item.addClass("chosen-single-with-deselect");
25790    };
25791
25792    Chosen.prototype.get_search_text = function() {
25793      return $('<div/>').text($.trim(this.search_field.val())).html();
25794    };
25795
25796    Chosen.prototype.winnow_results_set_highlight = function() {
25797      var do_high, selected_results;
25798      selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
25799      do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
25800      if (do_high != null) {
25801        return this.result_do_highlight(do_high);
25802      }
25803    };
25804
25805    Chosen.prototype.no_results = function(terms) {
25806      var no_results_html;
25807      no_results_html = $('<li class="no-results">' + this.results_none_found + ' "<span></span>"</li>');
25808      no_results_html.find("span").first().html(terms);
25809      this.search_results.append(no_results_html);
25810      return this.form_field_jq.trigger("chosen:no_results", {
25811        chosen: this
25812      });
25813    };
25814
25815    Chosen.prototype.no_results_clear = function() {
25816      return this.search_results.find(".no-results").remove();
25817    };
25818
25819    Chosen.prototype.keydown_arrow = function() {
25820      var next_sib;
25821      if (this.results_showing && this.result_highlight) {
25822        next_sib = this.result_highlight.nextAll("li.active-result").first();
25823        if (next_sib) {
25824          return this.result_do_highlight(next_sib);
25825        }
25826      } else {
25827        return this.results_show();
25828      }
25829    };
25830
25831    Chosen.prototype.keyup_arrow = function() {
25832      var prev_sibs;
25833      if (!this.results_showing && !this.is_multiple) {
25834        return this.results_show();
25835      } else if (this.result_highlight) {
25836        prev_sibs = this.result_highlight.prevAll("li.active-result");
25837        if (prev_sibs.length) {
25838          return this.result_do_highlight(prev_sibs.first());
25839        } else {
25840          if (this.choices_count() > 0) {
25841            this.results_hide();
25842          }
25843          return this.result_clear_highlight();
25844        }
25845      }
25846    };
25847
25848    Chosen.prototype.keydown_backstroke = function() {
25849      var next_available_destroy;
25850      if (this.pending_backstroke) {
25851        this.choice_destroy(this.pending_backstroke.find("a").first());
25852        return this.clear_backstroke();
25853      } else {
25854        next_available_destroy = this.search_container.siblings("li.search-choice").last();
25855        if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) {
25856          this.pending_backstroke = next_available_destroy;
25857          if (this.single_backstroke_delete) {
25858            return this.keydown_backstroke();
25859          } else {
25860            return this.pending_backstroke.addClass("search-choice-focus");
25861          }
25862        }
25863      }
25864    };
25865
25866    Chosen.prototype.clear_backstroke = function() {
25867      if (this.pending_backstroke) {
25868        this.pending_backstroke.removeClass("search-choice-focus");
25869      }
25870      return this.pending_backstroke = null;
25871    };
25872
25873    Chosen.prototype.keydown_checker = function(evt) {
25874      var stroke, _ref1;
25875      stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
25876      this.search_field_scale();
25877      if (stroke !== 8 && this.pending_backstroke) {
25878        this.clear_backstroke();
25879      }
25880      switch (stroke) {
25881        case 8:
25882          this.backstroke_length = this.search_field.val().length;
25883          break;
25884        case 9:
25885          if (this.results_showing && !this.is_multiple) {
25886            this.result_select(evt);
25887          }
25888          this.mouse_on_container = false;
25889          break;
25890        case 13:
25891          if (this.results_showing) {
25892            evt.preventDefault();
25893          }
25894          break;
25895        case 32:
25896          if (this.disable_search) {
25897            evt.preventDefault();
25898          }
25899          break;
25900        case 38:
25901          evt.preventDefault();
25902          this.keyup_arrow();
25903          break;
25904        case 40:
25905          evt.preventDefault();
25906          this.keydown_arrow();
25907          break;
25908      }
25909    };
25910
25911    Chosen.prototype.search_field_scale = function() {
25912      var div, f_width, h, style, style_block, styles, w, _i, _len;
25913      if (this.is_multiple) {
25914        h = 0;
25915        w = 0;
25916        style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
25917        styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
25918        for (_i = 0, _len = styles.length; _i < _len; _i++) {
25919          style = styles[_i];
25920          style_block += style + ":" + this.search_field.css(style) + ";";
25921        }
25922        div = $('<div />', {
25923          'style': style_block
25924        });
25925        div.text(this.search_field.val());
25926        $('body').append(div);
25927        w = div.width() + 25;
25928        div.remove();
25929        f_width = this.container.outerWidth();
25930        if (w > f_width - 10) {
25931          w = f_width - 10;
25932        }
25933        return this.search_field.css({
25934          'width': w + 'px'
25935        });
25936      }
25937    };
25938
25939    return Chosen;
25940
25941  })(AbstractChosen);
25942
25943}).call(this);
25944/*
25945
25946 scrollup v2.3.2
25947 Author: Mark Goodyear - http://markgoodyear.com
25948 Git: https://github.com/markgoodyear/scrollup
25949
25950 Copyright 2014 Mark Goodyear.
25951 Licensed under the MIT license
25952 http://www.opensource.org/licenses/mit-license.php
25953
25954 Twitter: @markgdyr
25955
25956 */
25957
25958(function($, window, document) {
25959
25960    // Main function
25961    $.fn.scrollUp = function (options) {
25962
25963        // Ensure that only one scrollUp exists
25964        if (!$.data(document.body, 'scrollUp')) {
25965            $.data(document.body, 'scrollUp', true);
25966            $.fn.scrollUp.init(options);
25967        }
25968    };
25969
25970    // Init
25971    $.fn.scrollUp.init = function(options) {
25972
25973        // Apply any options to the settings, override the defaults
25974        var o = $.fn.scrollUp.settings = $.extend({}, $.fn.scrollUp.defaults, options),
25975
25976        // Create element
25977		$self;
25978		if (o.scrollTrigger) {
25979			$self = $(o.scrollTrigger);
25980		} else {
25981	        $self = $('<a/>', {
25982	            id: o.scrollName,
25983	            href: '#top'
25984	        });
25985		}
25986
25987        // Set scrollTitle if there is one
25988        if (o.scrollTitle) {
25989            $self.attr('title', o.scrollTitle);
25990        }
25991
25992        $self.appendTo('body');
25993
25994        // If not using an image display text
25995        if (!(o.scrollImg || o.scrollTrigger)) {
25996            $self.html(o.scrollText);
25997        }
25998
25999        // Minimum CSS to make the magic happen
26000        $self.css({
26001            display: 'none',
26002            position: 'fixed',
26003            zIndex: o.zIndex
26004        });
26005
26006        // Active point overlay
26007        if (o.activeOverlay) {
26008            $('<div/>', { id: o.scrollName + '-active' }).css({ position: 'absolute', 'top': o.scrollDistance + 'px', width: '100%', borderTop: '1px dotted' + o.activeOverlay, zIndex: o.zIndex }).appendTo('body');
26009        }
26010
26011        // Switch animation type
26012        var animIn, animOut, animSpeed, scrollDis;
26013
26014        switch (o.animation) {
26015            case 'fade':
26016                animIn  = 'fadeIn';
26017                animOut = 'fadeOut';
26018                animSpeed = o.animationInSpeed;
26019                break;
26020            case 'slide':
26021                animIn  = 'slideDown';
26022                animOut = 'slideUp';
26023                animSpeed = o.animationInSpeed;
26024                break;
26025            default:
26026                animIn  = 'show';
26027                animOut = 'hide';
26028                animSpeed = 0;
26029        }
26030
26031        // If from top or bottom
26032        if (o.scrollFrom === 'top') {
26033            scrollDis = o.scrollDistance;
26034        } else {
26035            scrollDis = $(document).height() - $(window).height() - o.scrollDistance;
26036        }
26037
26038        // Trigger visible false by default
26039        var triggerVisible = false;
26040
26041        // Scroll function
26042        scrollEvent = $(window).scroll(function() {
26043            if ( $(window).scrollTop() > scrollDis ) {
26044                if (!triggerVisible) {
26045                    $self[animIn](animSpeed);
26046                    triggerVisible = true;
26047                }
26048            } else {
26049                if (triggerVisible) {
26050                    $self[animOut](animSpeed);
26051                    triggerVisible = false;
26052                }
26053            }
26054        });
26055
26056        var scrollTarget;
26057        if (o.scrollTarget) {
26058            if (typeof o.scrollTarget === 'number') {
26059                scrollTarget = o.scrollTarget;
26060            } else if (typeof o.scrollTarget === 'string') {
26061                scrollTarget = Math.floor($(o.scrollTarget).offset().top);
26062            }
26063        } else {
26064            scrollTarget = 0;
26065        }
26066
26067        // To the top
26068        $self.click(function(e) {
26069            e.preventDefault();
26070
26071            $('html, body').animate({
26072                scrollTop: scrollTarget
26073            }, o.scrollSpeed, o.easingType);
26074        });
26075    };
26076
26077    // Defaults
26078    $.fn.scrollUp.defaults = {
26079        scrollName: 'scrollUp', // Element ID
26080        scrollDistance: 300, // Distance from top/bottom before showing element (px)
26081        scrollFrom: 'top', // 'top' or 'bottom'
26082        scrollSpeed: 300, // Speed back to top (ms)
26083        easingType: 'linear', // Scroll to top easing (see http://easings.net/)
26084        animation: 'fade', // Fade, slide, none
26085        animationInSpeed: 200, // Animation in speed (ms)
26086        animationOutSpeed: 200, // Animation out speed (ms)
26087        scrollTrigger: false, // Set a custom triggering element. Can be an HTML string or jQuery object
26088        scrollTarget: false, // Set a custom target element for scrolling to. Can be element or number
26089        scrollText: 'Scroll to top', // Text for element, can contain HTML
26090        scrollTitle: false, // Set a custom <a> title if required. Defaults to scrollText
26091        scrollImg: false, // Set true to use image
26092        activeOverlay: false, // Set CSS color to display scrollUp active point, e.g '#00FFFF'
26093        zIndex: 2147483647 // Z-Index for the overlay
26094    };
26095
26096    // Destroy scrollUp plugin and clean all modifications to the DOM
26097    $.fn.scrollUp.destroy = function (scrollEvent){
26098        $.removeData( document.body, 'scrollUp' );
26099        $( '#' + $.fn.scrollUp.settings.scrollName ).remove();
26100        $( '#' + $.fn.scrollUp.settings.scrollName + '-active' ).remove();
26101
26102        // If 1.7 or above use the new .off()
26103        if ($.fn.jquery.split('.')[1] >= 7) {
26104            $(window).off( 'scroll', scrollEvent );
26105
26106        // Else use the old .unbind()
26107        } else {
26108            $(window).unbind( 'scroll', scrollEvent );
26109        }
26110    };
26111
26112    $.scrollUp = $.fn.scrollUp;
26113
26114})(jQuery, window, document);
26115// Sticky Plugin v1.0.0 for jQuery
26116// =============
26117// Author: Anthony Garand
26118// Improvements by German M. Bravo (Kronuz) and Ruud Kamphuis (ruudk)
26119// Improvements by Leonardo C. Daronco (daronco)
26120// Created: 2/14/2011
26121// Date: 2/12/2012
26122// Website: http://labs.anthonygarand.com/sticky
26123// Description: Makes an element on the page stick on the screen as you scroll
26124//       It will only set the 'top' and 'position' of your element, you
26125//       might need to adjust the width in some cases.
26126
26127(function($) {
26128  var defaults = {
26129      topSpacing: 0,
26130      bottomSpacing: 0,
26131      className: 'is-sticky',
26132      wrapperClassName: 'sticky-wrapper',
26133      center: false,
26134      getWidthFrom: ''
26135    },
26136    $window = $(window),
26137    $document = $(document),
26138    sticked = [],
26139    windowHeight = $window.height(),
26140    scroller = function() {
26141      var scrollTop = $window.scrollTop(),
26142        documentHeight = $document.height(),
26143        dwh = documentHeight - windowHeight,
26144        extra = (scrollTop > dwh) ? dwh - scrollTop : 0;
26145
26146      for (var i = 0; i < sticked.length; i++) {
26147        var s = sticked[i],
26148          elementTop = s.stickyWrapper.offset().top,
26149          etse = elementTop - s.topSpacing - extra;
26150
26151        if (scrollTop <= etse) {
26152          if (s.currentTop !== null) {
26153            s.stickyElement
26154              .css('position', '')
26155              .css('top', '');
26156            s.stickyElement.parent().removeClass(s.className);
26157            s.currentTop = null;
26158          }
26159        }
26160        else {
26161          var newTop = documentHeight - s.stickyElement.outerHeight()
26162            - s.topSpacing - s.bottomSpacing - scrollTop - extra;
26163          if (newTop < 0) {
26164            newTop = newTop + s.topSpacing;
26165          } else {
26166            newTop = s.topSpacing;
26167          }
26168          if (s.currentTop != newTop) {
26169            s.stickyElement
26170              .css('position', 'fixed')
26171              .css('top', newTop);
26172
26173            if (typeof s.getWidthFrom !== 'undefined') {
26174              s.stickyElement.css('width', $(s.getWidthFrom).width());
26175            }
26176
26177            s.stickyElement.parent().addClass(s.className);
26178            s.currentTop = newTop;
26179          }
26180        }
26181      }
26182    },
26183    resizer = function() {
26184      windowHeight = $window.height();
26185    },
26186    methods = {
26187      init: function(options) {
26188        var o = $.extend(defaults, options);
26189        return this.each(function() {
26190          var stickyElement = $(this);
26191
26192          var stickyId = stickyElement.attr('id');
26193          var wrapper = $('<div></div>')
26194            .attr('id', stickyId + '-sticky-wrapper')
26195            .addClass(o.wrapperClassName);
26196          stickyElement.wrapAll(wrapper);
26197
26198          if (o.center) {
26199            stickyElement.parent().css({width:stickyElement.outerWidth(),marginLeft:"auto",marginRight:"auto"});
26200          }
26201
26202          if (stickyElement.css("float") == "right") {
26203            stickyElement.css({"float":"none"}).parent().css({"float":"right"});
26204          }
26205
26206          var stickyWrapper = stickyElement.parent();
26207          stickyWrapper.css('height', stickyElement.outerHeight());
26208          sticked.push({
26209            topSpacing: o.topSpacing,
26210            bottomSpacing: o.bottomSpacing,
26211            stickyElement: stickyElement,
26212            currentTop: null,
26213            stickyWrapper: stickyWrapper,
26214            className: o.className,
26215            getWidthFrom: o.getWidthFrom
26216          });
26217        });
26218      },
26219      update: scroller
26220    };
26221
26222  // should be more efficient than using $window.scroll(scroller) and $window.resize(resizer):
26223  if (window.addEventListener) {
26224    window.addEventListener('scroll', scroller, false);
26225    window.addEventListener('resize', resizer, false);
26226  } else if (window.attachEvent) {
26227    window.attachEvent('onscroll', scroller);
26228    window.attachEvent('onresize', resizer);
26229  }
26230
26231  $.fn.sticky = function(method) {
26232    if (methods[method]) {
26233      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
26234    } else if (typeof method === 'object' || !method ) {
26235      return methods.init.apply( this, arguments );
26236    } else {
26237      $.error('Method ' + method + ' does not exist on jQuery.sticky');
26238    }
26239  };
26240  $(function() {
26241    setTimeout(scroller, 0);
26242  });
26243})(jQuery);
26244/*
26245 * jQuery plugin for easily checking/unchecking all checkboxes
26246 *
26247 * Copyright (c) 2013 Edmundas Kondrasovas
26248 * Dual licensed under the MIT and GPL licenses.
26249 * Uses the same license as jQuery, see:
26250 * http://docs.jquery.com/License
26251 *
26252 */
26253
26254(function($) {
26255
26256    $.fn.checkControl = function() {
26257        return this.each(function() {
26258            var $this = $(this),
26259                target = $this.data("targets"),
26260                action = $this.data("action");
26261
26262            $this.on("click", function(e) {
26263                if(action == "check") {
26264                    $(target).prop("checked", true);
26265                } else if(action == "uncheck") {
26266                    $(target).prop("checked", false);
26267                }
26268
26269                $(target).trigger("change");
26270
26271                e.preventDefault();
26272                return false;
26273            });
26274        });
26275    };
26276
26277})(jQuery);
26278/*
26279 * jQuery Easing v1.4.0 - http://gsgd.co.uk/sandbox/jquery/easing/
26280 * Open source under the BSD License.
26281 * Copyright © 2008 George McGinley Smith
26282 * All rights reserved.
26283 * https://raw.github.com/gdsmith/jquery-easing/master/LICENSE
26284 */
26285
26286
26287(function (factory) {
26288  if (typeof define === "function" && define.amd) {
26289    define(['jquery'], function ($) {
26290      return factory($);
26291    });
26292  } else if (typeof module === "object" && typeof module.exports === "object") {
26293    exports = factory(require('jquery'));
26294  } else {
26295    factory(jQuery);
26296  }
26297})(function($){
26298
26299  // Preserve the original jQuery "swing" easing as "jswing"
26300  $.easing['jswing'] = $.easing['swing'];
26301
26302  var pow = Math.pow,
26303      sqrt = Math.sqrt,
26304      sin = Math.sin,
26305      cos = Math.cos,
26306      PI = Math.PI,
26307      c1 = 1.70158,
26308      c2 = c1 * 1.525,
26309      c3 = c1 + 1,
26310      c4 = ( 2 * PI ) / 3,
26311      c5 = ( 2 * PI ) / 4.5;
26312
26313  // x is the fraction of animation progress, in the range 0..1
26314  function bounceOut(x) {
26315    var n1 = 7.5625,
26316        d1 = 2.75;
26317    if ( x < 1/d1 ) {
26318      return n1*x*x;
26319    } else if ( x < 2/d1 ) {
26320      return n1*(x-=(1.5/d1))*x + .75;
26321    } else if ( x < 2.5/d1 ) {
26322      return n1*(x-=(2.25/d1))*x + .9375;
26323    } else {
26324      return n1*(x-=(2.625/d1))*x + .984375;
26325    }
26326  }
26327
26328  $.extend( $.easing,
26329    {
26330      def: 'easeOutQuad',
26331      swing: function (x) {
26332        return $.easing[$.easing.def](x);
26333      },
26334      easeInQuad: function (x) {
26335        return x * x;
26336      },
26337      easeOutQuad: function (x) {
26338        return 1 - ( 1 - x ) * ( 1 - x );
26339      },
26340      easeInOutQuad: function (x) {
26341        return x < 0.5 ?
26342          2 * x * x :
26343          1 - pow( -2 * x + 2, 2 ) / 2;
26344      },
26345      easeInCubic: function (x) {
26346        return x * x * x;
26347      },
26348      easeOutCubic: function (x) {
26349        return 1 - pow( 1 - x, 3 );
26350      },
26351      easeInOutCubic: function (x) {
26352        return x < 0.5 ?
26353          4 * x * x * x :
26354          1 - pow( -2 * x + 2, 3 ) / 2;
26355      },
26356      easeInQuart: function (x) {
26357        return x * x * x * x;
26358      },
26359      easeOutQuart: function (x) {
26360        return 1 - pow( 1 - x, 4 );
26361      },
26362      easeInOutQuart: function (x) {
26363        return x < 0.5 ?
26364          8 * x * x * x * x :
26365          1 - pow( -2 * x + 2, 4 ) / 2;
26366      },
26367      easeInQuint: function (x) {
26368        return x * x * x * x * x;
26369      },
26370      easeOutQuint: function (x) {
26371        return 1 - pow( 1 - x, 5 );
26372      },
26373      easeInOutQuint: function (x) {
26374        return x < 0.5 ?
26375          16 * x * x * x * x * x :
26376          1 - pow( -2 * x + 2, 5 ) / 2;
26377      },
26378      easeInSine: function (x) {
26379        return 1 - cos( x * PI/2 );
26380      },
26381      easeOutSine: function (x) {
26382        return sin( x * PI/2 );
26383      },
26384      easeInOutSine: function (x) {
26385        return -( cos( PI * x ) - 1 ) / 2;
26386      },
26387      easeInExpo: function (x) {
26388        return x === 0 ? 0 : pow( 2, 10 * x - 10 );
26389      },
26390      easeOutExpo: function (x) {
26391        return x === 1 ? 1 : 1 - pow( 2, -10 * x );
26392      },
26393      easeInOutExpo: function (x) {
26394        return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ?
26395          pow( 2, 20 * x - 10 ) / 2 :
26396          ( 2 - pow( 2, -20 * x + 10 ) ) / 2;
26397      },
26398      easeInCirc: function (x) {
26399        return 1 - sqrt( 1 - pow( x, 2 ) );
26400      },
26401      easeOutCirc: function (x) {
26402        return sqrt( 1 - pow( x - 1, 2 ) );
26403      },
26404      easeInOutCirc: function (x) {
26405        return x < 0.5 ?
26406          ( 1 - sqrt( 1 - pow( 2 * x, 2 ) ) ) / 2 :
26407          ( sqrt( 1 - pow( -2 * x + 2, 2 ) ) + 1 ) / 2;
26408      },
26409      easeInElastic: function (x) {
26410        return x === 0 ? 0 : x === 1 ? 1 :
26411          -pow( 2, 10 * x - 10 ) * sin( ( x * 10 - 10.75 ) * c4 );
26412      },
26413      easeOutElastic: function (x) {
26414        return x === 0 ? 0 : x === 1 ? 1 :
26415          pow( 2, -10 * x ) * sin( ( x * 10 - 0.75 ) * c4 ) + 1;
26416      },
26417      easeInOutElastic: function (x) {
26418        return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ?
26419          -( pow( 2, 20 * x - 10 ) * sin( ( 20 * x - 11.125 ) * c5 )) / 2 :
26420          pow( 2, -20 * x + 10 ) * sin( ( 20 * x - 11.125 ) * c5 ) / 2 + 1;
26421      },
26422      easeInBack: function (x) {
26423        return c3 * x * x * x - c1 * x * x;
26424      },
26425      easeOutBack: function (x) {
26426        return 1 + c3 * pow( x - 1, 3 ) + c1 * pow( x - 1, 2 );
26427      },
26428      easeInOutBack: function (x) {
26429        return x < 0.5 ?
26430          ( pow( 2 * x, 2 ) * ( ( c2 + 1 ) * 2 * x - c2 ) ) / 2 :
26431          ( pow( 2 * x - 2, 2 ) *( ( c2 + 1 ) * ( x * 2 - 2 ) + c2 ) + 2 ) / 2;
26432      },
26433      easeInBounce: function (x) {
26434        return 1 - bounceOut( 1 - x );
26435      },
26436      easeOutBounce: bounceOut,
26437      easeInOutBounce: function (x) {
26438        return x < 0.5 ?
26439          ( 1 - bounceOut( 1 - 2 * x ) ) / 2 :
26440          ( 1 + bounceOut( 2 * x - 1 ) ) / 2;
26441      }
26442    });
26443
26444});
26445/**
26446 * Timeago is a jQuery plugin that makes it easy to support automatically
26447 * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
26448 *
26449 * @name timeago
26450 * @version 1.4.1
26451 * @requires jQuery v1.2.3+
26452 * @author Ryan McGeary
26453 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
26454 *
26455 * For usage and examples, visit:
26456 * http://timeago.yarp.com/
26457 *
26458 * Copyright (c) 2008-2015, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
26459 */
26460
26461
26462(function (factory) {
26463  if (typeof define === 'function' && define.amd) {
26464    // AMD. Register as an anonymous module.
26465    define(['jquery'], factory);
26466  } else {
26467    // Browser globals
26468    factory(jQuery);
26469  }
26470}(function ($) {
26471  $.timeago = function(timestamp) {
26472    if (timestamp instanceof Date) {
26473      return inWords(timestamp);
26474    } else if (typeof timestamp === "string") {
vendor: 4,206 bytes, lines 26475-26595
26475      return inWords($.timeago.parse(timestamp));
26476    } else if (typeof timestamp === "number") {
26477      return inWords(new Date(timestamp));
26478    } else {
26479      return inWords($.timeago.datetime(timestamp));
26480    }
26481  };
26482  var $t = $.timeago;
26483
26484  $.extend($.timeago, {
26485    settings: {
26486      refreshMillis: 60000,
26487      allowPast: true,
26488      allowFuture: false,
26489      localeTitle: false,
26490      cutoff: 0,
26491      strings: {
26492        prefixAgo: null,
26493        prefixFromNow: null,
26494        suffixAgo: "ago",
26495        suffixFromNow: "from now",
26496        inPast: 'any moment now',
26497        seconds: "less than a minute",
26498        minute: "about a minute",
26499        minutes: "%d minutes",
26500        hour: "about an hour",
26501        hours: "about %d hours",
26502        day: "a day",
26503        days: "%d days",
26504        month: "about a month",
26505        months: "%d months",
26506        year: "about a year",
26507        years: "%d years",
26508        wordSeparator: " ",
26509        numbers: []
26510      }
26511    },
26512
26513    inWords: function(distanceMillis) {
26514      if(!this.settings.allowPast && ! this.settings.allowFuture) {
26515          throw 'timeago allowPast and allowFuture settings can not both be set to false.';
26516      }
26517
26518      var $l = this.settings.strings;
26519      var prefix = $l.prefixAgo;
26520      var suffix = $l.suffixAgo;
26521      if (this.settings.allowFuture) {
26522        if (distanceMillis < 0) {
26523          prefix = $l.prefixFromNow;
26524          suffix = $l.suffixFromNow;
26525        }
26526      }
26527
26528      if(!this.settings.allowPast && distanceMillis >= 0) {
26529        return this.settings.strings.inPast;
26530      }
26531
26532      var seconds = Math.abs(distanceMillis) / 1000;
26533      var minutes = seconds / 60;
26534      var hours = minutes / 60;
26535      var days = hours / 24;
26536      var years = days / 365;
26537
26538      function substitute(stringOrFunction, number) {
26539        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
26540        var value = ($l.numbers && $l.numbers[number]) || number;
26541        return string.replace(/%d/i, value);
26542      }
26543
26544      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
26545        seconds < 90 && substitute($l.minute, 1) ||
26546        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
26547        minutes < 90 && substitute($l.hour, 1) ||
26548        hours < 24 && substitute($l.hours, Math.round(hours)) ||
26549        hours < 42 && substitute($l.day, 1) ||
26550        days < 30 && substitute($l.days, Math.round(days)) ||
26551        days < 45 && substitute($l.month, 1) ||
26552        days < 365 && substitute($l.months, Math.round(days / 30)) ||
26553        years < 1.5 && substitute($l.year, 1) ||
26554        substitute($l.years, Math.round(years));
26555
26556      var separator = $l.wordSeparator || "";
26557      if ($l.wordSeparator === undefined) { separator = " "; }
26558      return $.trim([prefix, words, suffix].join(separator));
26559    },
26560
26561    parse: function(iso8601) {
26562      var s = $.trim(iso8601);
26563      s = s.replace(/\.\d+/,""); // remove milliseconds
26564      s = s.replace(/-/,"/").replace(/-/,"/");
26565      s = s.replace(/T/," ").replace(/Z/," UTC");
26566      s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
26567      s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900
26568      return new Date(s);
26569    },
26570    datetime: function(elem) {
26571      var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
26572      return $t.parse(iso8601);
26573    },
26574    isTime: function(elem) {
26575      // jQuery's `is()` doesn't play well with HTML5 in IE
26576      return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
26577    }
26578  });
26579
26580  // functions that can be called via $(el).timeago('action')
26581  // init is default when no action is given
26582  // functions are called with context of a single element
26583  var functions = {
26584    init: function(){
26585      var refresh_el = $.proxy(refresh, this);
26586      refresh_el();
26587      var $s = $t.settings;
26588      if ($s.refreshMillis > 0) {
26589        this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis);
26590      }
26591    },
26592    update: function(time){
26593      var parsedTime = $t.parse(time);
26594      $(this).data('timeago', { datetime: parsedTime });
26595      if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleStr
26595ing());
26596      refresh.apply(this);
26597    },
26598    updateFromDOM: function(){
26599      $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) });
26600      refresh.apply(this);
26601    },
26602    dispose: function () {
26603      if (this._timeagoInterval) {
26604        window.clearInterval(this._timeagoInterval);
26605        this._timeagoInterval = null;
26606      }
26607    }
26608  };
26609
26610  $.fn.timeago = function(action, options) {
26611    var fn = action ? functions[action] : functions.init;
26612    if(!fn){
26613      throw new Error("Unknown function name '"+ action +"' for timeago");
26614    }
26615    // each over objects here and call the requested function
26616    this.each(function(){
26617      fn.call(this, options);
26618    });
26619    return this;
26620  };
26621
26622  function refresh() {
26623    //check if it's still visible
26624    if(!$.contains(document.documentElement,this)){
26625      //stop if it has been removed
26626      $(this).timeago("dispose");
26627      return this;
26628    }
26629
26630    var data = prepareData(this);
26631    var $s = $t.settings;
26632
26633    if (!isNaN(data.datetime)) {
26634      if ( $s.cutoff == 0 || Math.abs(distance(data.datetime)) < $s.cutoff) {
26635        $(this).text(inWords(data.datetime));
26636      }
26637    }
26638    return this;
26639  }
26640
26641  function prepareData(element) {
26642    element = $(element);
26643    if (!element.data("timeago")) {
26644      element.data("timeago", { datetime: $t.datetime(element) });
26645      var text = $.trim(element.text());
26646      if ($t.settings.localeTitle) {
26647        element.attr("title", element.data('timeago').datetime.toLocaleString());
26648      } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
26649        element.attr("title", text);
26650      }
26651    }
26652    return element.data("timeago");
26653  }
26654
26655  function inWords(date) {
26656    return $t.inWords(distance(date));
26657  }
26658
26659  function distance(date) {
26660    return (new Date().getTime() - date.getTime());
26661  }
26662
26663  // fix for IE6 suckage
26664  document.createElement("abbr");
26665  document.createElement("time");
26666}));
26667/*!
26668 * imagesLoaded PACKAGED v3.1.4
26669 * JavaScript is all like "You images are done yet or what?"
26670 * MIT License
26671 */
26672
26673
26674/*!
26675 * EventEmitter v4.2.6 - git.io/ee
26676 * Oliver Caldwell
26677 * MIT license
26678 * @preserve
26679 */
26680
26681
26682(function () {
26683	
26684
26685	/**
26686	 * Class for managing events.
26687	 * Can be extended to provide event functionality in other classes.
26688	 *
26689	 * @class EventEmitter Manages event registering and emitting.
26690	 */
26691	function EventEmitter() {}
26692
26693	// Shortcuts to improve speed and size
26694	var proto = EventEmitter.prototype;
26695	var exports = this;
26696	var originalGlobalValue = exports.EventEmitter;
26697
26698	/**
26699	 * Finds the index of the listener for the event in it's storage array.
26700	 *
26701	 * @param {Function[]} listeners Array of listeners to search through.
26702	 * @param {Function} listener Method to look for.
26703	 * @return {Number} Index of the specified listener, -1 if not found
26704	 * @api private
26705	 */
26706	function indexOfListener(listeners, listener) {
26707		var i = listeners.length;
26708		while (i--) {
26709			if (listeners[i].listener === listener) {
26710				return i;
26711			}
26712		}
26713
26714		return -1;
26715	}
26716
26717	/**
26718	 * Alias a method while keeping the context correct, to allow for overwriting of target method.
26719	 *
26720	 * @param {String} name The name of the target method.
26721	 * @return {Function} The aliased method
26722	 * @api private
26723	 */
26724	function alias(name) {
26725		return function aliasClosure() {
26726			return this[name].apply(this, arguments);
26727		};
26728	}
26729
26730	/**
26731	 * Returns the listener array for the specified event.
26732	 * Will initialise the event object and listener arrays if required.
26733	 * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object c
26733ontaining bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
26734	 * Each property in the object response is an array of listener functions.
26735	 *
26736	 * @param {String|RegExp} evt Name of the event to return the listeners from.
26737	 * @return {Function[]|Object} All listener functions for the event.
26738	 */
26739	proto.getListeners = function getListeners(evt) {
26740		var events = this._getEvents();
26741		var response;
26742		var key;
26743
26744		// Return a concatenated array of all matching events if
26745		// the selector is a regular expression.
26746		if (typeof evt === 'object') {
26747			response = {};
26748			for (key in events) {
26749				if (events.hasOwnProperty(key) && evt.test(key)) {
26750					response[key] = events[key];
26751				}
26752			}
26753		}
26754		else {
26755			response = events[evt] || (events[evt] = []);
26756		}
26757
26758		return response;
26759	};
26760
26761	/**
26762	 * Takes a list of listener objects and flattens it into a list of listener functions.
26763	 *
26764	 * @param {Object[]} listeners Raw listener objects.
26765	 * @return {Function[]} Just the listener functions.
26766	 */
26767	proto.flattenListeners = function flattenListeners(listeners) {
26768		var flatListeners = [];
26769		var i;
26770
26771		for (i = 0; i < listeners.length; i += 1) {
26772			flatListeners.push(listeners[i].listener);
26773		}
26774
26775		return flatListeners;
26776	};
26777
26778	/**
26779	 * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
26780	 *
26781	 * @param {String|RegExp} evt Name of the event to return the listeners from.
26782	 * @return {Object} All listener functions for an event in an object.
26783	 */
26784	proto.getListenersAsObject = function getListenersAsObject(evt) {
26785		var listeners = this.getListeners(evt);
26786		var response;
26787
26788		if (listeners instanceof Array) {
26789			response = {};
26790			response[evt] = listeners;
26791		}
26792
26793		return response || listeners;
26794	};
26795
26796	/**
26797	 * Adds a listener function to the specified event.
26798	 * The listener will not be added if it is a duplicate.
26799	 * If the listener returns true then it will be removed after it is called.
26800	 * If you pass a regular expression as the event name then the listener will be added to all events that match it.
26801	 *
26802	 * @param {String|RegExp} evt Name of the event to attach the listener to.
26803	 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
26804	 * @return {Object} Current instance of EventEmitter for chaining.
26805	 */
26806	proto.addListener = function addListener(evt, listener) {
26807		var listeners = this.getListenersAsObject(evt);
26808		var listenerIsWrapped = typeof listener === 'object';
26809		var key;
26810
26811		for (key in listeners) {
26812			if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
26813				listeners[key].push(listenerIsWrapped ? listener : {
26814					listener: listener,
26815					once: false
26816				});
26817			}
26818		}
26819
26820		return this;
26821	};
26822
26823	/**
26824	 * Alias of addListener
26825	 */
26826	proto.on = alias('addListener');
26827
26828	/**
26829	 * Semi-alias of addListener. It will add a listener that will be
26830	 * automatically removed after it's first execution.
26831	 *
26832	 * @param {String|RegExp} evt Name of the event to attach the listener to.
26833	 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
26834	 * @return {Object} Current instance of EventEmitter for chaining.
26835	 */
26836	proto.addOnceListener = function addOnceListener(evt, listener) {
26837		return this.addListener(evt, {
26838			listener: listener,
26839			once: true
26840		});
26841	};
26842
26843	/**
26844	 * Alias of addOnceListener.
26845	 */
26846	proto.once = alias('addOnceListener');
26847
26848	/**
26849	 * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
26850	 * You need to tell it what event names should be matched by a regex.
26851	 *
26852	 * @param {String} evt Name of the event to create.
26853	 * @return {Object} Current instance of EventEmitter for chaining.
26854	 */
26855	proto.defineEvent = function defineEvent(evt) {
26856		this.getListeners(evt);
26857		return this;
26858	};
26859
26860	/**
26861	 * Uses defineEvent to define multiple events.
26862	 *
26863	 * @param {String[]} evts An array of event names to define.
26864	 * @return {Object} Current instance of EventEmitter for chaining.
26865	 */
26866	proto.defineEvents = function defineEvents(evts) {
26867		for (var i = 0; i < evts.length; i += 1) {
26868			this.defineEvent(evts[i]);
26869		}
26870		return this;
26871	};
26872
26873	/**
26874	 * Removes a listener function from the specified event.
26875	 * When passed a regular expression as the event name, it will remove the listener from all events that match it.
26876	 *
26877	 * @param {String|RegExp} evt Name of the event to remove the listener from.
26878	 * @param {Function} listener Method to remove from the event.
26879	 * @return {Object} Current instance of EventEmitter for chaining.
26880	 */
26881	proto.removeListener = function removeListener(evt, listener) {
26882		var listeners = this.getListenersAsObject(evt);
26883		var index;
26884		var key;
26885
26886		for (key in listeners) {
26887			if (listeners.hasOwnProperty(key)) {
26888				index = indexOfListener(listeners[key], listener);
26889
26890				if (index !== -1) {
26891					listeners[key].splice(index, 1);
26892				}
26893			}
26894		}
26895
26896		return this;
26897	};
26898
26899	/**
26900	 * Alias of removeListener
26901	 */
26902	proto.off = alias('removeListener');
26903
26904	/**
26905	 * Adds listeners in bulk using the manipulateListeners method.
26906	 * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
26907	 * You can also pass it a regular expression to add the array of listeners to all events that match it.
26908	 * Yeah, this function does quite a bit. That's probably a bad thing.
26909	 *
26910	 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
26911	 * @param {Function[]} [listeners] An optional array of listener functions to add.
26912	 * @return {Object} Current instance of EventEmitter for chaining.
26913	 */
26914	proto.addListeners = function addListeners(evt, listeners) {
26915		// Pass through to manipulateListeners
26916		return this.manipulateListeners(false, evt, listeners);
26917	};
26918
26919	/**
26920	 * Removes listeners in bulk using the manipulateListeners method.
26921	 * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
26922	 * You can also pass it an event name and an array of listeners to be removed.
26923	 * You can also pass it a regular expression to remove the listeners from all events that match it.
26924	 *
26925	 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
26926	 * @param {Function[]} [listeners] An optional array of listener functions to remove.
26927	 * @return {Object} Current instance of EventEmitter for chaining.
26928	 */
26929	proto.removeListeners = function removeListeners(evt, listeners) {
26930		// Pass through to manipulateListeners
26931		return this.manipulateListeners(true, evt, listeners);
26932	};
26933
26934	/**
26935	 * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
26936	 * The first argument will determine if the listeners are removed (true) or added (false).
26937	 * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
26938	 * You can also pass it an event name and an array of listeners to be added/removed.
26939	 * You can also pass it a regular expression to manipulate the listeners of all events that match it.
26940	 *
26941	 * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
26942	 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
26943	 * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
26944	 * @return {Object} Current instance of EventEmitter for chaining.
26945	 */
26946	proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
26947		var i;
26948		var value;
26949		var single = remove ? this.removeListener : this.addListener;
26950		var multiple = remove ? this.removeListeners : this.addListeners;
26951
26952		// If evt is an object then pass each of it's properties to this method
26953		if (typeof evt === 'object' && !(evt instanceof RegExp)) {
26954			for (i in evt) {
26955				if (evt.hasOwnProperty(i) && (value = evt[i])) {
26956					// Pass the single listener straight through to the singular method
26957					if (typeof value === 'function') {
26958						single.call(this, i, value);
26959					}
26960					else {
26961						// Otherwise pass back to the multiple function
26962						multiple.call(this, i, value);
26963					}
26964				}
26965			}
26966		}
26967		else {
26968			// So evt must be a string
26969			// And listeners must be an array of listeners
26970			// Loop over it and pass each one to the multiple method
26971			i = listeners.length;
26972			while (i--) {
26973				single.call(this, evt, listeners[i]);
26974			}
26975		}
26976
26977		return this;
26978	};
26979
26980	/**
26981	 * Removes all listeners from a specified event.
26982	 * If you do not specify an event then all listeners will be removed.
26983	 * That means every event will be emptied.
26984	 * You can also pass a regex to remove all events that match it.
26985	 *
26986	 * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
26987	 * @return {Object} Current instance of EventEmitter for chaining.
26988	 */
26989	proto.removeEvent = function removeEvent(evt) {
26990		var type = typeof evt;
26991		var events = this._getEvents();
26992		var key;
26993
26994		// Remove different things depending on the state of evt
26995		if (type === 'string') {
26996			// Remove all listeners for the specified event
26997			delete events[evt];
26998		}
26999		else if (type === 'object') {
27000			// Remove all events matching the regex.
27001			for (key in events) {
27002				if (events.hasOwnProperty(key) && evt.test(key)) {
27003					delete events[key];
27004				}
27005			}
27006		}
27007		else {
27008			// Remove all listeners in all events
27009			delete this._events;
27010		}
27011
27012		return this;
27013	};
27014
27015	/**
27016	 * Alias of removeEvent.
27017	 *
27018	 * Added to mirror the node API.
27019	 */
27020	proto.removeAllListeners = alias('removeEvent');
27021
27022	/**
27023	 * Emits an event of your choice.
27024	 * When emitted, every listener attached to that event will be executed.
27025	 * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
27026	 * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
27027	 * So they will not arrive within the array on the other side, they will be separate.
27028	 * You can also pass a regular expression to emit to all events that match it.
27029	 *
27030	 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
27031	 * @param {Array} [args] Optional array of arguments to be passed to each listener.
27032	 * @return {Object} Current instance of EventEmitter for chaining.
27033	 */
27034	proto.emitEvent = function emitEvent(evt, args) {
27035		var listeners = this.getListenersAsObject(evt);
27036		var listener;
27037		var i;
27038		var key;
27039		var response;
27040
27041		for (key in listeners) {
27042			if (listeners.hasOwnProperty(key)) {
27043				i = listeners[key].length;
27044
27045				while (i--) {
27046					// If the listener returns true then it shall be removed from the event
27047					// The function is executed either with a basic call or an apply if there is an args array
27048					listener = listeners[key][i];
27049
27050					if (listener.once === true) {
27051						this.removeListener(evt, listener.listener);
27052					}
27053
27054					response = listener.listener.apply(this, args || []);
27055
27056					if (response === this._getOnceReturnValue()) {
27057						this.removeListener(evt, listener.listener);
27058					}
27059				}
27060			}
27061		}
27062
27063		return this;
27064	};
27065
27066	/**
27067	 * Alias of emitEvent
27068	 */
27069	proto.trigger = alias('emitEvent');
27070
27071	/**
27072	 * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
27073	 * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
27074	 *
27075	 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
27076	 * @param {...*} Optional additional arguments to be passed to each listener.
27077	 * @return {Object} Current instance of EventEmitter for chaining.
27078	 */
27079	proto.emit = function emit(evt) {
27080		var args = Array.prototype.slice.call(arguments, 1);
27081		return this.emitEvent(evt, args);
27082	};
27083
27084	/**
27085	 * Sets the current value to check against when executing listeners. If a
27086	 * listeners return value matches the one set here then it will be removed
27087	 * after execution. This value defaults to true.
27088	 *
27089	 * @param {*} value The new value to check for when executing listeners.
27090	 * @return {Object} Current instance of EventEmitter for chaining.
27091	 */
27092	proto.setOnceReturnValue = function setOnceReturnValue(value) {
27093		this._onceReturnValue = value;
27094		return this;
27095	};
27096
27097	/**
27098	 * Fetches the current value to check against when executing listeners. If
27099	 * the listeners return value matches this one then it should be removed
27100	 * automatically. It will return true by default.
27101	 *
27102	 * @return {*|Boolean} The current value to check for or the default, true.
27103	 * @api private
27104	 */
27105	proto._getOnceReturnValue = function _getOnceReturnValue() {
27106		if (this.hasOwnProperty('_onceReturnValue')) {
27107			return this._onceReturnValue;
27108		}
27109		else {
27110			return true;
27111		}
27112	};
27113
27114	/**
27115	 * Fetches the events object and creates one if required.
27116	 *
27117	 * @return {Object} The events storage object.
27118	 * @api private
27119	 */
27120	proto._getEvents = function _getEvents() {
27121		return this._events || (this._events = {});
27122	};
27123
27124	/**
27125	 * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
27126	 *
27127	 * @return {Function} Non conflicting EventEmitter class.
27128	 */
27129	EventEmitter.noConflict = function noConflict() {
27130		exports.EventEmitter = originalGlobalValue;
27131		return EventEmitter;
27132	};
27133
27134	// Expose the class either via AMD, CommonJS or the global object
27135	if (typeof define === 'function' && define.amd) {
27136		define('eventEmitter/EventEmitter',[],function () {
27137			return EventEmitter;
27138		});
27139	}
27140	else if (typeof module === 'object' && module.exports){
27141		module.exports = EventEmitter;
27142	}
27143	else {
27144		this.EventEmitter = EventEmitter;
27145	}
27146}.call(this));
27147
27148/*!
27149 * eventie v1.0.4
27150 * event binding helper
27151 *   eventie.bind( elem, 'click', myFn )
27152 *   eventie.unbind( elem, 'click', myFn )
27153 */
27154
27155/*jshint browser: true, undef: true, unused: true */
27156/*global define: false */
27157
27158( function( window ) {
27159
27160
27161
27162var docElem = document.documentElement;
27163
27164var bind = function() {};
27165
27166function getIEEvent( obj ) {
27167  var event = window.event;
27168  // add event.target
27169  event.target = event.target || event.srcElement || obj;
27170  return event;
27171}
27172
27173if ( docElem.addEventListener ) {
27174  bind = function( obj, type, fn ) {
27175    obj.addEventListener( type, fn, false );
27176  };
27177} else if ( docElem.attachEvent ) {
27178  bind = function( obj, type, fn ) {
27179    obj[ type + fn ] = fn.handleEvent ?
27180      function() {
27181        var event = getIEEvent( obj );
27182        fn.handleEvent.call( fn, event );
27183      } :
27184      function() {
27185        var event = getIEEvent( obj );
27186        fn.call( obj, event );
27187      };
27188    obj.attachEvent( "on" + type, obj[ type + fn ] );
27189  };
27190}
27191
27192var unbind = function() {};
27193
27194if ( docElem.removeEventListener ) {
27195  unbind = function( obj, type, fn ) {
27196    obj.removeEventListener( type, fn, false );
27197  };
27198} else if ( docElem.detachEvent ) {
27199  unbind = function( obj, type, fn ) {
27200    obj.detachEvent( "on" + type, obj[ type + fn ] );
27201    try {
27202      delete obj[ type + fn ];
27203    } catch ( err ) {
27204      // can't delete window object properties
27205      obj[ type + fn ] = undefined;
27206    }
27207  };
27208}
27209
27210var eventie = {
27211  bind: bind,
27212  unbind: unbind
27213};
27214
27215// transport
27216if ( typeof define === 'function' && define.amd ) {
27217  // AMD
27218  define( 'eventie/eventie',eventie );
27219} else {
27220  // browser global
27221  window.eventie = eventie;
27222}
27223
27224})( this );
27225
27226/*!
27227 * imagesLoaded v3.1.4
27228 * JavaScript is all like "You images are done yet or what?"
27229 * MIT License
27230 */
27231
27232( function( window, factory ) { 
27233  // universal module definition
27234
27235  /*global define: false, module: false, require: false */
27236
27237  if ( typeof define === 'function' && define.amd ) {
27238    // AMD
27239    define( [
27240      'eventEmitter/EventEmitter',
27241      'eventie/eventie'
27242    ], function( EventEmitter, eventie ) {
27243      return factory( window, EventEmitter, eventie );
27244    });
27245  } else if ( typeof exports === 'object' ) {
27246    // CommonJS
27247    module.exports = factory(
27248      window,
27249      require('eventEmitter'),
27250      require('eventie')
27251    );
27252  } else {
27253    // browser global
27254    window.imagesLoaded = factory(
27255      window,
27256      window.EventEmitter,
27257      window.eventie
27258    );
27259  }
27260
27261})( this,
27262
27263// --------------------------  factory -------------------------- //
27264
27265function factory( window, EventEmitter, eventie ) {
27266
27267
27268
27269var $ = window.jQuery;
27270var console = window.console;
27271var hasConsole = typeof console !== 'undefined';
27272
27273// -------------------------- helpers -------------------------- //
27274
27275// extend objects
27276function extend( a, b ) {
27277  for ( var prop in b ) {
27278    a[ prop ] = b[ prop ];
27279  }
27280  return a;
27281}
27282
27283var objToString = Object.prototype.toString;
27284function isArray( obj ) {
27285  return objToString.call( obj ) === '[object Array]';
27286}
27287
27288// turn element or nodeList into an array
27289function makeArray( obj ) {
27290  var ary = [];
27291  if ( isArray( obj ) ) {
27292    // use object if already an array
27293    ary = obj;
27294  } else if ( typeof obj.length === 'number' ) {
27295    // convert nodeList to array
27296    for ( var i=0, len = obj.length; i < len; i++ ) {
27297      ary.push( obj[i] );
27298    }
27299  } else {
27300    // array of single index
27301    ary.push( obj );
27302  }
27303  return ary;
27304}
27305
27306  // -------------------------- imagesLoaded -------------------------- //
27307
27308  /**
27309   * @param {Array, Element, NodeList, String} elem
27310   * @param {Object or Function} options - if function, use as callback
27311   * @param {Function} onAlways - callback function
27312   */
27313  function ImagesLoaded( elem, options, onAlways ) {
27314    // coerce ImagesLoaded() without new, to be new ImagesLoaded()
27315    if ( !( this instanceof ImagesLoaded ) ) {
27316      return new ImagesLoaded( elem, options );
27317    }
27318    // use elem as selector string
27319    if ( typeof elem === 'string' ) {
27320      elem = document.querySelectorAll( elem );
27321    }
27322
27323    this.elements = makeArray( elem );
27324    this.options = extend( {}, this.options );
27325
27326    if ( typeof options === 'function' ) {
27327      onAlways = options;
27328    } else {
27329      extend( this.options, options );
27330    }
27331
27332    if ( onAlways ) {
27333      this.on( 'always', onAlways );
27334    }
27335
27336    this.getImages();
27337
27338    if ( $ ) {
27339      // add jQuery Deferred object
27340      this.jqDeferred = new $.Deferred();
27341    }
27342
27343    // HACK check async to allow time to bind listeners
27344    var _this = this;
27345    setTimeout( function() {
27346      _this.check();
27347    });
27348  }
27349
27350  ImagesLoaded.prototype = new EventEmitter();
27351
27352  ImagesLoaded.prototype.options = {};
27353
27354  ImagesLoaded.prototype.getImages = function() {
27355    this.images = [];
27356
27357    // filter & find items if we have an item selector
27358    for ( var i=0, len = this.elements.length; i < len; i++ ) {
27359      var elem = this.elements[i];
27360      // filter siblings
27361      if ( elem.nodeName === 'IMG' ) {
27362        this.addImage( elem );
27363      }
27364      // find children
27365      var childElems = elem.querySelectorAll('img');
27366      // concat childElems to filterFound array
27367      for ( var j=0, jLen = childElems.length; j < jLen; j++ ) {
27368        var img = childElems[j];
27369        this.addImage( img );
27370      }
27371    }
27372  };
27373
27374  /**
27375   * @param {Image} img
27376   */
27377  ImagesLoaded.prototype.addImage = function( img ) {
27378    var loadingImage = new LoadingImage( img );
27379    this.images.push( loadingImage );
27380  };
27381
27382  ImagesLoaded.prototype.check = function() {
27383    var _this = this;
27384    var checkedCount = 0;
27385    var length = this.images.length;
27386    this.hasAnyBroken = false;
27387    // complete if no images
27388    if ( !length ) {
27389      this.complete();
27390      return;
27391    }
27392
27393    function onConfirm( image, message ) {
27394      if ( _this.options.debug && hasConsole ) {
27395        console.log( 'confirm', image, message );
27396      }
27397
27398      _this.progress( image );
27399      checkedCount++;
27400      if ( checkedCount === length ) {
27401        _this.complete();
27402      }
27403      return true; // bind once
27404    }
27405
27406    for ( var i=0; i < length; i++ ) {
27407      var loadingImage = this.images[i];
27408      loadingImage.on( 'confirm', onConfirm );
27409      loadingImage.check();
27410    }
27411  };
27412
27413  ImagesLoaded.prototype.progress = function( image ) {
27414    this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
27415    // HACK - Chrome triggers event before object properties have changed. #83
27416    var _this = this;
27417    setTimeout( function() {
27418      _this.emit( 'progress', _this, image );
27419      if ( _this.jqDeferred && _this.jqDeferred.notify ) {
27420        _this.jqDeferred.notify( _this, image );
27421      }
27422    });
27423  };
27424
27425  ImagesLoaded.prototype.complete = function() {
27426    var eventName = this.hasAnyBroken ? 'fail' : 'done';
27427    this.isComplete = true;
27428    var _this = this;
27429    // HACK - another setTimeout so that confirm happens after progress
27430    setTimeout( function() {
27431      _this.emit( eventName, _this );
27432      _this.emit( 'always', _this );
27433      if ( _this.jqDeferred ) {
27434        var jqMethod = _this.hasAnyBroken ? 'reject' : 'resolve';
27435        _this.jqDeferred[ jqMethod ]( _this );
27436      }
27437    });
27438  };
27439
27440  // -------------------------- jquery -------------------------- //
27441
27442  if ( $ ) {
27443    $.fn.imagesLoaded = function( options, callback ) {
27444      var instance = new ImagesLoaded( this, options, callback );
27445      return instance.jqDeferred.promise( $(this) );
27446    };
27447  }
27448
27449
27450  // --------------------------  -------------------------- //
27451
27452  function LoadingImage( img ) {
27453    this.img = img;
27454  }
27455
27456  LoadingImage.prototype = new EventEmitter();
27457
27458  LoadingImage.prototype.check = function() {
27459    // first check cached any previous images that have same src
27460    var resource = cache[ this.img.src ] || new Resource( this.img.src );
27461    if ( resource.isConfirmed ) {
27462      this.confirm( resource.isLoaded, 'cached was confirmed' );
27463      return;
27464    }
27465
27466    // If complete is true and browser supports natural sizes,
27467    // try to check for image status manually.
27468    if ( this.img.complete && this.img.naturalWidth !== undefined ) {
27469      // report based on naturalWidth
27470      this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
27471      return;
27472    }
27473
27474    // If none of the checks above matched, simulate loading on detached element.
27475    var _this = this;
27476    resource.on( 'confirm', function( resrc, message ) {
27477      _this.confirm( resrc.isLoaded, message );
27478      return true;
27479    });
27480
27481    resource.check();
27482  };
27483
27484  LoadingImage.prototype.confirm = function( isLoaded, message ) {
27485    this.isLoaded = isLoaded;
27486    this.emit( 'confirm', this, message );
27487  };
27488
27489  // -------------------------- Resource -------------------------- //
27490
27491  // Resource checks each src, only once
27492  // separate class from LoadingImage to prevent memory leaks. See #115
27493
27494  var cache = {};
27495
27496  function Resource( src ) {
27497    this.src = src;
27498    // add to cache
27499    cache[ src ] = this;
27500  }
27501
27502  Resource.prototype = new EventEmitter();
27503
27504  Resource.prototype.check = function() {
27505    // only trigger checking once
27506    if ( this.isChecked ) {
27507      return;
27508    }
27509    // simulate loading on detached element
27510    var proxyImage = new Image();
27511    eventie.bind( proxyImage, 'load', this );
27512    eventie.bind( proxyImage, 'error', this );
27513    proxyImage.src = this.src;
27514    // set flag
27515    this.isChecked = true;
27516  };
27517
27518  // ----- events ----- //
27519
27520  // trigger specified handler for event type
27521  Resource.prototype.handleEvent = function( event ) {
27522    var method = 'on' + event.type;
27523    if ( this[ method ] ) {
27524      this[ method ]( event );
27525    }
27526  };
27527
27528  Resource.prototype.onload = function( event ) {
27529    this.confirm( true, 'onload' );
27530    this.unbindProxyEvents( event );
27531  };
27532
27533  Resource.prototype.onerror = function( event ) {
27534    this.confirm( false, 'onerror' );
27535    this.unbindProxyEvents( event );
27536  };
27537
27538  // ----- confirm ----- //
27539
27540  Resource.prototype.confirm = function( isLoaded, message ) {
27541    this.isConfirmed = true;
27542    this.isLoaded = isLoaded;
27543    this.emit( 'confirm', this, message );
27544  };
27545
27546  Resource.prototype.unbindProxyEvents = function( event ) {
27547    eventie.unbind( event.target, 'load', this );
27548    eventie.unbind( event.target, 'error', this );
27549  };
27550
27551  // -----  ----- //
27552
27553  return ImagesLoaded;
27554
27555});
27556/*
27557 * Backstretch
27558 * http://srobbin.com/jquery-plugins/backstretch/
27559 *
27560 * Copyright (c) 2013 Scott Robbin
27561 * Licensed under the MIT license.
27562 */
27563
27564
27565;(function ($, window, undefined) {
27566  'use strict';
27567
27568  /* PLUGIN DEFINITION
27569   * ========================= */
27570
27571  $.fn.backstretch = function (images, options) {
27572    // We need at least one image or method name
27573    if (images === undefined || images.length === 0) {
27574      $.error("No images were supplied for Backstretch");
27575    }
27576
27577    /*
27578     * Scroll the page one pixel to get the right window height on iOS
27579     * Pretty harmless for everyone else
27580    */
27581    if ($(window).scrollTop() === 0 ) {
27582      window.scrollTo(0, 0);
27583    }
27584
27585    return this.each(function () {
27586      var $this = $(this)
27587        , obj = $this.data('backstretch');
27588
27589      // Do we already have an instance attached to this element?
27590      if (obj) {
27591
27592        // Is this a method they're trying to execute?
27593        if (typeof images == 'string' && typeof obj[images] == 'function') {
27594          // Call the method
27595          obj[images](options);
27596
27597          // No need to do anything further
27598          return;
27599        }
27600
27601        // Merge the old options with the new
27602        options = $.extend(obj.options, options);
27603
27604        // Remove the old instance
27605        obj.destroy(true);
27606      }
27607
27608      obj = new Backstretch(this, images, options);
27609      $this.data('backstretch', obj);
27610    });
27611  };
27612
27613  // If no element is supplied, we'll attach to body
27614  $.backstretch = function (images, options) {
27615    // Return the instance
27616    return $('body')
27617            .backstretch(images, options)
27618            .data('backstretch');
27619  };
27620
27621  // Custom selector
27622  $.expr[':'].backstretch = function(elem) {
27623    return $(elem).data('backstretch') !== undefined;
27624  };
27625
27626  /* DEFAULTS
27627   * ========================= */
27628
27629  $.fn.backstretch.defaults = {
27630      centeredX: true   // Should we center the image on the X axis?
27631    , centeredY: true   // Should we center the image on the Y axis?
27632    , duration: 5000    // Amount of time in between slides (if slideshow)
27633    , fade: 0           // Speed of fade transition between slides
27634  };
27635
27636  /* STYLES
27637   *
27638   * Baked-in styles that we'll apply to our elements.
27639   * In an effort to keep the plugin simple, these are not exposed as options.
27640   * That said, anyone can override these in their own stylesheet.
27641   * ========================= */
27642  var styles = {
27643      wrap: {
27644          left: 0
27645        , top: 0
27646        , overflow: 'hidden'
27647        , margin: 0
27648        , padding: 0
27649        , height: '100%'
27650        , width: '100%'
27651        , zIndex: -999999
27652      }
27653    , img: {
27654          position: 'absolute'
27655        , display: 'none'
27656        , margin: 0
27657        , padding: 0
27658        , border: 'none'
27659        , width: 'auto'
27660        , height: 'auto'
27661        , maxHeight: 'none'
27662        , maxWidth: 'none'
27663        , zIndex: -999999
27664      }
27665  };
27666
27667  /* CLASS DEFINITION
27668   * ========================= */
27669  var Backstretch = function (container, images, options) {
27670    this.options = $.extend({}, $.fn.backstretch.defaults, options || {});
27671
27672    /* In its simplest form, we allow Backstretch to be called on an image path.
27673     * e.g. $.backstretch('/path/to/image.jpg')
27674     * So, we need to turn this back into an array.
27675     */
27676    this.images = $.isArray(images) ? images : [images];
27677
27678    // Preload images
27679    $.each(this.images, function () {
27680      $('<img />')[0].src = this;
27681    });
27682
27683    // Convenience reference to know if the container is body.
27684    this.isBody = container === document.body;
27685
27686    /* We're keeping track of a few different elements
27687     *
27688     * Container: the element that Backstretch was called on.
27689     * Wrap: a DIV that we place the image into, so we can hide the overflow.
27690     * Root: Convenience reference to help calculate the correct height.
27691     */
27692    this.$container = $(container);
27693    this.$root = this.isBody ? supportsFixedPosition ? $(window) : $(document) : this.$container;
27694
27695    // Don't create a new wrap if one already exists (from a previous instance of Backstretch)
27696    var $existing = this.$container.children(".backstretch").first();
27697    this.$wrap = $existing.length ? $existing : $('<div class="backstretch"></div>').css(styles.wrap).appendTo(this.$container);
27698
27699    // Non-body elements need some style adjustments
27700    if (!this.isBody) {
27701      // If the container is statically positioned, we need to make it relative,
27702      // and if no zIndex is defined, we should set it to zero.
27703      var position = this.$container.css('position')
27704        , zIndex = this.$container.css('zIndex');
27705
27706      this.$container.css({
27707          position: position === 'static' ? 'relative' : position
27708        , zIndex: zIndex === 'auto' ? 0 : zIndex
27709        //, background: 'none'
27710      });
27711
27712      // Needs a higher z-index
27713      this.$wrap.css({zIndex: -999998});
27714    }
27715
27716    // Fixed or absolute positioning?
27717    this.$wrap.css({
27718      position: this.isBody && supportsFixedPosition ? 'fixed' : 'absolute'
27719    });
27720
27721    // Set the first image
27722    this.index = 0;
27723    this.show(this.index);
27724
27725    // Listen for resize
27726    $(window).on('resize.backstretch', $.proxy(this.resize, this))
27727             .on('orientationchange.backstretch', $.proxy(function () {
27728                // Need to do this in order to get the right window height
27729                if (this.isBody && window.pageYOffset === 0) {
27730                  window.scrollTo(0, 1);
27731                  this.resize();
27732                }
27733             }, this));
27734  };
27735
27736  /* PUBLIC METHODS
27737   * ========================= */
27738  Backstretch.prototype = {
27739      resize: function () {
27740        try {
27741          var bgCSS = {left: 0, top: 0}
27742            , rootWidth = this.isBody ? this.$root.width() : this.$root.innerWidth()
27743            , bgWidth = rootWidth
27744            , rootHeight = this.isBody ? ( window.innerHeight ? window.innerHeight : this.$root.height() ) : this.$root.innerHeight()
27745            , bgHeight = bgWidth / this.$img.data('ratio')
27746            , bgOffset;
27747
27748            // Make adjustments based on image ratio
27749            if (bgHeight >= rootHeight) {
27750                bgOffset = (bgHeight - rootHeight) / 2;
27751                if(this.options.centeredY) {
27752                  bgCSS.top = '-' + bgOffset + 'px';
27753                }
27754            } else {
27755                bgHeight = rootHeight;
27756                bgWidth = bgHeight * this.$img.data('ratio');
27757                bgOffset = (bgWidth - rootWidth) / 2;
27758                if(this.options.centeredX) {
27759                  bgCSS.left = '-' + bgOffset + 'px';
27760                }
27761            }
27762
27763            this.$wrap.css({width: rootWidth, height: rootHeight})
27764                      .find('img:not(.deleteable)').css({width: bgWidth, height: bgHeight}).css(bgCSS);
27765        } catch(err) {
27766            // IE7 seems to trigger resize before the image is loaded.
27767            // This try/catch block is a hack to let it fail gracefully.
27768        }
27769
27770        return this;
27771      }
27772
27773      // Show the slide at a certain position
27774    , show: function (newIndex) {
27775
27776        // Validate index
27777        if (Math.abs(newIndex) > this.images.length - 1) {
27778          return;
27779        }
27780
27781        // Vars
27782        var self = this
27783          , oldImage = self.$wrap.find('img').addClass('deleteable')
27784          , evtOptions = { relatedTarget: self.$container[0] };
27785
27786        // Trigger the "before" event
27787        self.$container.trigger($.Event('backstretch.before', evtOptions), [self, newIndex]);
27788
27789        // Set the new index
27790        this.index = newIndex;
27791
27792        // Pause the slideshow
27793        clearInterval(self.interval);
27794
27795        // New image
27796        self.$img = $('<img />')
27797                      .css(styles.img)
27798                      .bind('load', function (e) {
27799                        var imgWidth = this.width || $(e.target).width()
27800                          , imgHeight = this.height || $(e.target).height();
27801
27802                        // Save the ratio
27803                        $(this).data('ratio', imgWidth / imgHeight);
27804
27805                        // Show the image, then delete the old one
27806                        // "speed" option has been deprecated, but we want backwards compatibilty
27807                        $(this).fadeIn(self.options.speed || self.options.fade, function () {
27808                          oldImage.remove();
27809
27810                          // Resume the slideshow
27811                          if (!self.paused) {
27812                            self.cycle();
27813                          }
27814
27815                          // Trigger the "after" and "show" events
27816                          // "show" is being deprecated
27817                          $(['after', 'show']).each(function () {
27818                            self.$container.trigger($.Event('backstretch.' + this, evtOptions), [self, newIndex]);
27819                          });
27820                        });
27821
27822                        // Resize
27823                        self.resize();
27824                      })
27825                      .appendTo(self.$wrap);
27826
27827        // Hack for IE img onload event
27828        self.$img.attr('src', self.images[newIndex]);
27829        return self;
27830      }
27831
27832    , next: function () {
27833        // Next slide
27834        return this.show(this.index < this.images.length - 1 ? this.index + 1 : 0);
27835      }
27836
27837    , prev: function () {
27838        // Previous slide
27839        return this.show(this.index === 0 ? this.images.length - 1 : this.index - 1);
27840      }
27841
27842    , pause: function () {
27843        // Pause the slideshow
27844        this.paused = true;
27845        return this;
27846      }
27847
27848    , resume: function () {
27849        // Resume the slideshow
27850        this.paused = false;
27851        this.next();
27852        return this;
27853      }
27854
27855    , cycle: function () {
27856        // Start/resume the slideshow
27857        if(this.images.length > 1) {
27858          // Clear the interval, just in case
27859          clearInterval(this.interval);
27860
27861          this.interval = setInterval($.proxy(function () {
27862            // Check for paused slideshow
27863            if (!this.paused) {
27864              this.next();
27865            }
27866          }, this), this.options.duration);
27867        }
27868        return this;
27869      }
27870
27871    , destroy: function (preserveBackground) {
27872        // Stop the resize events
27873        $(window).off('resize.backstretch orientationchange.backstretch');
27874
27875        // Clear the interval
27876        clearInterval(this.interval);
27877
27878        // Remove Backstretch
27879        if(!preserveBackground) {
27880          this.$wrap.remove();
27881        }
27882        this.$container.removeData('backstretch');
27883      }
27884  };
27885
27886  /* SUPPORTS FIXED POSITION?
27887   *
27888   * Based on code from jQuery Mobile 1.1.0
27889   * http://jquerymobile.com/
27890   *
27891   * In a nutshell, we need to figure out if fixed positioning is supported.
27892   * Unfortunately, this is very difficult to do on iOS, and usually involves
27893   * injecting content, scrolling the page, etc.. It's ugly.
27894   * jQuery Mobile uses this workaround. It's not ideal, but works.
27895   *
27896   * Modified to detect IE6
27897   * ========================= */
27898
27899  var supportsFixedPosition = (function () {
27900    var ua = navigator.userAgent
27901      , platform = navigator.platform
27902        // Rendering engine is Webkit, and capture major version
27903      , wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ )
27904      , wkversion = !!wkmatch && wkmatch[ 1 ]
27905      , ffmatch = ua.match( /Fennec\/([0-9]+)/ )
27906      , ffversion = !!ffmatch && ffmatch[ 1 ]
27907      , operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ )
27908      , omversion = !!operammobilematch && operammobilematch[ 1 ]
27909      , iematch = ua.match( /MSIE ([0-9]+)/ )
27910      , ieversion = !!iematch && iematch[ 1 ];
27911
27912    return !(
27913      // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
27914      ((platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1  || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534) ||
27915
27916      // Opera Mini
27917      (window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]") ||
27918      (operammobilematch && omversion < 7458) ||
27919
27920      //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
27921      (ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533) ||
27922
27923      // Firefox Mobile before 6.0 -
27924      (ffversion && ffversion < 6) ||
27925
27926      // WebOS less than 3
27927      ("palmGetResource" in window && wkversion && wkversion < 534) ||
27928
27929      // MeeGo
27930      (ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1) ||
27931
27932      // IE6
27933      (ieversion && ieversion <= 6)
27934    );
27935  }());
27936
27937}(jQuery, window));
27938/*
27939
27940Tooltipster 3.1.4 | 2014-03-20
27941A rockin' custom tooltip jQuery plugin
27942
27943Developed by Caleb Jacob under the MIT license http://opensource.org/licenses/MIT
27944
27945THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27946
27947*/
27948
27949
27950;(function ($, window, document) {
27951
27952	var pluginName = "tooltipster",
27953		defaults = {
27954			animation: 'fade',
27955			arrow: true,
27956			arrowColor: '',
27957			autoClose: true,
27958			content: null,
27959			contentAsHTML: false,
27960			contentCloning: true,
27961			delay: 200,
27962			fixedWidth: 0,
27963			maxWidth: 0,
27964			functionInit: function(origin, content) {},
27965			functionBefore: function(origin, continueTooltip) {
27966				continueTooltip();
27967			},
27968			functionReady: function(origin, tooltip) {},
27969			functionAfter: function(origin) {},
27970			icon: '(?)',
27971			iconCloning: true,
27972			iconDesktop: false,
27973			iconTouch: false,
27974			iconTheme: 'tooltipster-icon',
27975			interactive: false,
27976			interactiveTolerance: 350,
27977			offsetX: 0,
27978			offsetY: 0,
27979			onlyOne: false,
27980			position: 'top',
27981			positionTracker: false,
27982			speed: 350,
27983			timer: 0,
27984			theme: 'tooltipster-default',
27985			touchDevices: true,
27986			trigger: 'hover',
27987			updateAnimation: true
27988		};
27989
27990	function Plugin(element, options) {
27991
27992		// list of instance variables
27993
27994		this.bodyOverflowX;
27995		// stack of custom callbacks provided as parameters to API methods
27996		this.callbacks = {
27997			hide: [],
27998			show: []
27999		};
28000		this.checkInterval = null;
28001		// this will be the user content shown in the tooltip
28002		this.content;
28003		// this is the original element which is being applied the tooltipster plugin
28004		this.$el = $(element);
28005		// this will be the element which triggers the appearance of the tooltip on hover/click/custom events.
28006		// it will be the same as this.$el if icons are not used (see in the options), otherwise it will correspond to the created icon
28007		this.$elProxy;
28008		this.elProxyPosition;
28009		this.enabled = true;
28010		this.options = $.extend({}, defaults, options);
28011		this.mouseIsOverProxy = false;
28012		// a unique namespace per instance, for easy selective unbinding
28013		this.namespace = 'tooltipster-'+ Math.round(Math.random()*100000);
28014		// status can be either : appearing, shown, disappearing, hidden
28015		this.status = 'hidden';
28016		this.timerHide = null;
28017		this.timerShow = null;
28018		// this will be the tooltip element (jQuery wrapped HTML element)
28019		this.$tooltip;
28020
28021		// for backward compatibility
28022		this.options.iconTheme = this.options.iconTheme.replace('.', '');
28023		this.options.theme = this.options.theme.replace('.', '');
28024
28025		// launch
28026
28027		this.init();
28028	}
28029
28030	Plugin.prototype = {
28031
28032		init: function() {
28033
28034			var self = this;
28035
28036			// disable the plugin on old browsers (including IE7 and lower)
28037			if (document.querySelector) {
28038
28039				// note : the content is null (empty) by default and can stay that way if the plugin remains initialized but not fed any content. The tooltip will just not appear.
28040
28041				// if content is provided in the options, its has precedence over the title attribute. Remark : an empty string is considered content, only 'null' represents the absence of content.
28042				if (self.options.content !== null){
28043					self.setContent(self.options.content);
28044				}
28045				else {
28046					// the same remark as above applies : empty strings (like title="") are considered content and will be shown. Do not define any attribute at all if you want to initialize the plugin without content at start.
28047					var t = self.$el.attr('title');
28048					if(typeof t === 'undefined') t = null;
28049
28050					self.setContent(t);
28051				}
28052
28053				var c = self.options.functionInit.call(self.$el, self.$el, self.content);
28054				if(typeof c !== 'undefined') self.setContent(c);
28055
28056				self.$el
28057					// strip the title off of the element to prevent the default tooltips from popping up
28058					.removeAttr('title')
28059					// to be able to find all instances on the page later (upon window events in particular)
28060					.addClass('tooltipstered');
28061
28062				// detect if we're changing the tooltip origin to an icon
28063				// note about this condition : if the device has touch capability and self.options.iconTouch is false, you'll have no icons event though you may consider your device as a desktop if it also has a mouse. Not sure why someone would have this use case though.
28064				if ((!deviceHasTouchCapability && self.options.iconDesktop) || (deviceHasTouchCapability && self.options.iconTouch)) {
28065
28066					// TODO : the tooltip should be automatically be given an absolute position to be near the origin. Otherwise, when the origin is floating or what, it's going to be nowhere near it and disturb the position flow of the page elements. It will imply that the icon also detects when its origin moves, to follow it : not trivial.
28067					// Until it's done, the icon feature does not really make sense since the user still has most of the work to do by himself
28068
28069					// if the icon provided is in the form of a string
28070					if(typeof self.options.icon === 'string'){
28071						// wrap it in a span with the icon class
28072						self.$elProxy = $('<span class="'+ self.options.iconTheme +'"></span>');
28073						self.$elProxy.text(self.options.icon);
28074					}
28075					// if it is an object (sensible choice)
28076					else {
28077						// (deep) clone the object if iconCloning == true, to make sure every instance has its own proxy. We use the icon without wrapping, no need to. We do not give it a class either, as the user will undoubtedly style the object on his own and since our css properties may conflict with his own
28078						if (self.options.iconCloning) self.$elProxy = self.options.icon.clone(true);
28079						else self.$elProxy = self.options.icon;
28080					}
28081
28082					self.$elProxy.insertAfter(self.$el);
28083				}
28084				else {
28085					self.$elProxy = self.$el;
28086				}
28087
28088				// for 'click' and 'hover' triggers : bind on events to open the tooltip. Closing is now handled in showTooltipNow() because of its bindings.
28089				// Notes about touch events :
28090					// - mouseenter, mouseleave and clicks happen even on pure touch devices because they are emulated. deviceIsPureTouch() is a simple attempt to detect them.
28091					// - on hybrid devices, we do not prevent touch gesture from opening tooltips. It would be too complex to differentiate real mouse events from emulated ones.
28092					// - we check deviceIsPureTouch() at each event rather than prior to binding because the situation may change during browsing
28093				if (self.options.trigger == 'hover') {
28094
28095					// these binding are for mouse interaction only
28096					self.$elProxy
28097						.on('mouseenter.'+ self.namespace, function() {
28098							if (!deviceIsPureTouch() || self.options.touchDevices) {
28099								self.mouseIsOverProxy = true;
28100								self.showTooltip();
28101							}
28102						})
28103						.on('mouseleave.'+ self.namespace, function() {
28104							if (!deviceIsPureTouch() || self.options.touchDevices) {
28105								self.mouseIsOverProxy = false;
28106							}
28107						});
28108
28109					// for touch interaction only
28110					if (deviceHasTouchCapability && self.options.touchDevices) {
28111
28112						// for touch devices, we immediately display the tooltip because we cannot rely on mouseleave to handle the delay
28113						self.$elProxy.on('touchstart.'+ self.namespace, function() {
28114							self.showTooltipNow();
28115						});
28116					}
28117				}
28118				else if (self.options.trigger == 'click') {
28119
28120					// note : for touch devices, we do not bind on touchstart, we only rely on the emulated clicks (triggered by taps)
28121					self.$elProxy.on('click.'+ self.namespace, function() {
28122						if (!deviceIsPureTouch() || self.options.touchDevices) {
28123							self.showTooltip();
28124						}
28125					});
28126				}
28127			}
28128		},
28129
28130		// this function will schedule the opening of the tooltip after the delay, if there is one
28131		showTooltip: function() {
28132
28133			var self = this;
28134
28135			if (self.status != 'shown' && self.status != 'appearing') {
28136
28137				if (self.options.delay) {
28138					self.timerShow = setTimeout(function(){
28139
28140						// for hover trigger, we check if the mouse is still over the proxy, otherwise we do not show anything
28141						if (self.options.trigger == 'click' || (self.options.trigger == 'hover' && self.mouseIsOverProxy)) {
28142							self.showTooltipNow();
28143						}
28144					}, self.options.delay);
28145				}
28146				else self.showTooltipNow();
28147			}
28148		},
28149
28150		// this function will open the tooltip right away
28151		showTooltipNow: function(callback) {
28152
28153			var self = this;
28154
28155			// call our constructor custom function before continuing
28156			self.options.functionBefore.call(self.$el, self.$el, function() {
28157
28158				// continue only if the tooltip is enabled and has any content
28159				if (self.enabled && self.content !== null) {
28160
28161					// save the method callback and cancel hide method callbacks
28162					if (callback) self.callbacks.show.push(callback);
28163					self.callbacks.hide = [];
28164
28165					//get rid of any appearance timer
28166					clearTimeout(self.timerShow);
28167					self.timerShow = null;
28168					clearTimeout(self.timerHide);
28169					self.timerHide = null;
28170
28171					// if we only want one tooltip open at a time, close all auto-closing tooltips currently open and not already disappearing
28172					if (self.options.onlyOne) {
28173						$('.tooltipstered').not(self.$el).each(function(i,el) {
28174
28175							// we have to use the public methods here
28176							var $el = $(el),
28177								s = $el[pluginName]('status'),
28178								ac = $el[pluginName]('option', 'autoClose');
28179
28180							if (s !== 'hidden' && s !== 'disappearing' && ac) {
28181								$el[pluginName]('hide');
28182							}
28183						});
28184					}
28185
28186					var finish = function() {
28187						self.status = 'shown';
28188
28189						// trigger any show method custom callbacks and reset them
28190						$.each(self.callbacks.show, function(i,c) { c.call(self.$el); });
28191						self.callbacks.show = [];
28192					};
28193
28194					// if this origin already has its tooltip open
28195					if (self.status !== 'hidden') {
28196
28197						// the timer (if any) will start (or restart) right now
28198						var extraTime = 0;
28199
28200						// if it was disappearing, cancel that
28201						if (self.status === 'disappearing') {
28202
28203							self.status = 'appearing';
28204
28205							if (supportsTransitions()) {
28206
28207								self.$tooltip
28208									.clearQueue()
28209									.removeClass('tooltipster-dying')
28210									.addClass('tooltipster-'+ self.options.animation +'-show');
28211
28212								if (self.options.speed > 0) self.$tooltip.delay(self.options.speed);
28213
28214								self.$tooltip.queue(finish);
28215							}
28216							else {
28217								// in case the tooltip was currently fading out, bring it back to life
28218								self.$tooltip
28219									.stop()
28220									.fadeIn(finish);
28221							}
28222						}
28223						// if the tooltip is already open, we still need to trigger the method custom callback
28224						else if(self.status === 'shown') {
28225							finish();
28226						}
28227					}
28228					// if the tooltip isn't already open, open that sucker up!
28229					else {
28230
28231						self.status = 'appearing';
28232
28233						// the timer (if any) will start when the tooltip has fully appeared after its transition
28234						var extraTime = self.options.speed;
28235
28236						// disable horizontal scrollbar to keep overflowing tooltips from jacking with it and then restore it to its previous value
28237						self.bodyOverflowX = $('body').css('overflow-x');
28238						$('body').css('overflow-x', 'hidden');
28239
28240						// get some other settings related to building the tooltip
28241						var animation = 'tooltipster-' + self.options.animation,
28242							animationSpeed = '-webkit-transition-duration: '+ self.options.speed +'ms; -webkit-animation-duration: '+ self.options.speed +'ms; -moz-transition-duration: '+ self.options.speed +'ms; -moz-animation-duration: '+ self.options.speed +'ms; -o-transition-duration: '+ self.options.speed +'ms; -o-animation-duration: '+ self.options.speed +'ms; -ms-transition-duration: '+ self.options.speed +'ms; -ms-animation-duration: '+ self.options.speed +'ms; transition-duration: '+ self.options.speed +'ms; animation-duration: '+ self.options.speed +'ms;',
28243							fixedWidth = self.options.fixedWidth > 0 ? 'width:'+ Math.round(self.options.fixedWidth) +'px;' : '',
28244							maxWidth = self.options.maxWidth > 0 ? 'max-width:'+ Math.round(self.options.maxWidth) +'px;' : '',
28245							pointerEvents = self.options.interactive ? 'pointer-events: auto;' : '';
28246
28247						// build the base of our tooltip
28248						self.$tooltip = $('<div class="tooltipster-base '+ self.options.theme +'" style="'+ fixedWidth +' '+ maxWidth +' '+ pointerEvents +' '+ animationSpeed +'"><div class="tooltipster-content"></div></div>');
28249
28250						// only add the animation class if the user has a browser that supports animations
28251						if (supportsTransitions()) self.$tooltip.addClass(animation);
28252
28253						// insert the content
28254						self.insertContent();
28255
28256						// attach
28257						self.$tooltip.appendTo('body');
28258
28259						// do all the crazy calculations and positioning
28260						self.positionTooltip();
28261
28262						// call our custom callback since the content of the tooltip is now part of the DOM
28263						self.options.functionReady.call(self.$el, self.$el, self.$tooltip);
28264
28265						// animate in the tooltip
28266						if (supportsTransitions()) {
28267
28268							self.$tooltip.addClass(animation + '-show');
28269
28270							if(self.options.speed > 0) self.$tooltip.delay(self.options.speed);
28271
28272							self.$tooltip.queue(finish);
28273						}
28274						else {
28275							self.$tooltip.css('display', 'none').fadeIn(self.options.speed, finish);
28276						}
28277
28278						// will check if our tooltip origin is removed while the tooltip is shown
28279						self.setCheckInterval();
28280
28281						// reposition on scroll (otherwise position:fixed element's tooltips will move away form their origin) and on resize (in case position can/has to be changed)
28282						$(window).on('scroll.'+ self.namespace +' resize.'+ self.namespace, function() {
28283							self.positionTooltip();
28284						});
28285
28286						// auto-close bindings
28287						if (self.options.autoClose) {
28288
28289							// in case a listener is already bound for autoclosing (mouse or touch, hover or click), unbind it first
28290							$('body').off('.'+ self.namespace);
28291
28292							// here we'll have to set different sets of bindings for both touch and mouse
28293							if (self.options.trigger == 'hover') {
28294
28295								// if the user touches the body, hide
28296								if (deviceHasTouchCapability) {
28297									// timeout 0 : explanation below in click section
28298									setTimeout(function() {
28299										// we don't want to bind on click here because the initial touchstart event has not yet triggered its click event, which is thus about to happen
28300										$('body').on('touchstart.'+ self.namespace, function() {
28301											self.hideTooltip();
28302										});
28303									}, 0);
28304								}
28305
28306								// if we have to allow interaction
28307								if (self.options.interactive) {
28308
28309									// touch events inside the tooltip must not close it
28310									if (deviceHasTouchCapability) {
28311										self.$tooltip.on('touchstart.'+ self.namespace, function(event) {
28312											event.stopPropagation();
28313										});
28314									}
28315
28316									// as for mouse interaction, we get rid of the tooltip only after the mouse has spent some time out of it
28317									var tolerance = null;
28318
28319									self.$elProxy.add(self.$tooltip)
28320										// hide after some time out of the proxy and the tooltip
28321										.on('mouseleave.'+ self.namespace + '-autoClose', function() {
28322											clearTimeout(tolerance);
28323											tolerance = setTimeout(function(){
28324												self.hideTooltip();
28325											}, self.options.interactiveTolerance);
28326										})
28327										// suspend timeout when the mouse is over the proxy or the tooltip
28328										.on('mouseenter.'+ self.namespace + '-autoClose', function() {
28329											clearTimeout(tolerance);
28330										});
28331								}
28332								// if this is a non-interactive tooltip, get rid of it if the mouse leaves
28333								else {
28334									self.$elProxy.on('mouseleave.'+ self.namespace + '-autoClose', function() {
28335										self.hideTooltip();
28336									});
28337								}
28338							}
28339							// here we'll set the same bindings for both clicks and touch on the body to hide the tooltip
28340							else if(self.options.trigger == 'click'){
28341
28342								// use a timeout to prevent immediate closing if the method was called on a click event and if options.delay == 0 (because of bubbling)
28343								setTimeout(function() {
28344									$('body').on('click.'+ self.namespace +' touchstart.'+ self.namespace, function() {
28345										self.hideTooltip();
28346									});
28347								}, 0);
28348
28349								// if interactive, we'll stop the events that were emitted from inside the tooltip to stop autoClosing
28350								if (self.options.interactive) {
28351
28352									// note : the touch events will just not be used if the plugin is not enabled on touch devices
28353									self.$tooltip.on('click.'+ self.namespace +' touchstart.'+ self.namespace, function(event) {
28354										event.stopPropagation();
28355									});
28356								}
28357							}
28358						}
28359					}
28360
28361					// if we have a timer set, let the countdown begin
28362					if (self.options.timer > 0) {
28363
28364						self.timerHide = setTimeout(function() {
28365							self.timerHide = null;
28366							self.hideTooltip();
28367						}, self.options.timer + extraTime);
28368					}
28369				}
28370			});
28371		},
28372
28373		setCheckInterval: function(){
28374
28375			var self = this;
28376
28377			self.checkInterval = setInterval(function() {
28378
28379				// if the tooltip and/or its interval should be stopped
28380				if (
28381						// if the origin has been removed
28382						$('body').find(self.$el).length === 0
28383						// if the elProxy has been removed
28384					||	$('body').find(self.$elProxy).length === 0
28385						// if the tooltip has been closed
28386					||	self.status == 'hidden'
28387						// if the tooltip has somehow been removed
28388					||	$('body').find(self.$tooltip).length === 0
28389				) {
28390					// remove the tooltip if it's still here
28391					if (self.status == 'shown' || self.status == 'appearing') self.hideTooltip();
28392
28393					// clear this interval as it is no longer necessary
28394					self.cancelCheckInterval();
28395				}
28396				// if everything is alright
28397				else {
28398					// compare the former and current positions of the elProxy to reposition the tooltip if need be
28399					if(self.options.positionTracker){
28400
28401						var p = self.positionInfo(self.$elProxy),
28402							identical = false;
28403
28404						// compare size first (a change requires repositioning too)
28405						if(areEqual(p.dimension, self.elProxyPosition.dimension)){
28406
28407							// for elements with a fixed position, we track the top and left properties (relative to window)
28408							if(self.$elProxy.css('position') === 'fixed'){
28409								if(areEqual(p.position, self.elProxyPosition.position)) identical = true;
28410							}
28411							// otherwise, track total offset (relative to document)
28412							else {
28413								if(areEqual(p.offset, self.elProxyPosition.offset)) identical = true;
28414							}
28415						}
28416
28417						if(!identical){
28418							self.positionTooltip();
28419						}
28420					}
28421				}
28422			}, 200);
28423		},
28424
28425		cancelCheckInterval: function(){
28426			clearInterval(this.checkInterval);
28427			// clean delete
28428			this.checkInterval = null;
28429		},
28430
28431		hideTooltip: function(callback) {
28432
28433			var self = this;
28434
28435			// save the method custom callback and cancel any show method custom callbacks
28436			if (callback) self.callbacks.hide.push(callback);
28437			self.callbacks.show = [];
28438
28439			// get rid of any appearance timeout
28440			clearTimeout(self.timerShow);
28441			self.timerShow = null;
28442			clearTimeout(self.timerHide);
28443			self.timerHide = null;
28444
28445			var finishCallbacks = function() {
28446				// trigger any hide method custom callbacks and reset them
28447				$.each(self.callbacks.hide, function(i,c) { c.call(self.$el); });
28448				self.callbacks.hide = [];
28449			};
28450
28451			// hide
28452			if (self.status == 'shown' || self.status == 'appearing') {
28453
28454				self.status = 'disappearing';
28455
28456				var finish = function() {
28457
28458					self.status = 'hidden';
28459
28460					// detach our content object first, so the next jQuery's remove() call does not unbind its event handlers
28461					if (typeof self.content == 'object' && self.content !== null) {
28462						self.content.detach();
28463					}
28464
28465					self.$tooltip.remove();
28466					self.$tooltip = null;
28467
28468					// unbind orientationchange, scroll and resize listeners
28469					$(window).off('.'+ self.namespace);
28470
28471					$('body')
28472						// unbind any auto-closing click/touch listeners
28473						.off('.'+ self.namespace)
28474						.css('overflow-x', self.bodyOverflowX);
28475
28476					// unbind any auto-closing click/touch listeners
28477					$('body').off('.'+ self.namespace);
28478
28479					// unbind any auto-closing hover listeners
28480					self.$elProxy.off('.'+ self.namespace + '-autoClose');
28481
28482					// call our constructor custom callback function
28483					self.options.functionAfter.call(self.$el, self.$el);
28484
28485					// call our method custom callbacks functions
28486					finishCallbacks();
28487				};
28488
28489				if (supportsTransitions()) {
28490
28491					self.$tooltip
28492						.clearQueue()
28493						.removeClass('tooltipster-' + self.options.animation + '-show')
28494						// for transitions only
28495						.addClass('tooltipster-dying');
28496
28497					if(self.options.speed > 0) self.$tooltip.delay(self.options.speed);
28498
28499					self.$tooltip.queue(finish);
28500				}
28501				else {
28502					self.$tooltip
28503						.stop()
28504						.fadeOut(self.options.speed, finish);
28505				}
28506			}
28507			// if the tooltip is already hidden, we still need to trigger the method custom callback
28508			else if(self.status == 'hidden') {
28509				finishCallbacks();
28510			}
28511		},
28512
28513		setContent: function(content) {
28514			// clone if asked. Cloning the object makes sure that each instance has its own version of the content (in case a same object were provided for several instances)
28515			// reminder : typeof null === object
28516			if (typeof content === 'object' && content !== null && this.options.contentCloning) {
28517				content = content.clone(true);
28518			}
28519			this.content = content;
28520		},
28521
28522		insertContent: function() {
28523
28524			var self = this,
28525				$d = this.$tooltip.find('.tooltipster-content');
28526
28527			if (typeof self.content === 'string' && !self.options.contentAsHTML) {
28528				$d.text(self.content);
28529			}
28530			else {
28531				$d
28532					.empty()
28533					.append(self.content);
28534			}
28535		},
28536
28537		updateTooltip: function(content) {
28538
28539			var self = this;
28540
28541			// change the content
28542			self.setContent(content);
28543
28544			if (self.content !== null) {
28545
28546				// update the tooltip if it is open
28547				if (self.status !== 'hidden') {
28548
28549					// reset the content in the tooltip
28550					self.insertContent();
28551
28552					// reposition and resize the tooltip
28553					self.positionTooltip();
28554
28555					// if we want to play a little animation showing the content changed
28556					if (self.options.updateAnimation) {
28557
28558						if (supportsTransitions()) {
28559
28560							self.$tooltip.css({
28561								'width': '',
28562								'-webkit-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
28563								'-moz-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
28564								'-o-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
28565								'-ms-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
28566								'transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms'
28567							}).addClass('tooltipster-content-changing');
28568
28569							// reset the CSS transitions and finish the change animation
28570							setTimeout(function() {
28571
28572								if(self.status != 'hidden'){
28573
28574									self.$tooltip.removeClass('tooltipster-content-changing');
28575
28576									// after the changing animation has completed, reset the CSS transitions
28577									setTimeout(function() {
28578
28579										if(self.status !== 'hidden'){
28580											self.$tooltip.css({
28581												'-webkit-transition': self.options.speed + 'ms',
28582												'-moz-transition': self.options.speed + 'ms',
28583												'-o-transition': self.options.speed + 'ms',
28584												'-ms-transition': self.options.speed + 'ms',
28585												'transition': self.options.speed + 'ms'
28586											});
28587										}
28588									}, self.options.speed);
28589								}
28590							}, self.options.speed);
28591						}
28592						else {
28593							self.$tooltip.fadeTo(self.options.speed, 0.5, function() {
28594								if(self.status != 'hidden'){
28595									self.$tooltip.fadeTo(self.options.speed, 1);
28596								}
28597							});
28598						}
28599					}
28600				}
28601			}
28602			else {
28603				self.hideTooltip();
28604			}
28605		},
28606
28607		positionInfo: function($el){
28608			return {
28609				dimension: {
28610					height: $el.outerHeight(false),
28611					width: $el.outerWidth(false)
28612				},
28613				offset: $el.offset(),
28614				position: {
28615					left: parseInt($el.css('left')),
28616					top: parseInt($el.css('top'))
28617				}
28618			};
28619		},
28620
28621		positionTooltip: function() {
28622
28623			var self = this;
28624
28625			// in case the tooltip has been removed from DOM manually
28626			if ($('body').find(self.$tooltip).length !== 0) {
28627
28628				// reset width
28629				self.$tooltip.css('width', '');
28630
28631				// find variables to determine placement
28632				self.elProxyPosition = self.positionInfo(self.$elProxy);
28633				var arrowReposition = null,
28634					windowWidth = $(window).width(),
28635					// shorthand
28636					proxy = self.elProxyPosition,
28637					tooltipWidth = self.$tooltip.outerWidth(false),
28638					tooltipInnerWidth = self.$tooltip.innerWidth() + 1, // this +1 stops FireFox from sometimes forcing an additional text line
28639					tooltipHeight = self.$tooltip.outerHeight(false);
28640
28641				// if this is an <area> tag inside a <map>, all hell breaks loose. Recalculate all the measurements based on coordinates
28642				if (self.$elProxy.is('area')) {
28643					var areaShape = self.$elProxy.attr('shape'),
28644						mapName = self.$elProxy.parent().attr('name'),
28645						map = $('img[usemap="#'+ mapName +'"]'),
28646						mapOffsetLeft = map.offset().left,
28647						mapOffsetTop = map.offset().top,
28648						areaMeasurements = self.$elProxy.attr('coords') !== undefined ? self.$elProxy.attr('coords').split(',') : undefined;
28649
28650					if (areaShape == 'circle') {
28651						var areaLeft = parseInt(areaMeasurements[0]),
28652							areaTop = parseInt(areaMeasurements[1]),
28653							areaWidth = parseInt(areaMeasurements[2]);
28654						proxy.dimension.height = areaWidth * 2;
28655						proxy.dimension.width = areaWidth * 2;
28656						proxy.offset.top = mapOffsetTop + areaTop - areaWidth;
28657						proxy.offset.left = mapOffsetLeft + areaLeft - areaWidth;
28658					}
28659					else if (areaShape == 'rect') {
28660						var areaLeft = parseInt(areaMeasurements[0]),
28661							areaTop = parseInt(areaMeasurements[1]),
28662							areaRight = parseInt(areaMeasurements[2]),
28663							areaBottom = parseInt(areaMeasurements[3]);
28664						proxy.dimension.height = areaBottom - areaTop;
28665						proxy.dimension.width = areaRight - areaLeft;
28666						proxy.offset.top = mapOffsetTop + areaTop;
28667						proxy.offset.left = mapOffsetLeft + areaLeft;
28668					}
28669					else if (areaShape == 'poly') {
28670						var areaXs = [],
28671							areaYs = [],
28672							areaSmallestX = 0,
28673							areaSmallestY = 0,
28674							areaGreatestX = 0,
28675							areaGreatestY = 0,
28676							arrayAlternate = 'even';
28677
28678						for (var i = 0; i < areaMeasurements.length; i++) {
28679							var areaNumber = parseInt(areaMeasurements[i]);
28680
28681							if (arrayAlternate == 'even') {
28682								if (areaNumber > areaGreatestX) {
28683									areaGreatestX = areaNumber;
28684									if (i === 0) {
28685										areaSmallestX = areaGreatestX;
28686									}
28687								}
28688
28689								if (areaNumber < areaSmallestX) {
28690									areaSmallestX = areaNumber;
28691								}
28692
28693								arrayAlternate = 'odd';
28694							}
28695							else {
28696								if (areaNumber > areaGreatestY) {
28697									areaGreatestY = areaNumber;
28698									if (i == 1) {
28699										areaSmallestY = areaGreatestY;
28700									}
28701								}
28702
28703								if (areaNumber < areaSmallestY) {
28704									areaSmallestY = areaNumber;
28705								}
28706
28707								arrayAlternate = 'even';
28708							}
28709						}
28710
28711						proxy.dimension.height = areaGreatestY - areaSmallestY;
28712						proxy.dimension.width = areaGreatestX - areaSmallestX;
28713						proxy.offset.top = mapOffsetTop + areaSmallestY;
28714						proxy.offset.left = mapOffsetLeft + areaSmallestX;
28715					}
28716					else {
28717						proxy.dimension.height = map.outerHeight(false);
28718						proxy.dimension.width = map.outerWidth(false);
28719						proxy.offset.top = mapOffsetTop;
28720						proxy.offset.left = mapOffsetLeft;
28721					}
28722				}
28723
28724				// hardcoding the width and removing the padding fixed an issue with the tooltip width collapsing when the window size is small
28725				if(self.options.fixedWidth === 0) {
28726					self.$tooltip.css({
28727						'width': Math.round(tooltipInnerWidth) + 'px',
28728						'padding-left': '0px',
28729						'padding-right': '0px'
28730					});
28731				}
28732
28733				// our function and global vars for positioning our tooltip
28734				var myLeft = 0,
28735					myLeftMirror = 0,
28736					myTop = 0,
28737					offsetY = parseInt(self.options.offsetY),
28738					offsetX = parseInt(self.options.offsetX),
28739					// this is the arrow position that will eventually be used. It may differ from the position option if the tooltip cannot be displayed in this position
28740					practicalPosition = self.options.position;
28741
28742				// a function to detect if the tooltip is going off the screen horizontally. If so, reposition the crap out of it!
28743				function dontGoOffScreenX() {
28744
28745					var windowLeft = $(window).scrollLeft();
28746
28747					// if the tooltip goes off the left side of the screen, line it up with the left side of the window
28748					if((myLeft - windowLeft) < 0) {
28749						arrowReposition = myLeft - windowLeft;
28750						myLeft = windowLeft;
28751					}
28752
28753					// if the tooltip goes off the right of the screen, line it up with the right side of the window
28754					if (((myLeft + tooltipWidth) - windowLeft) > windowWidth) {
28755						arrowReposition = myLeft - ((windowWidth + windowLeft) - tooltipWidth);
28756						myLeft = (windowWidth + windowLeft) - tooltipWidth;
28757					}
28758				}
28759
28760				// a function to detect if the tooltip is going off the screen vertically. If so, switch to the opposite!
28761				function dontGoOffScreenY(switchTo, switchFrom) {
28762					// if it goes off the top off the page
28763					if(((proxy.offset.top - $(window).scrollTop() - tooltipHeight - offsetY - 12) < 0) && (switchFrom.indexOf('top') > -1)) {
28764						practicalPosition = switchTo;
28765					}
28766
28767					// if it goes off the bottom of the page
28768					if (((proxy.offset.top + proxy.dimension.height + tooltipHeight + 12 + offsetY) > ($(window).scrollTop() + $(window).height())) && (switchFrom.indexOf('bottom') > -1)) {
28769						practicalPosition = switchTo;
28770						myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
28771					}
28772				}
28773
28774				if(practicalPosition == 'top') {
28775					var leftDifference = (proxy.offset.left + tooltipWidth) - (proxy.offset.left + proxy.dimension.width);
28776					myLeft = (proxy.offset.left + offsetX) - (leftDifference / 2);
28777					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
28778					dontGoOffScreenX();
28779					dontGoOffScreenY('bottom', 'top');
28780				}
28781
28782				if(practicalPosition == 'top-left') {
28783					myLeft = proxy.offset.left + offsetX;
28784					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
28785					dontGoOffScreenX();
28786					dontGoOffScreenY('bottom-left', 'top-left');
28787				}
28788
28789				if(practicalPosition == 'top-right') {
28790					myLeft = (proxy.offset.left + proxy.dimension.width + offsetX) - tooltipWidth;
28791					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
28792					dontGoOffScreenX();
28793					dontGoOffScreenY('bottom-right', 'top-right');
28794				}
28795
28796				if(practicalPosition == 'bottom') {
28797					var leftDifference = (proxy.offset.left + tooltipWidth) - (proxy.offset.left + proxy.dimension.width);
28798					myLeft = proxy.offset.left - (leftDifference / 2) + offsetX;
28799					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
28800					dontGoOffScreenX();
28801					dontGoOffScreenY('top', 'bottom');
28802				}
28803
28804				if(practicalPosition == 'bottom-left') {
28805					myLeft = proxy.offset.left + offsetX;
28806					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
28807					dontGoOffScreenX();
28808					dontGoOffScreenY('top-left', 'bottom-left');
28809				}
28810
28811				if(practicalPosition == 'bottom-right') {
28812					myLeft = (proxy.offset.left + proxy.dimension.width + offsetX) - tooltipWidth;
28813					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
28814					dontGoOffScreenX();
28815					dontGoOffScreenY('top-right', 'bottom-right');
28816				}
28817
28818				if(practicalPosition == 'left') {
28819					myLeft = proxy.offset.left - offsetX - tooltipWidth - 12;
28820					myLeftMirror = proxy.offset.left + offsetX + proxy.dimension.width + 12;
28821					var topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
28822					myTop = proxy.offset.top - (topDifference / 2) - offsetY;
28823
28824					// if the tooltip goes off boths sides of the page
28825					if((myLeft < 0) && ((myLeftMirror + tooltipWidth) > windowWidth)) {
28826						var borderWidth = parseFloat(self.$tooltip.css('border-width')) * 2,
28827							newWidth = (tooltipWidth + myLeft) - borderWidth;
28828						self.$tooltip.css('width', newWidth + 'px');
28829
28830						tooltipHeight = self.$tooltip.outerHeight(false);
28831						myLeft = proxy.offset.left - offsetX - newWidth - 12 - borderWidth;
28832						topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
28833						myTop = proxy.offset.top - (topDifference / 2) - offsetY;
28834					}
28835
28836					// if it only goes off one side, flip it to the other side
28837					else if(myLeft < 0) {
28838						myLeft = proxy.offset.left + offsetX + proxy.dimension.width + 12;
28839						arrowReposition = 'left';
28840					}
28841				}
28842
28843				if(practicalPosition == 'right') {
28844					myLeft = proxy.offset.left + offsetX + proxy.dimension.width + 12;
28845					myLeftMirror = proxy.offset.left - offsetX - tooltipWidth - 12;
28846					var topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
28847					myTop = proxy.offset.top - (topDifference / 2) - offsetY;
28848
28849					// if the tooltip goes off boths sides of the page
28850					if(((myLeft + tooltipWidth) > windowWidth) && (myLeftMirror < 0)) {
28851						var borderWidth = parseFloat(self.$tooltip.css('border-width')) * 2,
28852							newWidth = (windowWidth - myLeft) - borderWidth;
28853						self.$tooltip.css('width', newWidth + 'px');
28854
28855						tooltipHeight = self.$tooltip.outerHeight(false);
28856						topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
28857						myTop = proxy.offset.top - (topDifference / 2) - offsetY;
28858					}
28859
28860					// if it only goes off one side, flip it to the other side
28861					else if((myLeft + tooltipWidth) > windowWidth) {
28862						myLeft = proxy.offset.left - offsetX - tooltipWidth - 12;
28863						arrowReposition = 'right';
28864					}
28865				}
28866
28867				// if arrow is set true, style it and append it
28868				if (self.options.arrow) {
28869
28870					var arrowClass = 'tooltipster-arrow-' + practicalPosition;
28871
28872					// set color of the arrow
28873					if(self.options.arrowColor.length < 1) {
28874						var arrowColor = self.$tooltip.css('background-color');
28875					}
28876					else {
28877						var arrowColor = self.options.arrowColor;
28878					}
28879
28880					// if the tooltip was going off the page and had to re-adjust, we need to update the arrow's position
28881					if (!arrowReposition) {
28882						arrowReposition = '';
28883					}
28884					else if (arrowReposition == 'left') {
28885						arrowClass = 'tooltipster-arrow-right';
28886						arrowReposition = '';
28887					}
28888					else if (arrowReposition == 'right') {
28889						arrowClass = 'tooltipster-arrow-left';
28890						arrowReposition = '';
28891					}
28892					else {
28893						arrowReposition = 'left:'+ Math.round(arrowReposition) +'px;';
28894					}
28895
28896					// building the logic to create the border around the arrow of the tooltip
28897					if ((practicalPosition == 'top') || (practicalPosition == 'top-left') || (practicalPosition == 'top-right')) {
28898						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-bottom-width')),
28899							tooltipBorderColor = self.$tooltip.css('border-bottom-color');
28900					}
28901					else if ((practicalPosition == 'bottom') || (practicalPosition == 'bottom-left') || (practicalPosition == 'bottom-right')) {
28902						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-top-width')),
28903							tooltipBorderColor = self.$tooltip.css('border-top-color');
28904					}
28905					else if (practicalPosition == 'left') {
28906						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-right-width')),
28907							tooltipBorderColor = self.$tooltip.css('border-right-color');
28908					}
28909					else if (practicalPosition == 'right') {
28910						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-left-width')),
28911							tooltipBorderColor = self.$tooltip.css('border-left-color');
28912					}
28913					else {
28914						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-bottom-width')),
28915							tooltipBorderColor = self.$tooltip.css('border-bottom-color');
28916					}
28917
28918					if (tooltipBorderWidth > 1) {
28919						tooltipBorderWidth++;
28920					}
28921
28922					var arrowBorder = '';
28923					if (tooltipBorderWidth !== 0) {
28924						var arrowBorderSize = '',
28925							arrowBorderColor = 'border-color: '+ tooltipBorderColor +';';
28926						if (arrowClass.indexOf('bottom') !== -1) {
28927							arrowBorderSize = 'margin-top: -'+ Math.round(tooltipBorderWidth) +'px;';
28928						}
28929						else if (arrowClass.indexOf('top') !== -1) {
28930							arrowBorderSize = 'margin-bottom: -'+ Math.round(tooltipBorderWidth) +'px;';
28931						}
28932						else if (arrowClass.indexOf('left') !== -1) {
28933							arrowBorderSize = 'margin-right: -'+ Math.round(tooltipBorderWidth) +'px;';
28934						}
28935						else if (arrowClass.indexOf('right') !== -1) {
28936							arrowBorderSize = 'margin-left: -'+ Math.round(tooltipBorderWidth) +'px;';
28937						}
28938						arrowBorder = '<span class="tooltipster-arrow-border" style="'+ arrowBorderSize +' '+ arrowBorderColor +';"></span>';
28939					}
28940
28941					// if the arrow already exists, remove and replace it
28942					self.$tooltip.find('.tooltipster-arrow').remove();
28943
28944					// build out the arrow and append it
28945					var arrowConstruct = '<div class="'+ arrowClass +' tooltipster-arrow" style="'+ arrowReposition +'">'+ arrowBorder +'<span style="border-color:'+ arrowColor +';"></span></div>';
28946					self.$tooltip.append(arrowConstruct);
28947				}
28948
28949				// position the tooltip
28950				self.$tooltip.css({'top': Math.round(myTop) + 'px', 'left': Math.round(myLeft) + 'px'});
28951			}
28952		}
28953	};
28954
28955	$.fn[pluginName] = function () {
28956
28957		// for using in closures
28958		var args = arguments;
28959
28960		// if we are not in the context of jQuery wrapped HTML element(s) :
28961		// this happens when calling static methods in the form $.fn.tooltipster('methodName'), or when calling $(sel).tooltipster('methodName or options') where $(sel) does not match anything
28962		if (this.length === 0) {
28963
28964			// if the first argument is a method name
28965			if (typeof args[0] === 'string') {
28966
28967				var methodIsStatic = true;
28968
28969				// list static methods here (usable by calling $.fn.tooltipster('methodName');)
28970				switch (args[0]) {
28971
28972					case 'setDefaults':
28973						// change default options for all future instances
28974						$.extend(defaults, args[1]);
28975						break;
28976
28977					default:
28978						methodIsStatic = false;
28979						break;
28980				}
28981
28982				// $.fn.tooltipster('methodName') calls will return true
28983				if (methodIsStatic) return true;
28984				// $(sel).tooltipster('methodName') calls will return the list of objects event though it's empty because chaining should work on empty lists
28985				else return this;
28986			}
28987			// the first argument is undefined or an object of options : we are initalizing but there is no element matched by selector
28988			else {
28989				// still chainable : same as above
28990				return this;
28991			}
28992		}
28993		// this happens when calling $(sel).tooltipster('methodName or options') where $(sel) matches one or more elements
28994		else {
28995
28996			// method calls
28997			if (typeof args[0] === 'string') {
28998
28999				var v = '#*$~&';
29000
29001				this.each(function() {
29002
29003					// self represent the instance of the tooltipster plugin associated to the current HTML object of the loop
29004					var self = $(this).data('tooltipster');
29005
29006					// if the current element is a tooltipster instance
29007					if(self){
29008						switch (args[0]) {
29009
29010							case 'content':
29011							// 'update' is deprecated but kept for retrocompatibility
29012							case 'update':
29013								// getter method
29014								if(typeof args[1] === 'undefined'){
29015									v = self.content;
29016									// return false to stop .each iteration on the first element matched by the selector
29017									return false;
29018								}
29019								// setter method
29020								else {
29021									self.updateTooltip(args[1]);
29022									break;
29023								}
29024
29025							case 'destroy':
29026								self.hideTooltip();
29027
29028								if(self.$el[0] !== self.$elProxy[0]) self.$elProxy.remove();
29029
29030								// old school technique when outerHTML is not supported
29031								var stringifiedContent = (typeof self.content === 'string') ? self.content : $('<div></div>').append(self.content).html();
29032
29033								self.$el
29034									.removeClass('tooltipstered')
29035									.attr('title', stringifiedContent)
29036									.removeData('tooltipster')
29037									.off('.'+ self.namespace);
29038								break;
29039
29040							case 'disable':
29041								// hide first, in case the tooltip would not disappear on its own (autoClose false)
29042								self.hideTooltip();
29043								self.enabled = false;
29044								break;
29045
29046							case 'elementIcon':
29047								v = (self.$el[0] !== self.$elProxy[0]) ? self.$elProxy[0] : undefined;
29048								// return false : same as above
29049								return false;
29050
29051							case 'elementTooltip':
29052								v = self.$tooltip ? self.$tooltip[0] : undefined;
29053								// return false : same as above
29054								return false;
29055
29056							case 'enable':
29057								self.enabled = true;
29058								break;
29059
29060							case 'hide':
29061								self.hideTooltip(args[1]);
29062								break;
29063
29064							// for internal use only
29065							case 'option':
29066								v = self.options[args[1]];
29067								return false;
29068
29069							case 'reposition':
29070								self.positionTooltip();
29071								break;
29072
29073							case 'show':
29074								self.showTooltipNow(args[1]);
29075								break;
29076
29077							// for internal use use only, not part of the public API
29078							case 'status':
29079								v = self.status;
29080								return false;
29081
29082							default:
29083								throw new Error('Unknown method .tooltipster("' + args[0] + '")');
29084								break;
29085						}
29086					}
29087					else {
29088						throw new Error('You called Tooltipster\'s "' + args[0] + '" method on an uninitialized element');
29089					}
29090				});
29091
29092				return (v !== '#*$~&') ? v : this;
29093			}
29094			// first argument is undefined or an object : the tooltip is initializing
29095			else {
29096				// initialize a tooltipster instance for each element if it doesn't already have one, and attach the object to it
29097				return this.each(function () {
29098
29099					if (!$(this).data('tooltipster')) {
29100						$(this).data('tooltipster', new Plugin( this, args[0] ));
29101					}
29102				});
29103			}
29104		}
29105	};
29106
29107	// quick & dirty compare function (not bijective nor multidimensional)
29108	function areEqual(a,b) {
29109		var same = true;
29110		$.each(a, function(i, el){
29111			if(typeof b[i] === 'undefined' || a[i] !== b[i]){
29112				same = false;
29113				return false;
29114			}
29115		});
29116		return same;
29117	}
29118
29119	// detect if this device can trigger touch events
29120	var deviceHasTouchCapability = !!('ontouchstart' in window);
29121
29122	// we'll assume the device has no mouse until we detect any mouse movement
29123	var deviceHasMouse = false;
29124	$('body').one('mousemove', function() {
29125		deviceHasMouse = true;
29126	});
29127
29128	function deviceIsPureTouch() {
29129		return (!deviceHasMouse && deviceHasTouchCapability);
29130	}
29131
29132	// detecting support for CSS transitions
29133	function supportsTransitions() {
29134		var b = document.body || document.documentElement,
29135			s = b.style,
29136			p = 'transition';
29137
29138		if(typeof s[p] == 'string') {return true; }
29139
29140		v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'],
29141		p = p.charAt(0).toUpperCase() + p.substr(1);
29142		for(var i=0; i<v.length; i++) {
29143			if(typeof s[v[i] + p] == 'string') { return true; }
29144		}
29145		return false;
29146	}
29147})( jQuery, window, document );
29148(function() {
29149  String.prototype.toCamel = function() {
29150    return this.replace(/(\_[a-z])/g, function($1) {
29151      return $1.toUpperCase().replace("_", "");
29152    });
29153  };
29154
29155  String.prototype.camelToUnderscore = function() {
29156    return this.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1_$2').toLowerCase();
29157  };
29158
29159  String.prototype.capitalizeFirst = function() {
29160    return this.charAt(0).toUpperCase() + this.slice(1);
29161  };
29162
29163  String.prototype.truncate = function(length) {
29164    if (length == null) {
29165      length = 30;
29166    }
29167    if (this.length > length) {
29168      return this.substring(0, length) + '...';
29169    } else {
29170      return String(this);
29171    }
29172  };
29173
29174  this.parseDate = function(date) {
29175    var m, tzOffset;
29176    m = /^(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d) UTC$/.exec(date);
29177    tzOffset = new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]).getTimezoneOffset();
29178    return new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5] - tzOffset, +m[6]);
29179  };
29180
29181}).call(this);
29182/*!
29183* Mediator.js Library v0.9.8
29184* https://github.com/ajacksified/Mediator.js
29185*
29186* MIT Licensed (http://www.opensource.org/licenses/mit-license.php)
29187*
29188* For more information: http://thejacklawson.com/2011/06/mediators-for-modularized-asynchronous-programming-in-javascript/index.html
29189* Project on GitHub: https://github.com/ajacksified/Mediator.js
29190*
29191*/
29192
29193
29194!function(a,b){"use strict";"function"==typeof define&&define.amd?define("mediator-js",[],function(){return a.Mediator=b(),a.Mediator}):"undefined"!=typeof exports?exports.Mediator=b():a.Mediator=b()}(this,function(){"use strict";function a(){var a=function(){return(0|65536*(1+Math.random())).toString(16).substring(1)};return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()}function b(c,d,e){return this instanceof b?(this.id=a(),this.fn=c,this.options=d,this.context=e,this.channel=null,void 0):new b(c,d,e)}function c(a,b){return this instanceof c?(this.namespace=a||"",this._subscribers=[],this._channels={},this._parent=b,this.stopped=!1,void 0):new c(a)}function d(){return this instanceof d?(this._channels=new c(""),void 0):new d}return b.prototype={update:function(a){a&&(this.fn=a.fn||this.fn,this.context=a.context||this.context,this.options=a.options||this.options,this.channel&&this.options&&void 0!==this.options.priority&&this.channel.setPriority(this.id,this.options.priority))}},c.prototype={addSubscriber:function(a,c,d){var e=new b(a,c,d);return c&&void 0!==c.priority?(c.priority=c.priority>>0,c.priority<0&&(c.priority=0),c.priority>=this._subscribers.length&&(c.priority=this._subscribers.length-1),this._subscribers.splice(c.priority,0,e)):this._subscribers.push(e),e.channel=this,e},stopPropagation:function(){this.stopped=!0},getSubscriber:function(a){var b=0,c=this._subscribers.length;for(c;c>b;b++)if(this._subscribers[b].id===a||this._subscribers[b].fn===a)return this._subscribers[b]},setPriority:function(a,b){var e,f,g,h,c=0,d=0;for(d=0,h=this._subscribers.length;h>d&&this._subscribers[d].id!==a&&this._subscribers[d].fn!==a;d++)c++;e=this._subscribers[c],f=this._subscribers.slice(0,c),g=this._subscribers.slice(c+1),this._subscribers=f.concat(g),this._subscribers.splice(b,0,e)},addChannel:function(a){this._channels[a]=new c((this.namespace?this.namespace+":":"")+a,this)},hasChannel:function(a){return this._channels.hasOwnProperty(a)},returnChannel:function(a){return this._channels[a]},removeSubscriber:function(a){var b=this._subscribers.length-1;if(!a)return this._subscribers=[],void 0;for(b;b>=0;b--)(this._subscribers[b].fn===a||this._subscribers[b].id===a)&&(this._subscribers[b].channel=null,this._subscribers.splice(b,1))},publish:function(a){var e,g,h,b=0,c=this._subscribers.length,d=!1;for(c;c>b;b++)d=!1,e=this._subscribers[b],this.stopped||(g=this._subscribers.length,void 0!==e.options&&"function"==typeof e.o
29194ptions.predicate?e.options.predicate.apply(e.context,a)&&(d=!0):d=!0),d&&(e.options&&void 0!==e.options.calls&&(e.options.calls--,e.options.calls<1&&this.removeSubscriber(e.id)),e.fn.apply(e.context,a),h=this._subscribers.length,c=h,h===g-1&&b--);this._parent&&this._parent.publish(a),this.stopped=!1}},d.prototype={getChannel:function(a,b){var c=this._channels,d=a.split(":"),e=0,f=d.length;if(""===a)return c;if(d.length>0)for(f;f>e;e++){if(!c.hasChannel(d[e])){if(b)break;c.addChannel(d[e])}c=c.returnChannel(d[e])}return c},subscribe:function(a,b,c,d){var e=this.getChannel(a||"",!1);return c=c||{},d=d||{},e.addSubscriber(b,c,d)},once:function(a,b,c,d){return c=c||{},c.calls=1,this.subscribe(a,b,c,d)},getSubscriber:function(a,b){var c=this.getChannel(b||"",!0);return c.namespace!==b?null:c.getSubscriber(a)},remove:function(a,b){var c=this.getChannel(a||"",!0);return c.namespace!==a?!1:(c.removeSubscriber(b),void 0)},publish:function(a){var b=this.getChannel(a||"",!0);if(b.namespace!==a)return null;var c=Array.prototype.slice.call(arguments,1);c.push(b),b.publish(c)}},d.prototype.on=d.prototype.subscribe,d.prototype.bind=d.prototype.subscribe,d.prototype.emit=d.prototype.publish,d.prototype.trigger=d.prototype.publish,d.prototype.off=d.prototype.remove,d.Channel=c,d.Subscriber=b,d.version="0.9.8",d});
29195(function() {
29196  var context = this;
29197
29198  (function() {
29199    (function() {
29200      var slice = [].slice;
29201
29202      this.ActionCable = {
29203        INTERNAL: {
29204          "message_types": {
29205            "welcome": "welcome",
29206            "ping": "ping",
29207            "confirmation": "confirm_subscription",
29208            "rejection": "reject_subscription"
29209          },
29210          "default_mount_path": "/cable",
29211          "protocols": ["actioncable-v1-json", "actioncable-unsupported"]
29212        },
29213        WebSocket: window.WebSocket,
29214        logger: window.console,
29215        createConsumer: function(url) {
29216          var ref;
29217          if (url == null) {
29218            url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
29219          }
29220          return new ActionCable.Consumer(this.createWebSocketURL(url));
29221        },
29222        getConfig: function(name) {
29223          var element;
29224          element = document.head.querySelector("meta[name='action-cable-" + name + "']");
29225          return element != null ? element.getAttribute("content") : void 0;
29226        },
29227        createWebSocketURL: function(url) {
29228          var a;
29229          if (url && !/^wss?:/i.test(url)) {
29230            a = document.createElement("a");
29231            a.href = url;
29232            a.href = a.href;
29233            a.protocol = a.protocol.replace("http", "ws");
29234            return a.href;
29235          } else {
29236            return url;
29237          }
29238        },
29239        startDebugging: function() {
29240          return this.debugging = true;
29241        },
29242        stopDebugging: function() {
29243          return this.debugging = null;
29244        },
29245        log: function() {
29246          var messages, ref;
29247          messages = 1 <= arguments.length ? slice.call(arguments, 0) : [];
29248          if (this.debugging) {
29249            messages.push(Date.now());
29250            return (ref = this.logger).log.apply(ref, ["[ActionCable]"].concat(slice.call(messages)));
29251          }
29252        }
29253      };
29254
29255    }).call(this);
29256  }).call(context);
29257
29258  var ActionCable = context.ActionCable;
29259
29260  (function() {
29261    (function() {
29262      var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
29263
29264      ActionCable.ConnectionMonitor = (function() {
29265        var clamp, now, secondsSince;
29266
29267        ConnectionMonitor.pollInterval = {
29268          min: 3,
29269          max: 30
29270        };
29271
29272        ConnectionMonitor.staleThreshold = 6;
29273
29274        function ConnectionMonitor(connection) {
29275          this.connection = connection;
29276          this.visibilityDidChange = bind(this.visibilityDidChange, this);
29277          this.reconnectAttempts = 0;
29278        }
29279
29280        ConnectionMonitor.prototype.start = function() {
29281          if (!this.isRunning()) {
29282            this.startedAt = now();
29283            delete this.stoppedAt;
29284            this.startPolling();
29285            document.addEventListener("visibilitychange", this.visibilityDidChange);
29286            return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
29287          }
29288        };
29289
29290        ConnectionMonitor.prototype.stop = function() {
29291          if (this.isRunning()) {
29292            this.stoppedAt = now();
29293            this.stopPolling();
29294            document.removeEventListener("visibilitychange", this.visibilityDidChange);
29295            return ActionCable.log("ConnectionMonitor stopped");
29296          }
29297        };
29298
29299        ConnectionMonitor.prototype.isRunning = function() {
29300          return (this.startedAt != null) && (this.stoppedAt == null);
29301        };
29302
29303        ConnectionMonitor.prototype.recordPing = function() {
29304          return this.pingedAt = now();
29305        };
29306
29307        ConnectionMonitor.prototype.recordConnect = function() {
29308          this.reconnectAttempts = 0;
29309          this.recordPing();
29310          delete this.disconnectedAt;
29311          return ActionCable.log("ConnectionMonitor recorded connect");
29312        };
29313
29314        ConnectionMonitor.prototype.recordDisconnect = function() {
29315          this.disconnectedAt = now();
29316          return ActionCable.log("ConnectionMonitor recorded disconnect");
29317        };
29318
29319        ConnectionMonitor.prototype.startPolling = function() {
29320          this.stopPolling();
29321          return this.poll();
29322        };
29323
29324        ConnectionMonitor.prototype.stopPolling = function() {
29325          return clearTimeout(this.pollTimeout);
29326        };
29327
29328        ConnectionMonitor.prototype.poll = function() {
29329          return this.pollTimeout = setTimeout((function(_this) {
29330            return function() {
29331              _this.reconnectIfStale();
29332              return _this.poll();
29333            };
29334          })(this), this.getPollInterval());
29335        };
29336
29337        ConnectionMonitor.prototype.getPollInterval = function() {
29338          var interval, max, min, ref;
29339          ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
29340          interval = 5 * Math.log(this.reconnectAttempts + 1);
29341          return Math.round(clamp(interval, min, max) * 1000);
29342        };
29343
29344        ConnectionMonitor.prototype.reconnectIfStale = function() {
29345          if (this.connectionIsStale()) {
29346            ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
29347            this.reconnectAttempts++;
29348            if (this.disconnectedRecently()) {
29349              return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
29350            } else {
29351              ActionCable.log("ConnectionMonitor reopening");
29352              return this.connection.reopen();
29353            }
29354          }
29355        };
29356
29357        ConnectionMonitor.prototype.connectionIsStale = function() {
29358          var ref;
29359          return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
29360        };
29361
29362        ConnectionMonitor.prototype.disconnectedRecently = function() {
29363          return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
29364        };
29365
29366        ConnectionMonitor.prototype.visibilityDidChange = function() {
29367          if (document.visibilityState === "visible") {
29368            return setTimeout((function(_this) {
29369              return function() {
29370                if (_this.connectionIsStale() || !_this.connection.isOpen()) {
29371                  ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
29372                  return _this.connection.reopen();
29373                }
29374              };
29375            })(this), 200);
29376          }
29377        };
29378
29379        now = function() {
29380          return new Date().getTime();
29381        };
29382
29383        secondsSince = function(time) {
29384          return (now() - time) / 1000;
29385        };
29386
29387        clamp = function(number, min, max) {
29388          return Math.max(min, Math.min(max, number));
29389        };
29390
29391        return ConnectionMonitor;
29392
29393      })();
29394
29395    }).call(this);
29396    (function() {
29397      var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
29398        slice = [].slice,
29399        bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
29400        indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
29401
29402      ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;
29403
29404      supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];
29405
29406      ActionCable.Connection = (function() {
29407        Connection.reopenDelay = 500;
29408
29409        function Connection(consumer) {
29410          this.consumer = consumer;
29411          this.open = bind(this.open, this);
29412          this.subscriptions = this.consumer.subscriptions;
29413          this.monitor = new ActionCable.ConnectionMonitor(this);
29414          this.disconnected = true;
29415        }
29416
29417        Connection.prototype.send = function(data) {
29418          if (this.isOpen()) {
29419            this.webSocket.send(JSON.stringify(data));
29420            return true;
29421          } else {
29422            return false;
29423          }
29424        };
29425
29426        Connection.prototype.open = function() {
29427          if (this.isActive()) {
29428            ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
29429            return false;
29430          } else {
29431            ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
29432            if (this.webSocket != null) {
29433              this.uninstallEventHandlers();
29434            }
29435            this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols);
29436            this.installEventHandlers();
29437            this.monitor.start();
29438            return true;
29439          }
29440        };
29441
29442        Connection.prototype.close = function(arg) {
29443          var allowReconnect, ref1;
29444          allowReconnect = (arg != null ? arg : {
29445            allowReconnect: true
29446          }).allowReconnect;
29447          if (!allowReconnect) {
29448            this.monitor.stop();
29449          }
29450          if (this.isActive()) {
29451            return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
29452          }
29453        };
29454
29455        Connection.prototype.reopen = function() {
29456          var error;
29457          ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
29458          if (this.isActive()) {
29459            try {
29460              return this.close();
29461            } catch (error1) {
29462              error = error1;
29463              return ActionCable.log("Failed to reopen WebSocket", error);
29464            } finally {
29465              ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
29466              setTimeout(this.open, this.constructor.reopenDelay);
29467            }
29468          } else {
29469            return this.open();
29470          }
29471        };
29472
29473        Connection.prototype.getProtocol = function() {
29474          var ref1;
29475          return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
29476        };
29477
29478        Connection.prototype.isOpen = function() {
29479          return this.isState("open");
29480        };
29481
29482        Connection.prototype.isActive = function() {
29483          return this.isState("open", "connecting");
29484        };
29485
29486        Connection.prototype.isProtocolSupported = function() {
29487          var ref1;
29488          return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
29489        };
29490
29491        Connection.prototype.isState = function() {
29492          var ref1, states;
29493          states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
29494          return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
29495        };
29496
29497        Connection.prototype.getState = function() {
29498          var ref1, state, value;
29499          for (state in WebSocket) {
29500            value = WebSocket[state];
29501            if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
29502              return state.toLowerCase();
29503            }
29504          }
29505          return null;
29506        };
29507
29508        Connection.prototype.installEventHandlers = function() {
29509          var eventName, handler;
29510          for (eventName in this.events) {
29511            handler = this.events[eventName].bind(this);
29512            this.webSocket["on" + eventName] = handler;
29513          }
29514        };
29515
29516        Connection.prototype.uninstallEventHandlers = function() {
29517          var eventName;
29518          for (eventName in this.events) {
29519            this.webSocket["on" + eventName] = function() {};
29520          }
29521        };
29522
29523        Connection.prototype.events = {
29524          message: function(event) {
29525            var identifier, message, ref1, type;
29526            if (!this.isProtocolSupported()) {
29527              return;
29528            }
29529            ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
29530            switch (type) {
29531              case message_types.welcome:
29532                this.monitor.recordConnect();
29533                return this.subscriptions.reload();
29534              case message_types.ping:
29535                return this.monitor.recordPing();
29536              case message_types.confirmation:
29537                return this.subscriptions.notify(identifier, "connected");
29538              case message_types.rejection:
29539                return this.subscriptions.reject(identifier);
29540              default:
29541                return this.subscriptions.notify(identifier, "received", message);
29542            }
29543          },
29544          open: function() {
29545            ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
29546            this.disconnected = false;
29547            if (!this.isProtocolSupported()) {
29548              ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
29549              return this.close({
29550                allowReconnect: false
29551              });
29552            }
29553          },
29554          close: function(event) {
29555            ActionCable.log("WebSocket onclose event");
29556            if (this.disconnected) {
29557              return;
29558            }
29559            this.disconnected = true;
29560            this.monitor.recordDisconnect();
29561            return this.subscriptions.notifyAll("disconnected", {
29562              willAttemptReconnect: this.monitor.isRunning()
29563            });
29564          },
29565          error: function() {
29566            return ActionCable.log("WebSocket onerror event");
29567          }
29568        };
29569
29570        return Connection;
29571
29572      })();
29573
29574    }).call(this);
29575    (function() {
29576      var slice = [].slice;
29577
29578      ActionCable.Subscriptions = (function() {
29579        function Subscriptions(consumer) {
29580          this.consumer = consumer;
29581          this.subscriptions = [];
29582        }
29583
29584        Subscriptions.prototype.create = function(channelName, mixin) {
29585          var channel, params, subscription;
29586          channel = channelName;
29587          params = typeof channel === "object" ? channel : {
29588            channel: channel
29589          };
29590          subscription = new ActionCable.Subscription(this.consumer, params, mixin);
29591          return this.add(subscription);
29592        };
29593
29594        Subscriptions.prototype.add = function(subscription) {
29595          this.subscriptions.push(subscription);
29596          this.consumer.ensureActiveConnection();
29597          this.notify(subscription, "initialized");
29598          this.sendCommand(subscription, "subscribe");
29599          return subscription;
29600        };
29601
29602        Subscriptions.prototype.remove = function(subscription) {
29603          this.forget(subscription);
29604          if (!this.findAll(subscription.identifier).length) {
29605            this.sendCommand(subscription, "unsubscribe");
29606          }
29607          return subscription;
29608        };
29609
29610        Subscriptions.prototype.reject = function(identifier) {
29611          var i, len, ref, results, subscription;
29612          ref = this.findAll(identifier);
29613          results = [];
29614          for (i = 0, len = ref.length; i < len; i++) {
29615            subscription = ref[i];
29616            this.forget(subscription);
29617            this.notify(subscription, "rejected");
29618            results.push(subscription);
29619          }
29620          return results;
29621        };
29622
29623        Subscriptions.prototype.forget = function(subscription) {
29624          var s;
29625          this.subscriptions = (function() {
29626            var i, len, ref, results;
29627            ref = this.subscriptions;
29628            results = [];
29629            for (i = 0, len = ref.length; i < len; i++) {
29630              s = ref[i];
29631              if (s !== subscription) {
29632                results.push(s);
29633              }
29634            }
29635            return results;
29636          }).call(this);
29637          return subscription;
29638        };
29639
29640        Subscriptions.prototype.findAll = function(identifier) {
29641          var i, len, ref, results, s;
29642          ref = this.subscriptions;
29643          results = [];
29644          for (i = 0, len = ref.length; i < len; i++) {
29645            s = ref[i];
29646            if (s.identifier === identifier) {
29647              results.push(s);
29648            }
29649          }
29650          return results;
29651        };
29652
29653        Subscriptions.prototype.reload = function() {
29654          var i, len, ref, results, subscription;
29655          ref = this.subscriptions;
29656          results = [];
29657          for (i = 0, len = ref.length; i < len; i++) {
29658            subscription = ref[i];
29659            results.push(this.sendCommand(subscription, "subscribe"));
29660          }
29661          return results;
29662        };
29663
29664        Subscriptions.prototype.notifyAll = function() {
29665          var args, callbackName, i, len, ref, results, subscription;
29666          callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
29667          ref = this.subscriptions;
29668          results = [];
29669          for (i = 0, len = ref.length; i < len; i++) {
29670            subscription = ref[i];
29671            results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
29672          }
29673          return results;
29674        };
29675
29676        Subscriptions.prototype.notify = function() {
29677          var args, callbackName, i, len, results, subscription, subscriptions;
29678          subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
29679          if (typeof subscription === "string") {
29680            subscriptions = this.findAll(subscription);
29681          } else {
29682            subscriptions = [subscription];
29683          }
29684          results = [];
29685          for (i = 0, len = subscriptions.length; i < len; i++) {
29686            subscription = subscriptions[i];
29687            results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
29688          }
29689          return results;
29690        };
29691
29692        Subscriptions.prototype.sendCommand = function(subscription, command) {
29693          var identifier;
29694          identifier = subscription.identifier;
29695          return this.consumer.send({
29696            command: command,
29697            identifier: identifier
29698          });
29699        };
29700
29701        return Subscriptions;
29702
29703      })();
29704
29705    }).call(this);
29706    (function() {
29707      ActionCable.Subscription = (function() {
29708        var extend;
29709
29710        function Subscription(consumer, params, mixin) {
29711          this.consumer = consumer;
29712          if (params == null) {
29713            params = {};
29714          }
29715          this.identifier = JSON.stringify(params);
29716          extend(this, mixin);
29717        }
29718
29719        Subscription.prototype.perform = function(action, data) {
29720          if (data == null) {
29721            data = {};
29722          }
29723          data.action = action;
29724          return this.send(data);
29725        };
29726
29727        Subscription.prototype.send = function(data) {
29728          return this.consumer.send({
29729            command: "message",
29730            identifier: this.identifier,
29731            data: JSON.stringify(data)
29732          });
29733        };
29734
29735        Subscription.prototype.unsubscribe = function() {
29736          return this.consumer.subscriptions.remove(this);
29737        };
29738
29739        extend = function(object, properties) {
29740          var key, value;
29741          if (properties != null) {
29742            for (key in properties) {
29743              value = properties[key];
29744              object[key] = value;
29745            }
29746          }
29747          return object;
29748        };
29749
29750        return Subscription;
29751
29752      })();
29753
29754    }).call(this);
29755    (function() {
29756      ActionCable.Consumer = (function() {
29757        function Consumer(url) {
29758          this.url = url;
29759          this.subscriptions = new ActionCable.Subscriptions(this);
29760          this.connection = new ActionCable.Connection(this);
29761        }
29762
29763        Consumer.prototype.send = function(data) {
29764          return this.connection.send(data);
29765        };
29766
29767        Consumer.prototype.connect = function() {
29768          return this.connection.open();
29769        };
29770
29771        Consumer.prototype.disconnect = function() {
29772          return this.connection.close({
29773            allowReconnect: false
29774          });
29775        };
29776
29777        Consumer.prototype.ensureActiveConnection = function() {
29778          if (!this.connection.isActive()) {
29779            return this.connection.open();
29780          }
29781        };
29782
29783        return Consumer;
29784
29785      })();
29786
29787    }).call(this);
29788  }).call(this);
29789
29790  if (typeof module === "object" && module.exports) {
29791    module.exports = ActionCable;
29792  } else if (typeof define === "function" && define.amd) {
29793    define(ActionCable);
29794  }
29795}).call(this);
29796(function() {
29797  var config, mediator, settings;
29798
29799  mediator = typeof window.App !== 'undefined' && typeof window.App.events !== 'undefined' ? window.App.events : new Mediator();
29800
29801  config = typeof window.App !== 'undefined' && typeof (window.App.config !== 'undefined') ? window.App.config : {};
29802
29803  window.App = {
29804    Core: {},
29805    Pages: {},
29806    Widgets: {},
29807    Modules: {},
29808    Models: {},
29809    Collections: {},
29810    Routers: {},
29811    Views: {},
29812    events: mediator,
29813    config: config,
29814    state: {},
29815    cable: {}
29816  };
29817
29818  settings = $('#application-settings').data();
29819
29820  if (settings != null ? settings.userSignedIn : void 0) {
29821    window.App.cable = ActionCable.createConsumer();
29822    window.App.cable.subscriptions.create({
29823      channel: 'AppearanceChannel'
29824    });
29825  }
29826
29827}).call(this);
29828(function() {
29829  if (App.Core == null) {
29830    App.Core = {};
29831  }
29832
29833  App.Core.Page = (function() {
29834    Page.prototype.widgets = [];
29835
29836    Page.prototype._widgetInstances = {};
29837
29838    function Page() {
29839      $.extend(this, App.Core.Page);
29840      this._loadWidgets();
29841    }
29842
29843    Page.prototype._loadWidget = function(name) {
29844      var klass;
29845      klass = name.capitalizeFirst();
29846      if (App.Widgets[klass] == null) {
29847        if (typeof console !== "undefined" && console !== null) {
29848          if (typeof console.warn === "function") {
29849            console.warn("Widget " + name + " - App.Widgets." + klass + " not found!");
29850          }
29851        }
29852        return;
29853      }
29854      this._widgetInstances[name] = new App.Widgets[klass];
29855      return this._widgetInstances[name].initialize();
29856    };
29857
29858    Page.prototype._loadWidgets = function() {
29859      return _.each(this.widgets, (function(_this) {
29860        return function(name) {
29861          return _this._loadWidget(name);
29862        };
29863      })(this));
29864    };
29865
29866    Page.prototype.initialize = function() {};
29867
29868    return Page;
29869
29870  })();
29871
29872}).call(this);
29873(function() {
29874  App.events.subscribe('app:page:execute', function() {
29875    var action, controller, globalPageInstance, hasNamespace, namespace, page, pageInstance, pageName;
29876    controller = document.body.getAttribute('data-controller');
29877    action = document.body.getAttribute('data-action');
29878    page = (controller + "_" + action).toCamel();
29879    page = page.charAt(0).toUpperCase() + page.slice(1);
29880    I18n.locale = document.getElementsByTagName('html')[0].getAttribute('lang');
29881    I18n.fallbacks = true;
29882    hasNamespace = _.rest(page.split("/")).length !== 0;
29883    if (App.Pages.Global != null) {
29884      globalPageInstance = new App.Pages.Global;
29885      if (globalPageInstance != null) {
29886        globalPageInstance.initialize();
29887      }
29888    }
29889    if (hasNamespace) {
29890      namespace = _.first(page.split("/"));
29891      pageName = (_.rest(page.split("/")).map(function(word) {
29892        return word[0].toUpperCase() + word.slice(1);
29893      })).join('');
29894      if ((App.Pages[namespace] != null) && (App.Pages[namespace][pageName] != null)) {
29895        pageInstance = new App.Pages[namespace][pageName];
29896      }
29897    } else {
29898      if (App.Pages[page] != null) {
29899        pageInstance = new App.Pages[page];
29900      }
29901    }
29902    return pageInstance != null ? pageInstance.initialize() : void 0;
29903  });
29904
29905  App.events.subscribe('app:config:load', function(data) {
29906    _.extend(App.config, data);
29907    return App.state.csrfToken = $("meta[name='csrf-token']").attr('content');
29908  });
29909
29910}).call(this);
29911(function() {
29912
29913
29914}).call(this);
29915(function() {
29916  App.events.subscribe('app:ui:refresh', function(scope) {
29917    var $scope;
29918    $scope = $(scope);
29919    $scope.find('.select').chosen({
29920      disable_search_threshold: 10,
29921      search_contains: true
29922    });
29923    $scope.find('.has-tooltip, .js-tooltip').tooltipster({
29924      theme: 'tooltip--light',
29925      animation: 'grow'
29926    });
29927    $scope.find('.js-popover').tooltipster({
29928      theme: 'tooltip--light tooltip--popover',
29929      animation: 'grow',
29930      interactive: true,
29931      contentAsHTML: true,
29932      functionInit: function(origin) {
29933        return origin.data('content');
29934      }
29935    });
29936    return $scope.find('[data-check-control]').checkControl();
29937  });
29938
29939  App.events.subscribe('app:ui:init', function() {
29940    App.events.publish('app:ui:refresh', 'body');
29941    App.events.publish('app:bg:init');
29942    return App.events.publish('app:action-bar:init');
29943  });
29944
29945  App.events.subscribe('app:element:focus', function($el, delay) {
29946    if (delay == null) {
29947      delay = 0;
29948    }
29949    return setTimeout(function() {
29950      return $('html, body').animate({
29951        scrollTop: $el.offset().top,
29952        easing: 'easeInOutCirc'
29953      }, {
29954        easing: 'easeInOutCirc',
29955        duration: 600
29956      });
29957    }, delay);
29958  });
29959
29960  App.events.subscribe('app:bg:init', function() {
29961    var $backgrounds;
29962    $backgrounds = $('.js-bg').not('.is-loaded');
29963    $backgrounds.addClass('is-loading');
29964    return $backgrounds.each(function(index, el) {
29965      var deferred, trigger;
29966      trigger = $(el).data('trigger');
29967      switch (trigger) {
29968        case 'load':
29969          deferred = $.Deferred(function(promise) {
29970            return $(function() {
29971              return promise.resolve();
29972            });
29973          });
29974          break;
29975        case 'delay':
29976          deferred = $.Deferred(function(promise) {
29977            var delay;
29978            delay = parseInt($(el).data('delay'), 10);
29979            return setTimeout(function() {
29980              return promise.resolve();
29981            }, delay);
29982          });
29983          break;
29984        default:
29985          deferred = $.Deferred(function(promise) {
29986            return promise.resolve();
29987          });
29988      }
29989      return deferred.then(function() {
29990        var img, imgLoad;
29991        img = new Image();
29992        img.src = $(el).data('bg');
29993        imgLoad = imagesLoaded(img);
29994        imgLoad.on('done', function() {
29995          return $(el).backstretch([$(el).data('bg')], {
29996            fade: 600
29997          });
29998        });
29999        return imgLoad.on('always', function() {
30000          return $(el).removeClass('is-loading').addClass('is-loaded');
30001        });
30002      });
30003    });
30004  });
30005
30006  App.events.subscribe('app:action-bar:init', function() {
30007    var $actionBar, $siteFooter, actionBarHeight;
30008    $actionBar = $('.action-bar');
30009    $siteFooter = $('.site-footer');
30010    if (!($actionBar.exists() && $siteFooter.exists())) {
30011      return;
30012    }
30013    actionBarHeight = $(window).innerHeight() - $actionBar.position().top;
30014    return $siteFooter.css('margin-bottom', actionBarHeight);
30015  });
30016
30017}).call(this);
30018(function() {
30019
30020
30021}).call(this);
30022//     Backbone.js 1.1.2
30023
30024//     (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
30025//     Backbone may be freely distributed under the MIT license.
30026//     For all details and documentation:
30027//     http://backbonejs.org
30028
30029(function(root, factory) {
30030
30031  // Set up Backbone appropriately for the environment. Start with AMD.
30032  if (typeof define === 'function' && define.amd) {
30033    define(['underscore', 'jquery', 'exports'], function(_, $, exports
30033) {
30034      // Export global even in AMD case in case this script is loaded with
30035      // others that may still expect a global Backbone.
30036      root.Backbone = factory(root, exports, _, $);
30037    });
30038
30039  // Next for Node.js or CommonJS. jQuery may not be needed as a module.
30040  } else if (typeof exports !== 'undefined') {
30041    var _ = require('underscore');
30042    factory(root, exports, _);
30043
30044  // Finally, as a browser global.
30045  } else {
30046    root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
30047  }
30048
30049}(this, function(root, Backbone, _, $) {
30050
30051  // Initial Setup
30052  // -------------
30053
30054  // Save the previous value of the `Backbone` variable, so that it can be
30055  // restored later on, if `noConflict` is used.
30056  var previousBackbone = root.Backbone;
30057
30058  // Create local references to array methods we'll want to use later.
30059  var array = [];
30060  var push = array.push;
30061  var slice = array.slice;
30062  var splice = array.splice;
30063
30064  // Current version of the library. Keep in sync with `package.json`.
30065  Backbone.VERSION = '1.1.2';
30066
30067  // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
30068  // the `$` variable.
30069  Backbone.$ = $;
30070
30071  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
30072  // to its previous owner. Returns a reference to this Backbone object.
30073  Backbone.noConflict = function() {
30074    root.Backbone = previousBackbone;
30075    return this;
30076  };
30077
30078  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
30079  // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
30080  // set a `X-Http-Method-Override` header.
30081  Backbone.emulateHTTP = false;
30082
30083  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
30084  // `application/json` requests ... will encode the body as
30085  // `application/x-www-form-urlencoded` instead and will send the model in a
30086  // form param named `model`.
30087  Backbone.emulateJSON = false;
30088
30089  // Backbone.Events
30090  // ---------------
30091
30092  // A module that can be mixed in to *any object* in order to provide it with
30093  // custom events. You may bind with `on` or remove with `off` callback
30094  // functions to an event; `trigger`-ing an event fires all callbacks in
30095  // succession.
30096  //
30097  //     var object = {};
30098  //     _.extend(object, Backbone.Events);
30099  //     object.on('expand', function(){ alert('expanded'); });
30100  //     object.trigger('expand');
30101  //
30102  var Events = Backbone.Events = {
30103
30104    // Bind an event to a `callback` function. Passing `"all"` will bind
30105    // the callback to all events fired.
30106    on: function(name, callback, context) {
30107      if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
30108      this._events || (this._events = {});
30109      var events = this._events[name] || (this._events[name] = []);
30110      events.push({callback: callback, context: context, ctx: context || this});
30111      return this;
30112    },
30113
30114    // Bind an event to only be triggered a single time. After the first time
30115    // the callback is invoked, it will be removed.
30116    once: function(name, callback, context) {
30117      if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
30118      var self = this;
30119      var once = _.once(function() {
30120        self.off(name, once);
30121        callback.apply(this, arguments);
30122      });
30123      once._callback = callback;
30124      return this.on(name, once, context);
30125    },
30126
30127    // Remove one or many callbacks. If `context` is null, removes all
30128    // callbacks with that function. If `callback` is null, removes all
30129    // callbacks for the event. If `name` is null, removes all bound
30130    // callbacks for all events.
30131    off: function(name, callback, context) {
30132      var retain, ev, events, names, i, l, j, k;
30133      if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
30134      if (!name && !callback && !context) {
30135        this._events = void 0;
30136        return this;
30137      }
30138      names = name ? [name] : _.keys(this._events);
30139      for (i = 0, l = names.length; i < l; i++) {
30140        name = names[i];
30141        if (events = this._events[name]) {
30142          this._events[name] = retain = [];
30143          if (callback || context) {
30144            for (j = 0, k = events.length; j < k; j++) {
30145              ev = events[j];
30146              if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
30147                  (context && context !== ev.context)) {
30148                retain.push(ev);
30149              }
30150            }
30151          }
30152          if (!retain.length) delete this._events[name];
30153        }
30154      }
30155
30156      return this;
30157    },
30158
30159    // Trigger one or many events, firing all bound callbacks. Callbacks are
30160    // passed the same arguments as `trigger` is, apart from the event name
30161    // (unless you're listening on `"all"`, which will cause your callback to
30162    // receive the true name of the event as the first argument).
30163    trigger: function(name) {
30164      if (!this._events) return this;
30165      var args = slice.call(arguments, 1);
30166      if (!eventsApi(this, 'trigger', name, args)) return this;
30167      var events = this._events[name];
30168      var allEvents = this._events.all;
30169      if (events) triggerEvents(events, args);
30170      if (allEvents) triggerEvents(allEvents, arguments);
30171      return this;
30172    },
30173
30174    // Tell this object to stop listening to either specific events ... or
30175    // to every object it's currently listening to.
30176    stopListening: function(obj, name, callback) {
30177      var listeningTo = this._listeningTo;
30178      if (!listeningTo) return this;
30179      var remove = !name && !callback;
30180      if (!callback && typeof name === 'object') callback = this;
30181      if (obj) (listeningTo = {})[obj._listenId] = obj;
30182      for (var id in listeningTo) {
30183        obj = listeningTo[id];
30184        obj.off(name, callback, this);
30185        if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
30186      }
30187      return this;
30188    }
30189
30190  };
30191
30192  // Regular expression used to split event strings.
30193  var eventSplitter = /\s+/;
30194
30195  // Implement fancy features of the Events API such as multiple event
30196  // names `"change blur"` and jQuery-style event maps `{change: action}`
30197  // in terms of the existing API.
30198  var eventsApi = function(obj, action, name, rest) {
30199    if (!name) return true;
30200
30201    // Handle event maps.
30202    if (typeof name === 'object') {
30203      for (var key in name) {
30204        obj[action].apply(obj, [key, name[key]].concat(rest));
30205      }
30206      return false;
30207    }
30208
30209    // Handle space separated event names.
30210    if (eventSplitter.test(name)) {
30211      var names = name.split(eventSplitter);
30212      for (var i = 0, l = names.length; i < l; i++) {
30213        obj[action].apply(obj, [names[i]].concat(rest));
30214      }
30215      return false;
30216    }
30217
30218    return true;
30219  };
30220
30221  // A difficult-to-believe, but optimized internal dispatch function for
30222  // triggering events. Tries to keep the usual cases speedy (most internal
30223  // Backbone events have 3 arguments).
30224  var triggerEvents = function(events, args) {
30225    var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
30226    switch (args.length) {
30227      case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
30228      case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
30229      case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
30230      case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
30231      default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
30232    }
30233  };
30234
30235  var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
30236
30237  // Inversion-of-control versions of `on` and `once`. Tell *this* object to
30238  // listen to an event in another object ... keeping track of what it's
30239  // listening to.
30240  _.each(listenMethods, function(implementation, method) {
30241    Events[method] = function(obj, name, callback) {
30242      var listeningTo = this._listeningTo || (this._listeningTo = {});
30243      var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
30244      listeningTo[id] = obj;
30245      if (!callback && typeof name === 'object') callback = this;
30246      obj[implementation](name, callback, this);
30247      return this;
30248    };
30249  });
30250
30251  // Aliases for backwards compatibility.
30252  Events.bind   = Events.on;
30253  Events.unbind = Events.off;
30254
30255  // Allow the `Backbone` object to serve as a global event bus, for folks who
30256  // want global "pubsub" in a convenient place.
30257  _.extend(Backbone, Events);
30258
30259  // Backbone.Model
30260  // --------------
30261
30262  // Backbone **Models** are the basic data object in the framework --
30263  // frequently representing a row in a table in a database on your server.
30264  // A discrete chunk of data and a bunch of useful, related methods for
30265  // performing computations and transformations on that data.
30266
30267  // Create a new model with the specified attributes. A client id (`cid`)
30268  // is automatically generated and assigned for you.
30269  var Model = Backbone.Model = function(attributes, options) {
30270    var attrs = attributes || {};
30271    options || (options = {});
30272    this.cid = _.uniqueId('c');
30273    this.attributes = {};
30274    if (options.collection) this.collection = options.collection;
30275    if (options.parse) attrs = this.parse(attrs, options) || {};
30276    attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
30277    this.set(attrs, options);
30278    this.changed = {};
30279    this.initialize.apply(this, arguments);
30280  };
30281
30282  // Attach all inheritable methods to the Model prototype.
30283  _.extend(Model.prototype, Events, {
30284
30285    // A hash of attributes whose current and previous value differ.
30286    changed: null,
30287
30288    // The value returned during the last failed validation.
30289    validationError: null,
30290
30291    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
30292    // CouchDB users may want to set this to `"_id"`.
30293    idAttribute: 'id',
30294
30295    // Initialize is an empty function by default. Override it with your own
30296    // initialization logic.
30297    initialize: function(){},
30298
30299    // Return a copy of the model's `attributes` object.
30300    toJSON: function(options) {
30301      return _.clone(this.attributes);
30302    },
30303
30304    // Proxy `Backbone.sync` by default -- but override this if you need
30305    // custom syncing semantics for *this* particular model.
30306    sync: function() {
30307      return Backbone.sync.apply(this, arguments);
30308    },
30309
30310    // Get the value of an attribute.
30311    get: function(attr) {
30312      return this.attributes[attr];
30313    },
30314
30315    // Get the HTML-escaped value of an attribute.
30316    escape: function(attr) {
30317      return _.escape(this.get(attr));
30318    },
30319
30320    // Returns `true` if the attribute contains a value that is not null
30321    // or undefined.
30322    has: function(attr) {
30323      return this.get(attr) != null;
30324    },
30325
30326    // Set a hash of model attributes on the object, firing `"change"`. This is
30327    // the core primitive operation of a model, updating the data and notifying
30328    // anyone who needs to know about the change in state. The heart of the beast.
30329    set: function(key, val, options) {
30330      var attr, attrs, unset, changes, silent, changing, prev, current;
30331      if (key == null) return this;
30332
30333      // Handle both `"key", value` and `{key: value}` -style arguments.
30334      if (typeof key === 'object') {
30335        attrs = key;
30336        options = val;
30337      } else {
30338        (attrs = {})[key] = val;
30339      }
30340
30341      options || (options = {});
30342
30343      // Run validation.
30344      if (!this._validate(attrs, options)) return false;
30345
30346      // Extract attributes and options.
30347      unset           = options.unset;
30348      silent          = options.silent;
30349      changes         = [];
30350      changing        = this._changing;
30351      this._changing  = true;
30352
30353      if (!changing) {
30354        this._previousAttributes = _.clone(this.attributes);
30355        this.changed = {};
30356      }
30357      current = this.attributes, prev = this._previousAttributes;
30358
30359      // Check for changes of `id`.
30360      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
30361
30362      // For each `set` attribute, update or delete the current value.
30363      for (attr in attrs) {
30364        val = attrs[attr];
30365        if (!_.isEqual(current[attr], val)) changes.push(attr);
30366        if (!_.isEqual(prev[attr], val)) {
30367          this.changed[attr] = val;
30368        } else {
30369          delete this.changed[attr];
30370        }
30371        unset ? delete current[attr] : current[attr] = val;
30372      }
30373
30374      // Trigger all relevant attribute changes.
30375      if (!silent) {
30376        if (changes.length) this._pending = options;
30377        for (var i = 0, l = changes.length; i < l; i++) {
30378          this.trigger('change:' + changes[i], this, current[changes[i]], options);
30379        }
30380      }
30381
30382      // You might be wondering why there's a `while` loop here. Changes can
30383      // be recursively nested within `"change"` events.
30384      if (changing) return this;
30385      if (!silent) {
30386        while (this._pending) {
30387          options = this._pending;
30388          this._pending = false;
30389          this.trigger('change', this, options);
30390        }
30391      }
30392      this._pending = false;
30393      this._changing = false;
30394      return this;
30395    },
30396
30397    // Remove an attribute from the model, firing `"change"`. `unset` is a noop
30398    // if the attribute doesn't exist.
30399    unset: function(attr, options) {
30400      return this.set(attr, void 0, _.extend({}, options, {unset: true}));
30401    },
30402
30403    // Clear all attributes on the model, firing `"change"`.
30404    clear: function(options) {
30405      var attrs = {};
30406      for (var key in this.attributes) attrs[key] = void 0;
30407      return this.set(attrs, _.extend({}, options, {unset: true}));
30408    },
30409
30410    // Determine if the model has changed since the last `"change"` event.
30411    // If you specify an attribute name, determine if that attribute has changed.
30412    hasChanged: function(attr) {
30413      if (attr == null) return !_.isEmpty(this.changed);
30414      return _.has(this.changed, attr);
30415    },
30416
30417    // Return an object containing all the attributes that have changed, or
30418    // false if there are no changed attributes. Useful for determining what
30419    // parts of a view need to be updated and/or what attributes need to be
30420    // persisted to the server. Unset attributes will be set to undefined.
30421    // You can also pass an attributes object to diff against the model,
30422    // determining if there *would be* a change.
30423    changedAttributes: function(diff) {
30424      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
30425      var val, changed = false;
30426      var old = this._changing ? this._previousAttributes : this.attributes;
30427      for (var attr in diff) {
30428        if (_.isEqual(old[attr], (val = diff[attr]))) continue;
30429        (changed || (changed = {}))[attr] = val;
30430      }
30431      return changed;
30432    },
30433
30434    // Get the previous value of an attribute, recorded at the time the last
30435    // `"change"` event was fired.
30436    previous: function(attr) {
30437      if (attr == null || !this._previousAttributes) return null;
30438      return this._previousAttributes[attr];
30439    },
30440
30441    // Get all of the attributes of the model at the time of the previous
30442    // `"change"` event.
30443    previousAttributes: function() {
30444      return _.clone(this._previousAttributes);
30445    },
30446
30447    // Fetch the model from the server. If the server's representation of the
30448    // model differs from its current attributes, they will be overridden,
30449    // triggering a `"change"` event.
30450    fetch: function(options) {
30451      options = options ? _.clone(options) : {};
30452      if (options.parse === void 0) options.parse = true;
30453      var model = this;
30454      var success = options.success;
30455      options.success = function(resp) {
30456        if (!model.set(model.parse(resp, options), options)) return false;
30457        if (success) success(model, resp, options);
30458        model.trigger('sync', model, resp, options);
30459      };
30460      wrapError(this, options);
30461      return this.sync('read', this, options);
30462    },
30463
30464    // Set a hash of model attributes, and sync the model to the server.
30465    // If the server returns an attributes hash that differs, the model's
30466    // state will be `set` again.
30467    save: function(key, val, options) {
30468      var attrs, method, xhr, attributes = this.attributes;
30469
30470      // Handle both `"key", value` and `{key: value}` -style arguments.
30471      if (key == null || typeof key === 'object') {
30472        attrs = key;
30473        options = val;
30474      } else {
30475        (attrs = {})[key] = val;
30476      }
30477
30478      options = _.extend({validate: true}, options);
30479
30480      // If we're not waiting and attributes exist, save acts as
30481      // `set(attr).save(null, opts)` with validation. Otherwise, check if
30482      // the model will be valid when the attributes, if any, are set.
30483      if (attrs && !options.wait) {
30484        if (!this.set(attrs, options)) return false;
30485      } else {
30486        if (!this._validate(attrs, options)) return false;
30487      }
30488
30489      // Set temporary attributes if `{wait: true}`.
30490      if (attrs && options.wait) {
30491        this.attributes = _.extend({}, attributes, attrs);
30492      }
30493
30494      // After a successful server-side save, the client is (optionally)
30495      // updated with the server-side state.
30496      if (options.parse === void 0) options.parse = true;
30497      var model = this;
30498      var success = options.success;
30499      options.success = function(resp) {
30500        // Ensure attributes are restored during synchronous saves.
30501        model.attributes = attributes;
30502        var serverAttrs = model.parse(resp, options);
30503        if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
30504        if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
30505          return false;
30506        }
30507        if (success) success(model, resp, options);
30508        model.trigger('sync', model, resp, options);
30509      };
30510      wrapError(this, options);
30511
30512      method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
30513      if (method === 'patch') options.attrs = attrs;
30514      xhr = this.sync(method, this, options);
30515
30516      // Restore attributes.
30517      if (attrs && options.wait) this.attributes = attributes;
30518
30519      return xhr;
30520    },
30521
30522    // Destroy this model on the server if it was already persisted.
30523    // Optimistically removes the model from its collection, if it has one.
30524    // If `wait: true` is passed, waits for the server to respond before removal.
30525    destroy: function(options) {
30526      options = options ? _.clone(options) : {};
30527      var model = this;
30528      var success = options.success;
30529
30530      var destroy = function() {
30531        model.trigger('destroy', model, model.collection, options);
30532      };
30533
30534      options.success = function(resp) {
30535        if (options.wait || model.isNew()) destroy();
30536        if (success) success(model, resp, options);
30537        if (!model.isNew()) model.trigger('sync', model, resp, options);
30538      };
30539
30540      if (this.isNew()) {
30541        options.success();
30542        return false;
30543      }
30544      wrapError(this, options);
30545
30546      var xhr = this.sync('delete', this, options);
30547      if (!options.wait) destroy();
30548      return xhr;
30549    },
30550
30551    // Default URL for the model's representation on the server -- if you're
30552    // using Backbone's restful methods, override this to change the endpoint
30553    // that will be called.
30554    url: function() {
30555      var base =
30556        _.result(this, 'urlRoot') ||
30557        _.result(this.collection, 'url') ||
30558        urlError();
30559      if (this.isNew()) return base;
30560      return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
30561    },
30562
30563    // **parse** converts a response into the hash of attributes to be `set` on
30564    // the model. The default implementation is just to pass the response along.
30565    parse: function(resp, options) {
30566      return resp;
30567    },
30568
30569    // Create a new model with identical attributes to this one.
30570    clone: function() {
30571      return new this.constructor(this.attributes);
30572    },
30573
30574    // A model is new if it has never been saved to the server, and lacks an id.
30575    isNew: function() {
30576      return !this.has(this.idAttribute);
30577    },
30578
30579    // Check if the model is currently in a valid state.
30580    isValid: function(options) {
30581      return this._validate({}, _.extend(options || {}, { validate: true }));
30582    },
30583
30584    // Run validation against the next complete set of model attributes,
30585    // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
30586    _validate: function(attrs, options) {
30587      if (!options.validate || !this.validate) return true;
30588      attrs = _.extend({}, this.attributes, attrs);
30589      var error = this.validationError = this.validate(attrs, options) || null;
30590      if (!error) return true;
30591      this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
30592      return false;
30593    }
30594
30595  });
30596
30597  // Underscore methods that we want to implement on the Model.
30598  var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
30599
30600  // Mix in each Underscore method as a proxy to `Model#attributes`.
30601  _.each(modelMethods, function(method) {
30602    Model.prototype[method] = function() {
30603      var args = slice.call(arguments);
30604      args.unshift(this.attributes);
30605      return _[method].apply(_, args);
30606    };
30607  });
30608
30609  // Backbone.Collection
30610  // -------------------
30611
30612  // If models tend to represent a single row of data, a Backbone Collection is
30613  // more analagous to a table full of data ... or a small slice or page of that
30614  // table, or a collection of rows that belong together for a particular reason
30615  // -- all of the messages in this particular folder, all of the documents
30616  // belonging to this particular author, and so on. Collections maintain
30617  // indexes of their models, both in order, and for lookup by `id`.
30618
30619  // Create a new **Collection**, perhaps to contain a specific type of `model`.
30620  // If a `comparator` is specified, the Collection will maintain
30621  // its models in sort order, as they're added and removed.
30622  var Collection = Backbone.Collection = function(models, options) {
30623    options || (options = {});
30624    if (options.model) this.model = options.model;
30625    if (options.comparator !== void 0) this.comparator = options.comparator;
30626    this._reset();
30627    this.initialize.apply(this, arguments);
30628    if (models) this.reset(models, _.extend({silent: true}, options));
30629  };
30630
30631  // Default options for `Collection#set`.
30632  var setOptions = {add: true, remove: true, merge: true};
30633  var addOptions = {add: true, remove: false};
30634
30635  // Define the Collection's inheritable methods.
30636  _.extend(Collection.prototype, Events, {
30637
30638    // The default model for a collection is just a **Backbone.Model**.
30639    // This should be overridden in most cases.
30640    model: Model,
30641
30642    // Initialize is an empty function by default. Override it with your own
30643    // initialization logic.
30644    initialize: function(){},
30645
30646    // The JSON representation of a Collection is an array of the
30647    // models' attributes.
30648    toJSON: function(options) {
30649      return this.map(function(model){ return model.toJSON(options); });
30650    },
30651
30652    // Proxy `Backbone.sync` by default.
30653    sync: function() {
30654      return Backbone.sync.apply(this, arguments);
30655    },
30656
30657    // Add a model, or list of models to the set.
30658    add: function(models, options) {
30659      return this.set(models, _.extend({merge: false}, options, addOptions));
30660    },
30661
30662    // Remove a model, or a list of models from the set.
30663    remove: function(models, options) {
30664      var singular = !_.isArray(models);
30665      models = singular ? [models] : _.clone(models);
30666      options || (options = {});
30667      var i, l, index, model;
30668      for (i = 0, l = models.length; i < l; i++) {
30669        model = models[i] = this.get(models[i]);
30670        if (!model) continue;
30671        delete this._byId[model.id];
30672        delete this._byId[model.cid];
30673        index = this.indexOf(model);
30674        this.models.splice(index, 1);
30675        this.length--;
30676        if (!options.silent) {
30677          options.index = index;
30678          model.trigger('remove', model, this, options);
30679        }
30680        this._removeReference(model, options);
30681      }
30682      return singular ? models[0] : models;
30683    },
30684
30685    // Update a collection by `set`-ing a new list of models, adding new ones,
30686    // removing models that are no longer present, and merging models that
30687    // already exist in the collection, as necessary. Similar to **Model#set**,
30688    // the core operation for updating the data contained by the collection.
30689    set: function(models, options) {
30690      options = _.defaults({}, options, setOptions);
30691      if (options.parse) models = this.parse(models, options);
30692      var singular = !_.isArray(models);
30693      models = singular ? (models ? [models] : []) : _.clone(models);
30694      var i, l, id, model, attrs, existing, sort;
30695      var at = options.at;
30696      var targetModel = this.model;
30697      var sortable = this.comparator && (at == null) && options.sort !== false;
30698      var sortAttr = _.isString(this.comparator) ? this.comparator : null;
30699      var toAdd = [], toRemove = [], modelMap = {};
30700      var add = options.add, merge = options.merge, remove = options.remove;
30701      var order = !sortable && add && remove ? [] : false;
30702
30703      // Turn bare objects into model references, and prevent invalid models
30704      // from being added.
30705      for (i = 0, l = models.length; i < l; i++) {
30706        attrs = models[i] || {};
30707        if (attrs instanceof Model) {
30708          id = model = attrs;
30709        } else {
30710          id = attrs[targetModel.prototype.idAttribute || 'id'];
30711        }
30712
30713        // If a duplicate is found, prevent it from being added and
30714        // optionally merge it into the existing model.
30715        if (existing = this.get(id)) {
30716          if (remove) modelMap[existing.cid] = true;
30717          if (merge) {
30718            attrs = attrs === model ? model.attributes : attrs;
30719            if (options.parse) attrs = existing.parse(attrs, options);
30720            existing.set(attrs, options);
30721            if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
30722          }
30723          models[i] = existing;
30724
30725        // If this is a new, valid model, push it to the `toAdd` list.
30726        } else if (add) {
30727          model = models[i] = this._prepareModel(attrs, options);
30728          if (!model) continue;
30729          toAdd.push(model);
30730          this._addReference(model, options);
30731        }
30732
30733        // Do not add multiple models with the same `id`.
30734        model = existing || model;
30735        if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
30736        modelMap[model.id] = true;
30737      }
30738
30739      // Remove nonexistent models if appropriate.
30740      if (remove) {
30741        for (i = 0, l = this.length; i < l; ++i) {
30742          if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
30743        }
30744        if (toRemove.length) this.remove(toRemove, options);
30745      }
30746
30747      // See if sorting is needed, update `length` and splice in new models.
30748      if (toAdd.length || (order && order.length)) {
30749        if (sortable) sort = true;
30750        this.length += toAdd.length;
30751        if (at != null) {
30752          for (i = 0, l = toAdd.length; i < l; i++) {
30753            this.models.splice(at + i, 0, toAdd[i]);
30754          }
30755        } else {
30756          if (order) this.models.length = 0;
30757          var orderedModels = order || toAdd;
30758          for (i = 0, l = orderedModels.length; i < l; i++) {
30759            this.models.push(orderedModels[i]);
30760          }
30761        }
30762      }
30763
30764      // Silently sort the collection if appropriate.
30765      if (sort) this.sort({silent: true});
30766
30767      // Unless silenced, it's time to fire all appropriate add/sort events.
30768      if (!options.silent) {
30769        for (i = 0, l = toAdd.length; i < l; i++) {
30770          (model = toAdd[i]).trigger('add', model, this, options);
30771        }
30772        if (sort || (order && order.length)) this.trigger('sort', this, options);
30773      }
30774
30775      // Return the added (or merged) model (or models).
30776      return singular ? models[0] : models;
30777    },
30778
30779    // When you have more items than you want to add or remove individually,
30780    // you can reset the entire set with a new list of models, without firing
30781    // any granular `add` or `remove` events. Fires `reset` when finished.
30782    // Useful for bulk operations and optimizations.
30783    reset: function(models, options) {
30784      options || (options = {});
30785      for (var i = 0, l = this.models.length; i < l; i++) {
30786        this._removeReference(this.models[i], options);
30787      }
30788      options.previousModels = this.models;
30789      this._reset();
30790      models = this.add(models, _.extend({silent: true}, options));
30791      if (!options.silent) this.trigger('reset', this, options);
30792      return models;
30793    },
30794
30795    // Add a model to the end of the collection.
30796    push: function(model, options) {
30797      return this.add(model, _.extend({at: this.length}, options));
30798    },
30799
30800    // Remove a model from the end of the collection.
30801    pop: function(options) {
30802      var model = this.at(this.length - 1);
30803      this.remove(model, options);
30804      return model;
30805    },
30806
30807    // Add a model to the beginning of the collection.
30808    unshift: function(model, options) {
30809      return this.add(model, _.extend({at: 0}, options));
30810    },
30811
30812    // Remove a model from the beginning of the collection.
30813    shift: function(options) {
30814      var model = this.at(0);
30815      this.remove(model, options);
30816      return model;
30817    },
30818
30819    // Slice out a sub-array of models from the collection.
30820    slice: function() {
30821      return slice.apply(this.models, arguments);
30822    },
30823
30824    // Get a model from the set by id.
30825    get: function(obj) {
30826      if (obj == null) return void 0;
30827      return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];
30828    },
30829
30830    // Get the model at the given index.
30831    at: function(index) {
30832      return this.models[index];
30833    },
30834
30835    // Return models with matching attributes. Useful for simple cases of
30836    // `filter`.
30837    where: function(attrs, first) {
30838      if (_.isEmpty(attrs)) return first ? void 0 : [];
30839      return this[first ? 'find' : 'filter'](function(model) {
30840        for (var key in attrs) {
30841          if (attrs[key] !== model.get(key)) return false;
30842        }
30843        return true;
30844      });
30845    },
30846
30847    // Return the first model with matching attributes. Useful for simple cases
30848    // of `find`.
30849    findWhere: function(attrs) {
30850      return this.where(attrs, true);
30851    },
30852
30853    // Force the collection to re-sort itself. You don't need to call this under
30854    // normal circumstances, as the set will maintain sort order as each item
30855    // is added.
30856    sort: function(options) {
30857      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
30858      options || (options = {});
30859
30860      // Run sort based on type of `comparator`.
30861      if (_.isString(this.comparator) || this.comparator.length === 1) {
30862        this.models = this.sortBy(this.comparator, this);
30863      } else {
30864        this.models.sort(_.bind(this.comparator, this));
30865      }
30866
30867      if (!options.silent) this.trigger('sort', this, options);
30868      return this;
30869    },
30870
30871    // Pluck an attribute from each model in the collection.
30872    pluck: function(attr) {
30873      return _.invoke(this.models, 'get', attr);
30874    },
30875
30876    // Fetch the default set of models for this collection, resetting the
30877    // collection when they arrive. If `reset: true` is passed, the response
30878    // data will be passed through the `reset` method instead of `set`.
30879    fetch: function(options) {
30880      options = options ? _.clone(options) : {};
30881      if (options.parse === void 0) options.parse = true;
30882      var success = options.success;
30883      var collection = this;
30884      options.success = function(resp) {
30885        var method = options.reset ? 'reset' : 'set';
30886        collection[method](resp, options);
30887        if (success) success(collection, resp, options);
30888        collection.trigger('sync', collection, resp, options);
30889      };
30890      wrapError(this, options);
30891      return this.sync('read', this, options);
30892    },
30893
30894    // Create a new instance of a model in this collection. Add the model to the
30895    // collection immediately, unless `wait: true` is passed, in which case we
30896    // wait for the server to agree.
30897    create: function(model, options) {
30898      options = options ? _.clone(options) : {};
30899      if (!(model = this._prepareModel(model, options))) return false;
30900      if (!options.wait) this.add(model, options);
30901      var collection = this;
30902      var success = options.success;
30903      options.success = function(model, resp) {
30904        if (options.wait) collection.add(model, options);
30905        if (success) success(model, resp, options);
30906      };
30907      model.save(null, options);
30908      return model;
30909    },
30910
30911    // **parse** converts a response into a list of models to be added to the
30912    // collection. The default implementation is just to pass it through.
30913    parse: function(resp, options) {
30914      return resp;
30915    },
30916
30917    // Create a new collection with an identical list of models as this one.
30918    clone: function() {
30919      return new this.constructor(this.models);
30920    },
30921
30922    // Private method to reset all internal state. Called when the collection
30923    // is first initialized or reset.
30924    _reset: function() {
30925      this.length = 0;
30926      this.models = [];
30927      this._byId  = {};
30928    },
30929
30930    // Prepare a hash of attributes (or other model) to be added to this
30931    // collection.
30932    _prepareModel: function(attrs, options) {
30933      if (attrs instanceof Model) return attrs;
30934      options = options ? _.clone(options) : {};
30935      options.collection = this;
30936      var model = new this.model(attrs, options);
30937      if (!model.validationError) return model;
30938      this.trigger('invalid', this, model.validationError, options);
30939      return false;
30940    },
30941
30942    // Internal method to create a model's ties to a collection.
30943    _addReference: function(model, options) {
30944      this._byId[model.cid] = model;
30945      if (model.id != null) this._byId[model.id] = model;
30946      if (!model.collection) model.collection = this;
30947      model.on('all', this._onModelEvent, this);
30948    },
30949
30950    // Internal method to sever a model's ties to a collection.
30951    _removeReference: function(model, options) {
30952      if (this === model.collection) delete model.collection;
30953      model.off('all', this._onModelEvent, this);
30954    },
30955
30956    // Internal method called every time a model in the set fires an event.
30957    // Sets need to update their indexes when models change ids. All other
30958    // events simply proxy through. "add" and "remove" events that originate
30959    // in other collections are ignored.
30960    _onModelEvent: function(event, model, collection, options) {
30961      if ((event === 'add' || event === 'remove') && collection !== this) return;
30962      if (event === 'destroy') this.remove(model, options);
30963      if (model && event === 'change:' + model.idAttribute) {
30964        delete this._byId[model.previous(model.idAttribute)];
30965        if (model.id != null) this._byId[model.id] = model;
30966      }
30967      this.trigger.apply(this, arguments);
30968    }
30969
30970  });
30971
30972  // Underscore methods that we want to implement on the Collection.
30973  // 90% of the core usefulness of Backbone Collections is actually implemented
30974  // right here:
30975  var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
30976    'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
30977    'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
30978    'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
30979    'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
30980    'lastIndexOf', 'isEmpty', 'chain', 'sample'];
30981
30982  // Mix in each Underscore method as a proxy to `Collection#models`.
30983  _.each(methods, function(method) {
30984    Collection.prototype[method] = function() {
30985      var args = slice.call(arguments);
30986      args.unshift(this.models);
30987      return _[method].apply(_, args);
30988    };
30989  });
30990
30991  // Underscore methods that take a property name as an argument.
30992  var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
30993
30994  // Use attributes instead of properties.
30995  _.each(attributeMethods, function(method) {
30996    Collection.prototype[method] = function(value, context) {
30997      var iterator = _.isFunction(value) ? value : function(model) {
30998        return model.get(value);
30999      };
31000      return _[method](this.models, iterator, context);
31001    };
31002  });
31003
31004  // Backbone.View
31005  // -------------
31006
31007  // Backbone Views are almost more convention than they are actual code. A View
31008  // is simply a JavaScript object that represents a logical chunk of UI in the
31009  // DOM. This might be a single item, an entire list, a sidebar or panel, or
31010  // even the surrounding frame which wraps your whole app. Defining a chunk of
31011  // UI as a **View** allows you to define your DOM events declaratively, without
31012  // having to worry about render order ... and makes it easy for the view to
31013  // react to specific changes in the state of your models.
31014
31015  // Creating a Backbone.View creates its initial element outside of the DOM,
31016  // if an existing element is not provided...
31017  var View = Backbone.View = function(options) {
31018    this.cid = _.uniqueId('view');
31019    options || (options = {});
31020    _.extend(this, _.pick(options, viewOptions));
31021    this._ensureElement();
31022    this.initialize.apply(this, arguments);
31023    this.delegateEvents();
31024  };
31025
31026  // Cached regex to split keys for `delegate`.
31027  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
31028
31029  // List of view options to be merged as properties.
31030  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
31031
31032  // Set up all inheritable **Backbone.View** properties and methods.
31033  _.extend(View.prototype, Events, {
31034
31035    // The default `tagName` of a View's element is `"div"`.
31036    tagName: 'div',
31037
31038    // jQuery delegate for element lookup, scoped to DOM elements within the
31039    // current view. This should be preferred to global lookups where possible.
31040    $: function(selector) {
31041      return this.$el.find(selector);
31042    },
31043
31044    // Initialize is an empty function by default. Override it with your own
31045    // initialization logic.
31046    initialize: function(){},
31047
31048    // **render** is the core function that your view should override, in order
31049    // to populate its element (`this.el`), with the appropriate HTML. The
31050    // convention is for **render** to always return `this`.
31051    render: function() {
31052      return this;
31053    },
31054
31055    // Remove this view by taking the element out of the DOM, and removing any
31056    // applicable Backbone.Events listeners.
31057    remove: function() {
31058      this.$el.remove();
31059      this.stopListening();
31060      return this;
31061    },
31062
31063    // Change the view's element (`this.el` property), including event
31064    // re-delegation.
31065    setElement: function(element, delegate) {
31066      if (this.$el) this.undelegateEvents();
31067      this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
31068      this.el = this.$el[0];
31069      if (delegate !== false) this.delegateEvents();
31070      return this;
31071    },
31072
31073    // Set callbacks, where `this.events` is a hash of
31074    //
31075    // *{"event selector": "callback"}*
31076    //
31077    //     {
31078    //       'mousedown .title':  'edit',
31079    //       'click .button':     'save',
31080    //       'click .open':       function(e) { ... }
31081    //     }
31082    //
31083    // pairs. Callbacks will be bound to the view, with `this` set properly.
31084    // Uses event delegation for efficiency.
31085    // Omitting the selector binds the event to `this.el`.
31086    // This only works for delegate-able events: not `focus`, `blur`, and
31087    // not `change`, `submit`, and `reset` in Internet Explorer.
31088    delegateEvents: function(events) {
31089      if (!(events || (events = _.result(this, 'events')))) return this;
31090      this.undelegateEvents();
31091      for (var key in events) {
31092        var method = events[key];
31093        if (!_.isFunction(method)) method = this[events[key]];
31094        if (!method) continue;
31095
31096        var match = key.match(delegateEventSplitter);
31097        var eventName = match[1], selector = match[2];
31098        method = _.bind(method, this);
31099        eventName += '.delegateEvents' + this.cid;
31100        if (selector === '') {
31101          this.$el.on(eventName, method);
31102        } else {
31103          this.$el.on(eventName, selector, method);
31104        }
31105      }
31106      return this;
31107    },
31108
31109    // Clears all callbacks previously bound to the view with `delegateEvents`.
31110    // You usually don't need to use this, but may wish to if you have multiple
31111    // Backbone views attached to the same DOM element.
31112    undelegateEvents: function() {
31113      this.$el.off('.delegateEvents' + this.cid);
31114      return this;
31115    },
31116
31117    // Ensure that the View has a DOM element to render into.
31118    // If `this.el` is a string, pass it through `$()`, take the first
31119    // matching element, and re-assign it to `el`. Otherwise, create
31120    // an element from the `id`, `className` and `tagName` properties.
31121    _ensureElement: function() {
31122      if (!this.el) {
31123        var attrs = _.extend({}, _.result(this, 'attributes'));
31124        if (this.id) attrs.id = _.result(this, 'id');
31125        if (this.className) attrs['class'] = _.result(this, 'className');
31126        var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
31127        this.setElement($el, false);
31128      } else {
31129        this.setElement(_.result(this, 'el'), false);
31130      }
31131    }
31132
31133  });
31134
31135  // Backbone.sync
31136  // -------------
31137
31138  // Override this function to change the manner in which Backbone persists
31139  // models to the server. You will be passed the type of request, and the
31140  // model in question. By default, makes a RESTful Ajax request
31141  // to the model's `url()`. Some possible customizations could be:
31142  //
31143  // * Use `setTimeout` to batch rapid-fire updates into a single request.
31144  // * Send up the models as XML instead of JSON.
31145  // * Persist models via WebSockets instead of Ajax.
31146  //
31147  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
31148  // as `POST`, with a `_method` parameter containing the true HTTP method,
31149  // as well as all requests with the body as `application/x-www-form-urlencoded`
31150  // instead of `application/json` with the model in a param named `model`.
31151  // Useful when interfacing with server-side languages like **PHP** that make
31152  // it difficult to read the body of `PUT` requests.
31153  Backbone.sync = function(method, model, options) {
31154    var type = methodMap[method];
31155
31156    // Default options, unless specified.
31157    _.defaults(options || (options = {}), {
31158      emulateHTTP: Backbone.emulateHTTP,
31159      emulateJSON: Backbone.emulateJSON
31160    });
31161
31162    // Default JSON-request options.
31163    var params = {type: type, dataType: 'json'};
31164
31165    // Ensure that we have a URL.
31166    if (!options.url) {
31167      params.url = _.result(model, 'url') || urlError();
31168    }
31169
31170    // Ensure that we have the appropriate request data.
31171    if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
31172      params.contentType = 'application/json';
31173      params.data = JSON.stringify(options.attrs || model.toJSON(options));
31174    }
31175
31176    // For older servers, emulate JSON by encoding the request into an HTML-form.
31177    if (options.emulateJSON) {
31178      params.contentType = 'application/x-www-form-urlencoded';
31179      params.data = params.data ? {model: params.data} : {};
31180    }
31181
31182    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
31183    // And an `X-HTTP-Method-Override` header.
31184    if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
31185      params.type = 'POST';
31186      if (options.emulateJSON) params.data._method = type;
31187      var beforeSend = options.beforeSend;
31188      options.beforeSend = function(xhr) {
31189        xhr.setRequestHeader('X-HTTP-Method-Override', type);
31190        if (beforeSend) return beforeSend.apply(this, arguments);
31191      };
31192    }
31193
31194    // Don't process data on a non-GET request.
31195    if (params.type !== 'GET' && !options.emulateJSON) {
31196      params.processData = false;
31197    }
31198
31199    // If we're sending a `PATCH` request, and we're in an old Internet Explorer
31200    // that still has ActiveX enabled by default, override jQuery to use that
31201    // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
31202    if (params.type === 'PATCH' && noXhrPatch) {
31203      params.xhr = function() {
31204        return new ActiveXObject("Microsoft.XMLHTTP");
31205      };
31206    }
31207
31208    // Make the request, allowing the user to override any Ajax options.
31209    var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
31210    model.trigger('request', model, xhr, options);
31211    return xhr;
31212  };
31213
31214  var noXhrPatch =
31215    typeof window !== 'undefined' && !!window.ActiveXObject &&
31216      !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
31217
31218  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
31219  var methodMap = {
31220    'create': 'POST',
31221    'update': 'PUT',
31222    'patch':  'PATCH',
31223    'delete': 'DELETE',
31224    'read':   'GET'
31225  };
31226
31227  // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
31228  // Override this if you'd like to use a different library.
31229  Backbone.ajax = function() {
31230    return Backbone.$.ajax.apply(Backbone.$, arguments);
31231  };
31232
31233  // Backbone.Router
31234  // ---------------
31235
31236  // Routers map faux-URLs to actions, and fire events when routes are
31237  // matched. Creating a new one sets its `routes` hash, if not set statically.
31238  var Router = Backbone.Router = function(options) {
31239    options || (options = {});
31240    if (options.routes) this.routes = options.routes;
31241    this._bindRoutes();
31242    this.initialize.apply(this, arguments);
31243  };
31244
31245  // Cached regular expressions for matching named param parts and splatted
31246  // parts of route strings.
31247  var optionalParam = /\((.*?)\)/g;
31248  var namedParam    = /(\(\?)?:\w+/g;
31249  var splatParam    = /\*\w+/g;
31250  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
31251
31252  // Set up all inheritable **Backbone.Router** properties and methods.
31253  _.extend(Router.prototype, Events, {
31254
31255    // Initialize is an empty function by default. Override it with your own
31256    // initialization logic.
31257    initialize: function(){},
31258
31259    // Manually bind a single named route to a callback. For example:
31260    //
31261    //     this.route('search/:query/p:num', 'search', function(query, num) {
31262    //       ...
31263    //     });
31264    //
31265    route: function(route, name, callback) {
31266      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
31267      if (_.isFunction(name)) {
31268        callback = name;
31269        name = '';
31270      }
31271      if (!callback) callback = this[name];
31272      var router = this;
31273      Backbone.history.route(route, function(fragment) {
31274        var args = router._extractParameters(route, fragment);
31275        router.execute(callback, args);
31276        router.trigger.apply(router, ['route:' + name].concat(args));
31277        router.trigger('route', name, args);
31278        Backbone.history.trigger('route', router, name, args);
31279      });
31280      return this;
31281    },
31282
31283    // Execute a route handler with the provided parameters.  This is an
31284    // excellent place to do pre-route setup or post-route cleanup.
31285    execute: function(callback, args) {
31286      if (callback) callback.apply(this, args);
31287    },
31288
31289    // Simple proxy to `Backbone.history` to save a fragment into the history.
31290    navigate: function(fragment, options) {
31291      Backbone.history.navigate(fragment, options);
31292      return this;
31293    },
31294
31295    // Bind all defined routes to `Backbone.history`. We have to reverse the
31296    // order of the routes here to support behavior where the most general
31297    // routes can be defined at the bottom of the route map.
31298    _bindRoutes: function() {
31299      if (!this.routes) return;
31300      this.routes = _.result(this, 'routes');
31301      var route, routes = _.keys(this.routes);
31302      while ((route = routes.pop()) != null) {
31303        this.route(route, this.routes[route]);
31304      }
31305    },
31306
31307    // Convert a route string into a regular expression, suitable for matching
31308    // against the current location hash.
31309    _routeToRegExp: function(route) {
31310      route = route.replace(escapeRegExp, '\\$&')
31311                   .replace(optionalParam, '(?:$1)?')
31312                   .replace(namedParam, function(match, optional) {
31313                     return optional ? match : '([^/?]+)';
31314                   })
31315                   .replace(splatParam, '([^?]*?)');
31316      return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
31317    },
31318
31319    // Given a route, and a URL fragment that it matches, return the array of
31320    // extracted decoded parameters. Empty or unmatched parameters will be
31321    // treated as `null` to normalize cross-browser behavior.
31322    _extractParameters: function(route, fragment) {
31323      var params = route.exec(fragment).slice(1);
31324      return _.map(params, function(param, i) {
31325        // Don't decode the search params.
31326        if (i === params.length - 1) return param || null;
31327        if (!param) return null
31328        try {
31329          return decodeURIComponent(param)
31330        } catch (e) {
31331          if (typeof window !== 'undefined' && window.Sentry && window.Sentry.captureException) {
31332            window.Sentry.captureException(new Error('URL parameter decoding failed'), {
31333              tags: {
31334                error_type: 'url_decode_failure',
31335                library: 'backbone',
31336                version: '1.1.2',
31337              },
31338              extra: {
31339                original_param: param,
31340                param_index: i,
31341                route_fragment: fragment,
31342                error_message: e.message,
31343                stack_trace: e.stack,
31344                timestamp: new Date().toISOString(),
31345                page_url: typeof window !== 'undefined' ? window.location.href : 'unknown',
31346              },
31347              level: 'warning',
31348            })
31349          }
31350          return param
31351        }
31352      });
31353    }
31354  });
31355
31356  // Backbone.History
31357  // ----------------
31358
31359  // Handles cross-browser history management, based on either
31360  // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
31361  // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
31362  // and URL fragments. If the browser supports neither (old IE, natch),
31363  // falls back to polling.
31364  var History = Backbone.History = function() {
31365    this.handlers = [];
31366    _.bindAll(this, 'checkUrl');
31367
31368    // Ensure that `History` can be used outside of the browser.
31369    if (typeof window !== 'undefined') {
31370      this.location = window.location;
31371      this.history = window.history;
31372    }
31373  };
31374
31375  // Cached regex for stripping a leading hash/slash and trailing space.
31376  var routeStripper = /^[#\/]|\s+$/g;
31377
31378  // Cached regex for stripping leading and trailing slashes.
31379  var rootStripper = /^\/+|\/+$/g;
31380
31381  // Cached regex for detecting MSIE.
31382  var isExplorer = /msie [\w.]+/;
31383
31384  // Cached regex for removing a trailing slash.
31385  var trailingSlash = /\/$/;
31386
31387  // Cached regex for stripping urls of hash.
31388  var pathStripper = /#.*$/;
31389
31390  // Has the history handling already been started?
31391  History.started = false;
31392
31393  // Set up all inheritable **Backbone.History** properties and methods.
31394  _.extend(History.prototype, Events, {
31395
31396    // The default interval to poll for hash changes, if necessary, is
31397    // twenty times a second.
31398    interval: 50,
31399
31400    // Are we at the app root?
31401    atRoot: function() {
31402      return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
31403    },
31404
31405    // Gets the true hash value. Cannot use location.hash directly due to bug
31406    // in Firefox where location.hash will always be decoded.
31407    getHash: function(window) {
31408      var match = (window || this).location.href.match(/#(.*)$/);
31409      return match ? match[1] : '';
31410    },
31411
31412    // Get the cross-browser normalized URL fragment, either from the URL,
31413    // the hash, or the override.
31414    getFragment: function(fragment, forcePushState) {
31415      if (fragment == null) {
31416        if (this._hasPushState || !this._wantsHashChange || forcePushState) {
31417          fragment = decodeURI(this.location.pathname + this.location.search);
31418          var root = this.root.replace(trailingSlash, '');
31419          if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
31420        } else {
31421          fragment = this.getHash();
31422        }
31423      }
31424      return fragment.replace(routeStripper, '');
31425    },
31426
31427    // Start the hash change handling, returning `true` if the current URL matches
31428    // an existing route, and `false` otherwise.
31429    start: function(options) {
31430      if (History.started) throw new Error("Backbone.history has already been started");
31431      History.started = true;
31432
31433      // Figure out the initial configuration. Do we need an iframe?
31434      // Is pushState desired ... is it available?
31435      this.options          = _.extend({root: '/'}, this.options, options);
31436      this.root             = this.options.root;
31437      this._wantsHashChange = this.options.hashChange !== false;
31438      this._wantsPushState  = !!this.options.pushState;
31439      this._hasPushState    = !!(this.options.pushState && this.history && this.history.pushState);
31440      var fragment          = this.getFragment();
31441      var docMode           = document.documentMode;
31442      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
31443
31444      // Normalize root to always include a leading and trailing slash.
31445      this.root = ('/' + this.root + '/').replace(rootStripper, '/');
31446
31447      if (oldIE && this._wantsHashChange) {
31448        var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
31449        this.iframe = frame.hide().appendTo('body')[0].contentWindow;
31450        this.navigate(fragment);
31451      }
31452
31453      // Depending on whether we're using pushState or hashes, and whether
31454      // 'onhashchange' is supported, determine how we check the URL state.
31455      if (this._hasPushState) {
31456        Backbone.$(window).on('popstate', this.checkUrl);
31457      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
31458        Backbone.$(window).on('hashchange', this.checkUrl);
31459      } else if (this._wantsHashChange) {
31460        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
31461      }
31462
31463      // Determine if we need to change the base url, for a pushState link
31464      // opened by a non-pushState browser.
31465      this.fragment = fragment;
31466      var loc = this.location;
31467
31468      // Transition from hashChange to pushState or vice versa if both are
31469      // requested.
31470      if (this._wantsHashChange && this._wantsPushState) {
31471
31472        // If we've started off with a route from a `pushState`-enabled
31473        // browser, but we're currently in a browser that doesn't support it...
31474        if (!this._hasPushState && !this.atRoot()) {
31475          this.fragment = this.getFragment(null, true);
31476          this.location.replace(this.root + '#' + this.fragment);
31477          // Return immediately as browser will do redirect to new url
31478          return true;
31479
31480        // Or if we've started out with a hash-based route, but we're currently
31481        // in a browser where it could be `pushState`-based instead...
31482        } else if (this._hasPushState && this.atRoot() && loc.hash) {
31483          this.fragment = this.getHash().replace(routeStripper, '');
31484          this.history.replaceState({}, document.title, this.root + this.fragment);
31485        }
31486
31487      }
31488
31489      if (!this.options.silent) return this.loadUrl();
31490    },
31491
31492    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
31493    // but possibly useful for unit testing Routers.
31494    stop: function() {
31495      Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
31496      if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
31497      History.started = false;
31498    },
31499
31500    // Add a route to be tested when the fragment changes. Routes added later
31501    // may override previous routes.
31502    route: function(route, callback) {
31503      this.handlers.unshift({route: route, callback: callback});
31504    },
31505
31506    // Checks the current URL to see if it has changed, and if it has,
31507    // calls `loadUrl`, normalizing across the hidden iframe.
31508    checkUrl: function(e) {
31509      var current = this.getFragment();
31510      if (current === this.fragment && this.iframe) {
31511        current = this.getFragment(this.getHash(this.iframe));
31512      }
31513      if (current === this.fragment) return false;
31514      if (this.iframe) this.navigate(current);
31515      this.loadUrl();
31516    },
31517
31518    // Attempt to load the current URL fragment. If a route succeeds with a
31519    // match, returns `true`. If no defined routes matches the fragment,
31520    // returns `false`.
31521    loadUrl: function(fragment) {
31522      fragment = this.fragment = this.getFragment(fragment);
31523      return _.any(this.handlers, function(handler) {
31524        if (handler.route.test(fragment)) {
31525          handler.callback(fragment);
31526          return true;
31527        }
31528      });
31529    },
31530
31531    // Save a fragment into the hash history, or replace the URL state if the
31532    // 'replace' option is passed. You are responsible for properly URL-encoding
31533    // the fragment in advance.
31534    //
31535    // The options object can contain `trigger: true` if you wish to have the
31536    // route callback be fired (not usually desirable), or `replace: true`, if
31537    // you wish to modify the current URL without adding an entry to the history.
31538    navigate: function(fragment, options) {
31539      if (!History.started) return false;
31540      if (!options || options === true) options = {trigger: !!options};
31541
31542      var url = this.root + (fragment = this.getFragment(fragment || ''));
31543
31544      // Strip the hash for matching.
31545      fragment = fragment.replace(pathStripper, '');
31546
31547      if (this.fragment === fragment) return;
31548      this.fragment = fragment;
31549
31550      // Don't include a trailing slash on the root.
31551      if (fragment === '' && url !== '/') url = url.slice(0, -1);
31552
31553      // If pushState is available, we use it to set the fragment as a real URL.
31554      if (this._hasPushState) {
31555        this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
31556
31557      // If hash changes haven't been explicitly disabled, update the hash
31558      // fragment to store history.
31559      } else if (this._wantsHashChange) {
31560        this._updateHash(this.location, fragment, options.replace);
31561        if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
31562          // Opening and closing the iframe tricks IE7 and earlier to push a
31563          // history entry on hash-tag change.  When replace is true, we don't
31564          // want this.
31565          if(!options.replace) this.iframe.document.open().close();
31566          this._updateHash(this.iframe.location, fragment, options.replace);
31567        }
31568
31569      // If you've told us that you explicitly don't want fallback hashchange-
31570      // based history, then `navigate` becomes a page refresh.
31571      } else {
31572        return this.location.assign(url);
31573      }
31574      if (options.trigger) return this.loadUrl(fragment);
31575    },
31576
31577    // Update the hash location, either replacing the current entry, or adding
31578    // a new one to the browser history.
31579    _updateHash: function(location, fragment, replace) {
31580      if (replace) {
31581        var href = location.href.replace(/(javascript:|#).*$/, '');
31582        location.replace(href + '#' + fragment);
31583      } else {
31584        // Some browsers require that `hash` contains a leading #.
31585        location.hash = '#' + fragment;
31586      }
31587    }
31588
31589  });
31590
31591  // Create the default Backbone.history.
31592  Backbone.history = new History;
31593
31594  // Helpers
31595  // -------
31596
31597  // Helper function to correctly set up the prototype chain, for subclasses.
31598  // Similar to `goog.inherits`, but uses a hash of prototype properties and
31599  // class properties to be extended.
31600  var extend = function(protoProps, staticProps) {
31601    var parent = this;
31602    var child;
31603
31604    // The constructor function for the new subclass is either defined by you
31605    // (the "constructor" property in your `extend` definition), or defaulted
31606    // by us to simply call the parent's constructor.
31607    if (protoProps && _.has(protoProps, 'constructor')) {
31608      child = protoProps.constructor;
31609    } else {
31610      child = function(){ return parent.apply(this, arguments); };
31611    }
31612
31613    // Add static properties to the constructor function, if supplied.
31614    _.extend(child, parent, staticProps);
31615
31616    // Set the prototype chain to inherit from `parent`, without calling
31617    // `parent`'s constructor function.
31618    var Surrogate = function(){ this.constructor = child; };
31619    Surrogate.prototype = parent.prototype;
31620    child.prototype = new Surrogate;
31621
31622    // Add prototype properties (instance properties) to the subclass,
31623    // if supplied.
31624    if (protoProps) _.extend(child.prototype, protoProps);
31625
31626    // Set a convenience property in case the parent's prototype is needed
31627    // later.
31628    child.__super__ = parent.prototype;
31629
31630    return child;
31631  };
31632
31633  // Set up inheritance for the model, collection, router, view and history.
31634  Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
31635
31636  // Throw an error when a URL is needed, and none is supplied.
31637  var urlError = function() {
31638    throw new Error('A "url" property or function must be specified');
31639  };
31640
31641  // Wrap an optional error callback with a fallback error event.
31642  var wrapError = function(model, options) {
31643    var error = options.error;
31644    options.error = function(resp) {
31645      if (error) error(model, resp, options);
31646      model.trigger('error', model, resp, options);
31647    };
31648  };
31649
31650  return Backbone;
31651
31652}));
31653// Backbone.Syphon, v0.4.1
31654// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
31655// Distributed under MIT license
31656// http://github.com/derickbailey/backbone.syphon
31657Backbone.Syphon = (function(Backbone, $, _){
31658  var Syphon = {};
31659
31660  // Ignore Element Types
31661  // --------------------
31662
31663  // Tell Syphon to ignore all elements of these types. You can
31664  // push new types to ignore directly in to this array.
31665  Syphon.ignoredTypes = ["button", "submit", "reset", "fieldset"];
31666
31667  // Syphon
31668  // ------
31669
31670  // Get a JSON object that represents
31671  // all of the form inputs, in this view.
31672  // Alternately, pass a form element directly
31673  // in place of the view.
31674  Syphon.serialize = function(view, options){
31675    var data = {};
31676
31677    // Build the configuration
31678    var config = buildConfig(options);
31679
31680    // Get all of the elements to process
31681    var elements = getInputElements(view, config);
31682
31683    // Process all of the elements
31684    _.each(elements, function(el){
31685      var $el = $(el);
31686      var type = getElementType($el);
31687
31688      // Get the key for the input
31689      var keyExtractor = config.keyExtractors.get(type);
31690      var key = keyExtractor($el);
31691
31692      // Get the value for the input
31693      var inputReader = config.inputReaders.get(type);
31694      var value = inputReader($el);
31695
31696      // Get the key assignment validator and make sure
31697      // it's valid before assigning the value to the key
31698      var validKeyAssignment = config.keyAssignmentValidators.get(type);
31699      if (validKeyAssignment($el, key, value)){
31700        var keychain = config.keySplitter(key);
31701        data = assignKeyValue(data, keychain, value);
31702      }
31703    });
31704
31705    // Done; send back the results.
31706    return data;
31707  };
31708
31709  // Use the given JSON object to populate
31710  // all of the form inputs, in this view.
31711  // Alternately, pass a form element directly
31712  // in place of the view.
31713  Syphon.deserialize = function(view, data, options){
31714    // Build the configuration
31715    var config = buildConfig(options);
31716
31717    // Get all of the elements to process
31718    var elements = getInputElements(view, config);
31719
31720    // Flatten the data structure that we are deserializing
31721    var flattenedData = flattenData(config, data);
31722
31723    // Process all of the elements
31724    _.each(elements, function(el){
31725      var $el = $(el);
31726      var type = getElementType($el);
31727
31728      // Get the key for the input
31729      var keyExtractor = config.keyExtractors.get(type);
31730      var key = keyExtractor($el);
31731
31732      // Get the input writer and the value to write
31733      var inputWriter = config.inputWriters.get(type);
31734      var value = flattenedData[key];
31735
31736      // Write the value to the input
31737      inputWriter($el, value);
31738    });
31739  };
31740
31741  // Helpers
31742  // -------
31743
31744  // Retrieve all of the form inputs
31745  // from the form
31746  var getInputElements = function(view, config){
31747    var form = getForm(view);
31748    var elements = form.elements;
31749
31750    elements = _.reject(elements, function(el){
31751      var reject;
31752      var type = getElementType(el);
31753      var extractor = config.keyExtractors.get(type);
31754      var identifier = extractor($(el));
31755
31756      var foundInIgnored = _.include(config.ignoredTypes, type);
31757      var foundInInclude = _.include(config.include, identifier);
31758      var foundInExclude = _.include(config.exclude, identifier);
31759
31760      if (foundInInclude){
31761        reject = false;
31762      } else {
31763        if (config.include){
31764          reject = true;
31765        } else {
31766          reject = (foundInExclude || foundInIgnored);
31767        }
31768      }
31769
31770      return reject;
31771    });
31772
31773    return elements;
31774  };
31775
31776  // Determine what type of element this is. It
31777  // will either return the `type` attribute of
31778  // an `<input>` element, or the `tagName` of
31779  // the element when the element is not an `<input>`.
31780  var getElementType = function(el){
31781    var typeAttr;
31782    var $el = $(el);
31783    var tagName = $el[0].tagName;
31784    var type = tagName;
31785
31786    if (tagName.toLowerCase() === "input"){
31787      typeAttr = $el.attr("type");
31788      if (typeAttr){
31789        type = typeAttr;
31790      } else {
31791        type = "text";
31792      }
31793    }
31794
31795    // Always return the type as lowercase
31796    // so it can be matched to lowercase
31797    // type registrations.
31798    return type.toLowerCase();
31799  };
31800
31801  // If a form element is given, just return it.
31802  // Otherwise, get the form element from the view.
31803  var getForm = function(viewOrForm){
31804    if (_.isUndefined(viewOrForm.$el) && viewOrForm.tagName.toLowerCase() === 'form'){
31805      return viewOrForm;
31806    } else {
31807      return viewOrForm.$el.is("form") ? viewOrForm.el : viewOrForm.$("form")[0];
31808    }
31809  };
31810
31811  // Build a configuration object and initialize
31812  // default values.
31813  var buildConfig = function(options){
31814    var config = _.clone(options) || {};
31815
31816    config.ignoredTypes = _.clone(Syphon.ignoredTypes);
31817    config.inputReaders = config.inputReaders || Syphon.InputReaders;
31818    config.inputWriters = config.inputWriters || Syphon.InputWriters;
31819    config.keyExtractors = config.keyExtractors || Syphon.KeyExtractors;
31820    config.keySplitter = config.keySplitter || Syphon.KeySplitter;
31821    config.keyJoiner = config.keyJoiner || Syphon.KeyJoiner;
31822    config.keyAssignmentValidators = config.keyAssignmentValidators || Syphon.KeyAssignmentValidators;
31823
31824    return config;
31825  };
31826
31827  // Assigns `value` to a parsed JSON key.
31828  //
31829  // The first parameter is the object which will be
31830  // modified to store the key/value pair.
31831  //
31832  // The second parameter accepts an array of keys as a
31833  // string with an option array containing a
31834  // single string as the last option.
31835  //
31836  // The third parameter is the value to be assigned.
31837  //
31838  // Examples:
31839  //
31840  // `["foo", "bar", "baz"] => {foo: {bar: {baz: "value"}}}`
31841  //
31842  // `["foo", "bar", ["baz"]] => {foo: {bar: {baz: ["value"]}}}`
31843  //
31844  // When the final value is an array with a string, the key
31845  // becomes an array, and values are pushed in to the array,
31846  // allowing multiple fields with the same name to be
31847  // assigned to the array.
31848  var assignKeyValue = function(obj, keychain, value) {
31849    if (!keychain){ return obj; }
31850
31851    var key = keychain.shift();
31852
31853    // build the current object we need to store data
31854    if (!obj[key]){
31855      obj[key] = _.isArray(key) ? [] : {};
31856    }
31857
31858    // if it's the last key in the chain, assign the value directly
31859    if (keychain.length === 0){
31860      if (_.isArray(obj[key])){
31861        obj[key].push(value);
31862      } else {
31863        obj[key] = value;
31864      }
31865    }
31866
31867    // recursive parsing of the array, depth-first
31868    if (keychain.length > 0){
31869      assignKeyValue(obj[key], keychain, value);
31870    }
31871
31872    return obj;
31873  };
31874
31875  // Flatten the data structure in to nested strings, using the
31876  // provided `KeyJoiner` function.
31877  //
31878  // Example:
31879  //
31880  // This input:
31881  //
31882  // ```js
31883  // {
31884  //   widget: "wombat",
31885  //   foo: {
31886  //     bar: "baz",
31887  //     baz: {
31888  //       quux: "qux"
31889  //     },
31890  //     quux: ["foo", "bar"]
31891  //   }
31892  // }
31893  // ```
31894  //
31895  // With a KeyJoiner that uses [ ] square brackets,
31896  // should produce this output:
31897  //
31898  // ```js
31899  // {
31900  //  "widget": "wombat",
31901  //  "foo[bar]": "baz",
31902  //  "foo[baz][quux]": "qux",
31903  //  "foo[quux]": ["foo", "bar"]
31904  // }
31905  // ```
31906  var flattenData = function(config, data, parentKey){
31907    var flatData = {};
31908
31909    _.each(data, function(value, keyName){
31910      var hash = {};
31911
31912      // If there is a parent key, join it with
31913      // the current, child key.
31914      if (parentKey){
31915        keyName = config.keyJoiner(parentKey, keyName);
31916      }
31917
31918      if (_.isArray(value)){
31919        keyName += "[]";
31920        hash[keyName] = value;
31921      } else if (_.isObject(value)){
31922        hash = flattenData(config, value, keyName);
31923      } else {
31924        hash[keyName] = value;
31925      }
31926
31927      // Store the resulting key/value pairs in the
31928      // final flattened data object
31929      _.extend(flatData, hash);
31930    });
31931
31932    return flatData;
31933  };
31934
31935  return Syphon;
31936})(Backbone, jQuery, _);
31937
31938// Type Registry
31939// -------------
31940
31941// Type Registries allow you to register something to
31942// an input type, and retrieve either the item registered
31943// for a specific type or the default registration
31944Backbone.Syphon.TypeRegistry = function(){
31945  this.registeredTypes = {};
31946};
31947
31948// Borrow Backbone's `extend` keyword for our TypeRegistry
31949Backbone.Syphon.TypeRegistry.extend = Backbone.Model.extend;
31950
31951_.extend(Backbone.Syphon.TypeRegistry.prototype, {
31952
31953  // Get the registered item by type. If nothing is
31954  // found for the specified type, the default is
31955  // returned.
31956  get: function(type){
31957    var item = this.registeredTypes[type];
31958
31959    if (!item){
31960      item = this.registeredTypes["default"];
31961    }
31962
31963    return item;
31964  },
31965
31966  // Register a new item for a specified type
31967  register: function(type, item){
31968    this.registeredTypes[type] = item;
31969  },
31970
31971  // Register a default item to be used when no
31972  // item for a specified type is found
31973  registerDefault: function(item){
31974    this.registeredTypes["default"] = item;
31975  },
31976
31977  // Remove an item from a given type registration
31978  unregister: function(type){
31979    if (this.registeredTypes[type]){
31980      delete this.registeredTypes[type];
31981    }
31982  }
31983});
31984
31985
31986
31987
31988// Key Extractors
31989// --------------
31990
31991// Key extractors produce the "key" in `{key: "value"}`
31992// pairs, when serializing.
31993Backbone.Syphon.KeyExtractorSet = Backbone.Syphon.TypeRegistry.extend();
31994
31995// Built-in Key Extractors
31996Backbone.Syphon.KeyExtractors = new Backbone.Syphon.KeyExtractorSet();
31997
31998// The default key extractor, which uses the
31999// input element's "id" attribute
32000Backbone.Syphon.KeyExtractors.registerDefault(function($el){
32001  return $el.prop("name");
32002});
32003
32004
32005// Input Readers
32006// -------------
32007
32008// Input Readers are used to extract the value from
32009// an input element, for the serialized object result
32010Backbone.Syphon.InputReaderSet = Backbone.Syphon.TypeRegistry.extend();
32011
32012// Built-in Input Readers
32013Backbone.Syphon.InputReaders = new Backbone.Syphon.InputReaderSet();
32014
32015// The default input reader, which uses an input
32016// element's "value"
32017Backbone.Syphon.InputReaders.registerDefault(function($el){
32018  return $el.val();
32019});
32020
32021// Checkbox reader, returning a boolean value for
32022// whether or not the checkbox is checked.
32023Backbone.Syphon.InputReaders.register("checkbox", function($el){
32024  var checked = $el.prop("checked");
32025  return checked;
32026});
32027
32028
32029// Input Writers
32030// -------------
32031
32032// Input Writers are used to insert a value from an
32033// object into an input element.
32034Backbone.Syphon.InputWriterSet = Backbone.Syphon.TypeRegistry.extend();
32035
32036// Built-in Input Writers
32037Backbone.Syphon.InputWriters = new Backbone.Syphon.InputWriterSet();
32038
32039// The default input writer, which sets an input
32040// element's "value"
32041Backbone.Syphon.InputWriters.registerDefault(function($el, value){
32042  $el.val(value);
32043});
32044
32045// Checkbox writer, set whether or not the checkbox is checked
32046// depending on the boolean value.
32047Backbone.Syphon.InputWriters.register("checkbox", function($el, value){
32048  $el.prop("checked", value);
32049});
32050
32051// Radio button writer, set whether or not the radio button is
32052// checked.  The button should only be checked if it's value
32053// equals the given value.
32054Backbone.Syphon.InputWriters.register("radio", function($el, value){
32055  $el.prop("checked", $el.val() === value);
32056});
32057
32058// Key Assignment Validators
32059// -------------------------
32060
32061// Key Assignment Validators are used to determine whether or not a
32062// key should be assigned to a value, after the key and value have been
32063// extracted from the element. This is the last opportunity to prevent
32064// bad data from getting serialized to your object.
32065
32066Backbone.Syphon.KeyAssignmentValidatorSet = Backbone.Syphon.TypeRegistry.extend();
32067
32068// Build-in Key Assignment Validators
32069Backbone.Syphon.KeyAssignmentValidators = new Backbone.Syphon.KeyAssignmentValidatorSet();
32070
32071// Everything is valid by default
32072Backbone.Syphon.KeyAssignmentValidators.registerDefault(function(){ return true; });
32073
32074// But only the "checked" radio button for a given
32075// radio button group is valid
32076Backbone.Syphon.KeyAssignmentValidators.register("radio", function($el, key, value){
32077  return $el.prop("checked");
32078});
32079
32080
32081// Backbone.Syphon.KeySplitter
32082// ---------------------------
32083
32084// This function is used to split DOM element keys in to an array
32085// of parts, which are then used to create a nested result structure.
32086// returning `["foo", "bar"]` results in `{foo: { bar: "value" }}`.
32087//
32088// Override this method to use a custom key splitter, such as:
32089// `<input name="foo.bar.baz">`, `return key.split(".")`
32090Backbone.Syphon.KeySplitter = function(key){
32091  var matches = key.match(/[^\[\]]+/g);
32092
32093  if (key.indexOf("[]") === key.length - 2){
32094    lastKey = matches.pop();
32095    matches.push([lastKey]);
32096  }
32097
32098  return matches;
32099}
32100
32101
32102// Backbone.Syphon.KeyJoiner
32103// -------------------------
32104
32105// Take two segments of a key and join them together, to create the
32106// de-normalized key name, when deserializing a data structure back
32107// in to a form.
32108//
32109// Example: 
32110//
32111// With this data strucutre `{foo: { bar: {baz: "value", quux: "another"} } }`,
32112// the key joiner will be called with these parameters, and assuming the
32113// join happens with "[ ]" square brackets, the specified output:
32114// 
32115// `KeyJoiner("foo", "bar")` //=> "foo[bar]"
32116// `KeyJoiner("foo[bar]", "baz")` //=> "foo[bar][baz]"
32117// `KeyJoiner("foo[bar]", "quux")` //=> "foo[bar][quux]"
32118
vendor: 5,964 bytes, lines 32119-32335
32119Backbone.Syphon.KeyJoiner = function(parentKey, childKey){
32120  return parentKey + "[" + childKey + "]";
32121}
32122;
32123/**!
32124
32125 @license
32126 handlebars v4.7.7
32127
32128Copyright (C) 2011-2019 by Yehuda Katz
32129
32130Permission is hereby granted, free of charge, to any person obtaining a copy
32131of this software and associated documentation files (the "Software"), to deal
32132in the Software without restriction, including without limitation the rights
32133to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32134copies of the Software, and to permit persons to whom the Software is
32135furnished to do so, subject to the following conditions:
32136
32137The above copyright notice and this permission notice shall be included in
32138all copies or substantial portions of the Software.
32139
32140THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32141IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32142FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32143AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32144LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32145OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32146THE SOFTWARE.
32147
32148*/
32149
32150(function webpackUniversalModuleDefinition(root, factory) {
32151	if(typeof exports === 'object' && typeof module === 'object')
32152		module.exports = factory();
32153	else if(typeof define === 'function' && define.amd)
32154		define([], factory);
32155	else if(typeof exports === 'object')
32156		exports["Handlebars"] = factory();
32157	else
32158		root["Handlebars"] = factory();
32159})(this, function() {
32160return /******/ (function(modules) { // webpackBootstrap
32161/******/ 	// The module cache
32162/******/ 	var installedModules = {};
32163
32164/******/ 	// The require function
32165/******/ 	function __webpack_require__(moduleId) {
32166
32167/******/ 		// Check if module is in cache
32168/******/ 		if(installedModules[moduleId])
32169/******/ 			return installedModules[moduleId].exports;
32170
32171/******/ 		// Create a new module (and put it into the cache)
32172/******/ 		var module = installedModules[moduleId] = {
32173/******/ 			exports: {},
32174/******/ 			id: moduleId,
32175/******/ 			loaded: false
32176/******/ 		};
32177
32178/******/ 		// Execute the module function
32179/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
32180
32181/******/ 		// Flag the module as loaded
32182/******/ 		module.loaded = true;
32183
32184/******/ 		// Return the exports of the module
32185/******/ 		return module.exports;
32186/******/ 	}
32187
32188
32189/******/ 	// expose the modules object (__webpack_modules__)
32190/******/ 	__webpack_require__.m = modules;
32191
32192/******/ 	// expose the module cache
32193/******/ 	__webpack_require__.c = installedModules;
32194
32195/******/ 	// __webpack_public_path__
32196/******/ 	__webpack_require__.p = "";
32197
32198/******/ 	// Load entry module and return exports
32199/******/ 	return __webpack_require__(0);
32200/******/ })
32201/************************************************************************/
32202/******/ ([
32203/* 0 */
32204/***/ (function(module, exports, __webpack_require__) {
32205
32206	'use strict';
32207
32208	var _interopRequireWildcard = __webpack_require__(1)['default'];
32209
32210	var _interopRequireDefault = __webpack_require__(2)['default'];
32211
32212	exports.__esModule = true;
32213
32214	var _handlebarsBase = __webpack_require__(3);
32215
32216	var base = _interopRequireWildcard(_handlebarsBase);
32217
32218	// Each of these augment the Handlebars object. No need to setup here.
32219	// (This is done to easily share code between commonjs and browse envs)
32220
32221	var _handlebarsSafeString = __webpack_require__(36);
32222
32223	var _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString);
32224
32225	var _handlebarsException = __webpack_require__(5);
32226
32227	var _handlebarsException2 = _interopRequireDefault(_handlebarsException);
32228
32229	var _handlebarsUtils = __webpack_require__(4);
32230
32231	var Utils = _interopRequireWildcard(_handlebarsUtils);
32232
32233	var _handlebarsRuntime = __webpack_require__(37);
32234
32235	var runtime = _interopRequireWildcard(_handlebarsRuntime);
32236
32237	var _handlebarsNoConflict = __webpack_require__(43);
32238
32239	var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict);
32240
32241	// For compatibility and usage outside of module systems, make the Handlebars object a namespace
32242	function create() {
32243	  var hb = new base.HandlebarsEnvironment();
32244
32245	  Utils.extend(hb, base);
32246	  hb.SafeString = _handlebarsSafeString2['default'];
32247	  hb.Exception = _handlebarsException2['default'];
32248	  hb.Utils = Utils;
32249	  hb.escapeExpression = Utils.escapeExpression;
32250
32251	  hb.VM = runtime;
32252	  hb.template = function (spec) {
32253	    return runtime.template(spec, hb);
32254	  };
32255
32256	  return hb;
32257	}
32258
32259	var inst = create();
32260	inst.create = create;
32261
32262	_handlebarsNoConflict2['default'](inst);
32263
32264	inst['default'] = inst;
32265
32266	exports['default'] = inst;
32267	module.exports = exports['default'];
32268
32269/***/ }),
32270/* 1 */
32271/***/ (function(module, exports) {
32272
32273	"use strict";
32274
32275	exports["default"] = function (obj) {
32276	  if (obj && obj.__esModule) {
32277	    return obj;
32278	  } else {
32279	    var newObj = {};
32280
32281	    if (obj != null) {
32282	      for (var key in obj) {
32283	        if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
32284	      }
32285	    }
32286
32287	    newObj["default"] = obj;
32288	    return newObj;
32289	  }
32290	};
32291
32292	exports.__esModule = true;
32293
32294/***/ }),
32295/* 2 */
32296/***/ (function(module, exports) {
32297
32298	"use strict";
32299
32300	exports["default"] = function (obj) {
32301	  return obj && obj.__esModule ? obj : {
32302	    "default": obj
32303	  };
32304	};
32305
32306	exports.__esModule = true;
32307
32308/***/ }),
32309/* 3 */
32310/***/ (function(module, exports, __webpack_require__) {
32311
32312	'use strict';
32313
32314	var _interopRequireDefault = __webpack_require__(2)['default'];
32315
32316	exports.__esModule = true;
32317	exports.HandlebarsEnvironment = HandlebarsEnvironment;
32318
32319	var _utils = __webpack_require__(4);
32320
32321	var _exception = __webpack_require__(5);
32322
32323	var _exception2 = _interopRequireDefault(_exception);
32324
32325	var _helpers = __webpack_require__(9);
32326
32327	var _decorators = __webpack_require__(29);
32328
32329	var _logger = __webpack_require__(31);
32330
32331	var _logger2 = _interopRequireDefault(_logger);
32332
32333	var _internalProtoAccess = __webpack_require__(32);
32334
32335	var VERSION = '4.7.7
vendor: 9,203 bytes, lines 32335-32678
32335';
32336	exports.VERSION = VERSION;
32337	var COMPILER_REVISION = 8;
32338	exports.COMPILER_REVISION = COMPILER_REVISION;
32339	var LAST_COMPATIBLE_COMPILER_REVISION = 7;
32340
32341	exports.LAST_COMPATIBLE_COMPILER_REVISION = LAST_COMPATIBLE_COMPILER_REVISION;
32342	var REVISION_CHANGES = {
32343	  1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
32344	  2: '== 1.0.0-rc.3',
32345	  3: '== 1.0.0-rc.4',
32346	  4: '== 1.x.x',
32347	  5: '== 2.0.0-alpha.x',
32348	  6: '>= 2.0.0-beta.1',
32349	  7: '>= 4.0.0 <4.3.0',
32350	  8: '>= 4.3.0'
32351	};
32352
32353	exports.REVISION_CHANGES = REVISION_CHANGES;
32354	var objectType = '[object Object]';
32355
32356	function HandlebarsEnvironment(helpers, partials, decorators) {
32357	  this.helpers = helpers || {};
32358	  this.partials = partials || {};
32359	  this.decorators = decorators || {};
32360
32361	  _helpers.registerDefaultHelpers(this);
32362	  _decorators.registerDefaultDecorators(this);
32363	}
32364
32365	HandlebarsEnvironment.prototype = {
32366	  constructor: HandlebarsEnvironment,
32367
32368	  logger: _logger2['default'],
32369	  log: _logger2['default'].log,
32370
32371	  registerHelper: function registerHelper(name, fn) {
32372	    if (_utils.toString.call(name) === objectType) {
32373	      if (fn) {
32374	        throw new _exception2['default']('Arg not supported with multiple helpers');
32375	      }
32376	      _utils.extend(this.helpers, name);
32377	    } else {
32378	      this.helpers[name] = fn;
32379	    }
32380	  },
32381	  unregisterHelper: function unregisterHelper(name) {
32382	    delete this.helpers[name];
32383	  },
32384
32385	  registerPartial: function registerPartial(name, partial) {
32386	    if (_utils.toString.call(name) === objectType) {
32387	      _utils.extend(this.partials, name);
32388	    } else {
32389	      if (typeof partial === 'undefined') {
32390	        throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined');
32391	      }
32392	      this.partials[name] = partial;
32393	    }
32394	  },
32395	  unregisterPartial: function unregisterPartial(name) {
32396	    delete this.partials[name];
32397	  },
32398
32399	  registerDecorator: function registerDecorator(name, fn) {
32400	    if (_utils.toString.call(name) === objectType) {
32401	      if (fn) {
32402	        throw new _exception2['default']('Arg not supported with multiple decorators');
32403	      }
32404	      _utils.extend(this.decorators, name);
32405	    } else {
32406	      this.decorators[name] = fn;
32407	    }
32408	  },
32409	  unregisterDecorator: function unregisterDecorator(name) {
32410	    delete this.decorators[name];
32411	  },
32412	  /**
32413	   * Reset the memory of illegal property accesses that have already been logged.
32414	   * @deprecated should only be used in handlebars test-cases
32415	   */
32416	  resetLoggedPropertyAccesses: function resetLoggedPropertyAccesses() {
32417	    _internalProtoAccess.resetLoggedProperties();
32418	  }
32419	};
32420
32421	var log = _logger2['default'].log;
32422
32423	exports.log = log;
32424	exports.createFrame = _utils.createFrame;
32425	exports.logger = _logger2['default'];
32426
32427/***/ }),
32428/* 4 */
32429/***/ (function(module, exports) {
32430
32431	'use strict';
32432
32433	exports.__esModule = true;
32434	exports.extend = extend;
32435	exports.indexOf = indexOf;
32436	exports.escapeExpression = escapeExpression;
32437	exports.isEmpty = isEmpty;
32438	exports.createFrame = createFrame;
32439	exports.blockParams = blockParams;
32440	exports.appendContextPath = appendContextPath;
32441	var escape = {
32442	  '&': '&amp;',
32443	  '<': '&lt;',
32444	  '>': '&gt;',
32445	  '"': '&quot;',
32446	  "'": '&#x27;',
32447	  '`': '&#x60;',
32448	  '=': '&#x3D;'
32449	};
32450
32451	var badChars = /[&<>"'`=]/g,
32452	    possible = /[&<>"'`=]/;
32453
32454	function escapeChar(chr) {
32455	  return escape[chr];
32456	}
32457
32458	function extend(obj /* , ...source */) {
32459	  for (var i = 1; i < arguments.length; i++) {
32460	    for (var key in arguments[i]) {
32461	      if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
32462	        obj[key] = arguments[i][key];
32463	      }
32464	    }
32465	  }
32466
32467	  return obj;
32468	}
32469
32470	var toString = Object.prototype.toString;
32471
32472	exports.toString = toString;
32473	// Sourced from lodash
32474	// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
32475	/* eslint-disable func-style */
32476	var isFunction = function isFunction(value) {
32477	  return typeof value === 'function';
32478	};
32479	// fallback for older versions of Chrome and Safari
32480	/* istanbul ignore next */
32481	if (isFunction(/x/)) {
32482	  exports.isFunction = isFunction = function (value) {
32483	    return typeof value === 'function' && toString.call(value) === '[object Function]';
32484	  };
32485	}
32486	exports.isFunction = isFunction;
32487
32488	/* eslint-enable func-style */
32489
32490	/* istanbul ignore next */
32491	var isArray = Array.isArray || function (value) {
32492	  return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false;
32493	};
32494
32495	exports.isArray = isArray;
32496	// Older IE versions do not directly support indexOf so we must implement our own, sadly.
32497
32498	function indexOf(array, value) {
32499	  for (var i = 0, len = array.length; i < len; i++) {
32500	    if (array[i] === value) {
32501	      return i;
32502	    }
32503	  }
32504	  return -1;
32505	}
32506
32507	function escapeExpression(string) {
32508	  if (typeof string !== 'string') {
32509	    // don't escape SafeStrings, since they're already safe
32510	    if (string && string.toHTML) {
32511	      return string.toHTML();
32512	    } else if (string == null) {
32513	      return '';
32514	    } else if (!string) {
32515	      return string + '';
32516	    }
32517
32518	    // Force a string conversion as this will be done by the append regardless and
32519	    // the regex test will do this transparently behind the scenes, causing issues if
32520	    // an object's to string has escaped characters in it.
32521	    string = '' + string;
32522	  }
32523
32524	  if (!possible.test(string)) {
32525	    return string;
32526	  }
32527	  return string.replace(badChars, escapeChar);
32528	}
32529
32530	function isEmpty(value) {
32531	  if (!value && value !== 0) {
32532	    return true;
32533	  } else if (isArray(value) && value.length === 0) {
32534	    return true;
32535	  } else {
32536	    return false;
32537	  }
32538	}
32539
32540	function createFrame(object) {
32541	  var frame = extend({}, object);
32542	  frame._parent = object;
32543	  return frame;
32544	}
32545
32546	function blockParams(params, ids) {
32547	  params.path = ids;
32548	  return params;
32549	}
32550
32551	function appendContextPath(contextPath, id) {
32552	  return (contextPath ? contextPath + '.' : '') + id;
32553	}
32554
32555/***/ }),
32556/* 5 */
32557/***/ (function(module, exports, __webpack_require__) {
32558
32559	'use strict';
32560
32561	var _Object$defineProperty = __webpack_require__(6)['default'];
32562
32563	exports.__esModule = true;
32564	var errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack'];
32565
32566	function Exception(message, node) {
32567	  var loc = node && node.loc,
32568	      line = undefined,
32569	      endLineNumber = undefined,
32570	      column = undefined,
32571	      endColumn = undefined;
32572
32573	  if (loc) {
32574	    line = loc.start.line;
32575	    endLineNumber = loc.end.line;
32576	    column = loc.start.column;
32577	    endColumn = loc.end.column;
32578
32579	    message += ' - ' + line + ':' + column;
32580	  }
32581
32582	  var tmp = Error.prototype.constructor.call(this, message);
32583
32584	  // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
32585	  for (var idx = 0; idx < errorProps.length; idx++) {
32586	    this[errorProps[idx]] = tmp[errorProps[idx]];
32587	  }
32588
32589	  /* istanbul ignore else */
32590	  if (Error.captureStackTrace) {
32591	    Error.captureStackTrace(this, Exception);
32592	  }
32593
32594	  try {
32595	    if (loc) {
32596	      this.lineNumber = line;
32597	      this.endLineNumber = endLineNumber;
32598
32599	      // Work around issue under safari where we can't directly set the column value
32600	      /* istanbul ignore next */
32601	      if (_Object$defineProperty) {
32602	        Object.defineProperty(this, 'column', {
32603	          value: column,
32604	          enumerable: true
32605	        });
32606	        Object.defineProperty(this, 'endColumn', {
32607	          value: endColumn,
32608	          enumerable: true
32609	        });
32610	      } else {
32611	        this.column = column;
32612	        this.endColumn = endColumn;
32613	      }
32614	    }
32615	  } catch (nop) {
32616	    /* Ignore if the browser is very particular */
32617	  }
32618	}
32619
32620	Exception.prototype = new Error();
32621
32622	exports['default'] = Exception;
32623	module.exports = exports['default'];
32624
32625/***/ }),
32626/* 6 */
32627/***/ (function(module, exports, __webpack_require__) {
32628
32629	module.exports = { "default": __webpack_require__(7), __esModule: true };
32630
32631/***/ }),
32632/* 7 */
32633/***/ (function(module, exports, __webpack_require__) {
32634
32635	var $ = __webpack_require__(8);
32636	module.exports = function defineProperty(it, key, desc){
32637	  return $.setDesc(it, key, desc);
32638	};
32639
32640/***/ }),
32641/* 8 */
32642/***/ (function(module, exports) {
32643
32644	var $Object = Object;
32645	module.exports = {
32646	  create:     $Object.create,
32647	  getProto:   $Object.getPrototypeOf,
32648	  isEnum:     {}.propertyIsEnumerable,
32649	  getDesc:    $Object.getOwnPropertyDescriptor,
32650	  setDesc:    $Object.defineProperty,
32651	  setDescs:   $Object.defineProperties,
32652	  getKeys:    $Object.keys,
32653	  getNames:   $Object.getOwnPropertyNames,
32654	  getSymbols: $Object.getOwnPropertySymbols,
32655	  each:       [].forEach
32656	};
32657
32658/***/ }),
32659/* 9 */
32660/***/ (function(module, exports, __webpack_require__) {
32661
32662	'use strict';
32663
32664	var _interopRequireDefault = __webpack_require__(2)['default'];
32665
32666	exports.__esModule = true;
32667	exports.registerDefaultHelpers = registerDefaultHelpers;
32668	exports.moveHelperToHooks = moveHelperToHooks;
32669
32670	var _helpersBlockHelperMissing = __webpack_require__(10);
32671
32672	var _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing);
32673
32674	var _helpersEach = __webpack_require__(11);
32675
32676	var _helpersEach2 = _interopRequireDefault(_helpersEach);
32677
32678	var _helpersHelperMissing = __webpack_require__(
vendor: 29,993 bytes, lines 32678-33696
3267824);
32679
32680	var _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing);
32681
32682	var _helpersIf = __webpack_require__(25);
32683
32684	var _helpersIf2 = _interopRequireDefault(_helpersIf);
32685
32686	var _helpersLog = __webpack_require__(26);
32687
32688	var _helpersLog2 = _interopRequireDefault(_helpersLog);
32689
32690	var _helpersLookup = __webpack_require__(27);
32691
32692	var _helpersLookup2 = _interopRequireDefault(_helpersLookup);
32693
32694	var _helpersWith = __webpack_require__(28);
32695
32696	var _helpersWith2 = _interopRequireDefault(_helpersWith);
32697
32698	function registerDefaultHelpers(instance) {
32699	  _helpersBlockHelperMissing2['default'](instance);
32700	  _helpersEach2['default'](instance);
32701	  _helpersHelperMissing2['default'](instance);
32702	  _helpersIf2['default'](instance);
32703	  _helpersLog2['default'](instance);
32704	  _helpersLookup2['default'](instance);
32705	  _helpersWith2['default'](instance);
32706	}
32707
32708	function moveHelperToHooks(instance, helperName, keepHelper) {
32709	  if (instance.helpers[helperName]) {
32710	    instance.hooks[helperName] = instance.helpers[helperName];
32711	    if (!keepHelper) {
32712	      delete instance.helpers[helperName];
32713	    }
32714	  }
32715	}
32716
32717/***/ }),
32718/* 10 */
32719/***/ (function(module, exports, __webpack_require__) {
32720
32721	'use strict';
32722
32723	exports.__esModule = true;
32724
32725	var _utils = __webpack_require__(4);
32726
32727	exports['default'] = function (instance) {
32728	  instance.registerHelper('blockHelperMissing', function (context, options) {
32729	    var inverse = options.inverse,
32730	        fn = options.fn;
32731
32732	    if (context === true) {
32733	      return fn(this);
32734	    } else if (context === false || context == null) {
32735	      return inverse(this);
32736	    } else if (_utils.isArray(context)) {
32737	      if (context.length > 0) {
32738	        if (options.ids) {
32739	          options.ids = [options.name];
32740	        }
32741
32742	        return instance.helpers.each(context, options);
32743	      } else {
32744	        return inverse(this);
32745	      }
32746	    } else {
32747	      if (options.data && options.ids) {
32748	        var data = _utils.createFrame(options.data);
32749	        data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name);
32750	        options = { data: data };
32751	      }
32752
32753	      return fn(context, options);
32754	    }
32755	  });
32756	};
32757
32758	module.exports = exports['default'];
32759
32760/***/ }),
32761/* 11 */
32762/***/ (function(module, exports, __webpack_require__) {
32763
32764	/* WEBPACK VAR INJECTION */(function(global) {'use strict';
32765
32766	var _Object$keys = __webpack_require__(12)['default'];
32767
32768	var _interopRequireDefault = __webpack_require__(2)['default'];
32769
32770	exports.__esModule = true;
32771
32772	var _utils = __webpack_require__(4);
32773
32774	var _exception = __webpack_require__(5);
32775
32776	var _exception2 = _interopRequireDefault(_exception);
32777
32778	exports['default'] = function (instance) {
32779	  instance.registerHelper('each', function (context, options) {
32780	    if (!options) {
32781	      throw new _exception2['default']('Must pass iterator to #each');
32782	    }
32783
32784	    var fn = options.fn,
32785	        inverse = options.inverse,
32786	        i = 0,
32787	        ret = '',
32788	        data = undefined,
32789	        contextPath = undefined;
32790
32791	    if (options.data && options.ids) {
32792	      contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
32793	    }
32794
32795	    if (_utils.isFunction(context)) {
32796	      context = context.call(this);
32797	    }
32798
32799	    if (options.data) {
32800	      data = _utils.createFrame(options.data);
32801	    }
32802
32803	    function execIteration(field, index, last) {
32804	      if (data) {
32805	        data.key = field;
32806	        data.index = index;
32807	        data.first = index === 0;
32808	        data.last = !!last;
32809
32810	        if (contextPath) {
32811	          data.contextPath = contextPath + field;
32812	        }
32813	      }
32814
32815	      ret = ret + fn(context[field], {
32816	        data: data,
32817	        blockParams: _utils.blockParams([context[field], field], [contextPath + field, null])
32818	      });
32819	    }
32820
32821	    if (context && typeof context === 'object') {
32822	      if (_utils.isArray(context)) {
32823	        for (var j = context.length; i < j; i++) {
32824	          if (i in context) {
32825	            execIteration(i, i, i === context.length - 1);
32826	          }
32827	        }
32828	      } else if (global.Symbol && context[global.Symbol.iterator]) {
32829	        var newContext = [];
32830	        var iterator = context[global.Symbol.iterator]();
32831	        for (var it = iterator.next(); !it.done; it = iterator.next()) {
32832	          newContext.push(it.value);
32833	        }
32834	        context = newContext;
32835	        for (var j = context.length; i < j; i++) {
32836	          execIteration(i, i, i === context.length - 1);
32837	        }
32838	      } else {
32839	        (function () {
32840	          var priorKey = undefined;
32841
32842	          _Object$keys(context).forEach(function (key) {
32843	            // We're running the iterations one step out of sync so we can detect
32844	            // the last iteration without have to scan the object twice and create
32845	            // an itermediate keys array.
32846	            if (priorKey !== undefined) {
32847	              execIteration(priorKey, i - 1);
32848	            }
32849	            priorKey = key;
32850	            i++;
32851	          });
32852	          if (priorKey !== undefined) {
32853	            execIteration(priorKey, i - 1, true);
32854	          }
32855	        })();
32856	      }
32857	    }
32858
32859	    if (i === 0) {
32860	      ret = inverse(this);
32861	    }
32862
32863	    return ret;
32864	  });
32865	};
32866
32867	module.exports = exports['default'];
32868	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
32869
32870/***/ }),
32871/* 12 */
32872/***/ (function(module, exports, __webpack_require__) {
32873
32874	module.exports = { "default": __webpack_require__(13), __esModule: true };
32875
32876/***/ }),
32877/* 13 */
32878/***/ (function(module, exports, __webpack_require__) {
32879
32880	__webpack_require__(14);
32881	module.exports = __webpack_require__(20).Object.keys;
32882
32883/***/ }),
32884/* 14 */
32885/***/ (function(module, exports, __webpack_require__) {
32886
32887	// 19.1.2.14 Object.keys(O)
32888	var toObject = __webpack_require__(15);
32889
32890	__webpack_require__(17)('keys', function($keys){
32891	  return function keys(it){
32892	    return $keys(toObject(it));
32893	  };
32894	});
32895
32896/***/ }),
32897/* 15 */
32898/***/ (function(module, exports, __webpack_require__) {
32899
32900	// 7.1.13 ToObject(argument)
32901	var defined = __webpack_require__(16);
32902	module.exports = function(it){
32903	  return Object(defined(it));
32904	};
32905
32906/***/ }),
32907/* 16 */
32908/***/ (function(module, exports) {
32909
32910	// 7.2.1 RequireObjectCoercible(argument)
32911	module.exports = function(it){
32912	  if(it == undefined)throw TypeError("Can't call method on  " + it);
32913	  return it;
32914	};
32915
32916/***/ }),
32917/* 17 */
32918/***/ (function(module, exports, __webpack_require__) {
32919
32920	// most Object methods by ES6 should accept primitives
32921	var $export = __webpack_require__(18)
32922	  , core    = __webpack_require__(20)
32923	  , fails   = __webpack_require__(23);
32924	module.exports = function(KEY, exec){
32925	  var fn  = (core.Object || {})[KEY] || Object[KEY]
32926	    , exp = {};
32927	  exp[KEY] = exec(fn);
32928	  $export($export.S + $export.F * fails(function(){ fn(1); }), 'Object', exp);
32929	};
32930
32931/***/ }),
32932/* 18 */
32933/***/ (function(module, exports, __webpack_require__) {
32934
32935	var global    = __webpack_require__(19)
32936	  , core      = __webpack_require__(20)
32937	  , ctx       = __webpack_require__(21)
32938	  , PROTOTYPE = 'prototype';
32939
32940	var $export = function(type, name, source){
32941	  var IS_FORCED = type & $export.F
32942	    , IS_GLOBAL = type & $export.G
32943	    , IS_STATIC = type & $export.S
32944	    , IS_PROTO  = type & $export.P
32945	    , IS_BIND   = type & $export.B
32946	    , IS_WRAP   = type & $export.W
32947	    , exports   = IS_GLOBAL ? core : core[name] || (core[name] = {})
32948	    , target    = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE]
32949	    , key, own, out;
32950	  if(IS_GLOBAL)source = name;
32951	  for(key in source){
32952	    // contains in native
32953	    own = !IS_FORCED && target && key in target;
32954	    if(own && key in exports)continue;
32955	    // export native or passed
32956	    out = own ? target[key] : source[key];
32957	    // prevent global pollution for namespaces
32958	    exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key]
32959	    // bind timers to global for call from export context
32960	    : IS_BIND && own ? ctx(out, global)
32961	    // wrap global constructors for prevent change them in library
32962	    : IS_WRAP && target[key] == out ? (function(C){
32963	      var F = function(param){
32964	        return this instanceof C ? new C(param) : C(param);
32965	      };
32966	      F[PROTOTYPE] = C[PROTOTYPE];
32967	      return F;
32968	    // make static versions for prototype methods
32969	    })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out;
32970	    if(IS_PROTO)(exports[PROTOTYPE] || (exports[PROTOTYPE] = {}))[key] = out;
32971	  }
32972	};
32973	// type bitmap
32974	$export.F = 1;  // forced
32975	$export.G = 2;  // global
32976	$export.S = 4;  // static
32977	$export.P = 8;  // proto
32978	$export.B = 16; // bind
32979	$export.W = 32; // wrap
32980	module.exports = $export;
32981
32982/***/ }),
32983/* 19 */
32984/***/ (function(module, exports) {
32985
32986	// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
32987	var global = module.exports = typeof window != 'undefined' && window.Math == Math
32988	  ? window : typeof self != 'undefined' && self.Math == Math ? self : Function('return this')();
32989	if(typeof __g == 'number')__g = global; // eslint-disable-line no-undef
32990
32991/***/ }),
32992/* 20 */
32993/***/ (function(module, exports) {
32994
32995	var core = module.exports = {version: '1.2.6'};
32996	if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef
32997
32998/***/ }),
32999/* 21 */
33000/***/ (function(module, exports, __webpack_require__) {
33001
33002	// optional / simple context binding
33003	var aFunction = __webpack_require__(22);
33004	module.exports = function(fn, that, length){
33005	  aFunction(fn);
33006	  if(that === undefined)return fn;
33007	  switch(length){
33008	    case 1: return function(a){
33009	      return fn.call(that, a);
33010	    };
33011	    case 2: return function(a, b){
33012	      return fn.call(that, a, b);
33013	    };
33014	    case 3: return function(a, b, c){
33015	      return fn.call(that, a, b, c);
33016	    };
33017	  }
33018	  return function(/* ...args */){
33019	    return fn.apply(that, arguments);
33020	  };
33021	};
33022
33023/***/ }),
33024/* 22 */
33025/***/ (function(module, exports) {
33026
33027	module.exports = function(it){
33028	  if(typeof it != 'function')throw TypeError(it + ' is not a function!');
33029	  return it;
33030	};
33031
33032/***/ }),
33033/* 23 */
33034/***/ (function(module, exports) {
33035
33036	module.exports = function(exec){
33037	  try {
33038	    return !!exec();
33039	  } catch(e){
33040	    return true;
33041	  }
33042	};
33043
33044/***/ }),
33045/* 24 */
33046/***/ (function(module, exports, __webpack_require__) {
33047
33048	'use strict';
33049
33050	var _interopRequireDefault = __webpack_require__(2)['default'];
33051
33052	exports.__esModule = true;
33053
33054	var _exception = __webpack_require__(5);
33055
33056	var _exception2 = _interopRequireDefault(_exception);
33057
33058	exports['default'] = function (instance) {
33059	  instance.registerHelper('helperMissing', function () /* [args, ]options */{
33060	    if (arguments.length === 1) {
33061	      // A missing field in a {{foo}} construct.
33062	      return undefined;
33063	    } else {
33064	      // Someone is actually trying to call something, blow up.
33065	      throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"');
33066	    }
33067	  });
33068	};
33069
33070	module.exports = exports['default'];
33071
33072/***/ }),
33073/* 25 */
33074/***/ (function(module, exports, __webpack_require__) {
33075
33076	'use strict';
33077
33078	var _interopRequireDefault = __webpack_require__(2)['default'];
33079
33080	exports.__esModule = true;
33081
33082	var _utils = __webpack_require__(4);
33083
33084	var _exception = __webpack_require__(5);
33085
33086	var _exception2 = _interopRequireDefault(_exception);
33087
33088	exports['default'] = function (instance) {
33089	  instance.registerHelper('if', function (conditional, options) {
33090	    if (arguments.length != 2) {
33091	      throw new _exception2['default']('#if requires exactly one argument');
33092	    }
33093	    if (_utils.isFunction(conditional)) {
33094	      conditional = conditional.call(this);
33095	    }
33096
33097	    // Default behavior is to render the positive path if the value is truthy and not empty.
33098	    // The `includeZero` option may be set to treat the condtional as purely not empty based on the
33099	    // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
33100	    if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) {
33101	      return options.inverse(this);
33102	    } else {
33103	      return options.fn(this);
33104	    }
33105	  });
33106
33107	  instance.registerHelper('unless', function (conditional, options) {
33108	    if (arguments.length != 2) {
33109	      throw new _exception2['default']('#unless requires exactly one argument');
33110	    }
33111	    return instance.helpers['if'].call(this, conditional, {
33112	      fn: options.inverse,
33113	      inverse: options.fn,
33114	      hash: options.hash
33115	    });
33116	  });
33117	};
33118
33119	module.exports = exports['default'];
33120
33121/***/ }),
33122/* 26 */
33123/***/ (function(module, exports) {
33124
33125	'use strict';
33126
33127	exports.__esModule = true;
33128
33129	exports['default'] = function (instance) {
33130	  instance.registerHelper('log', function () /* message, options */{
33131	    var args = [undefined],
33132	        options = arguments[arguments.length - 1];
33133	    for (var i = 0; i < arguments.length - 1; i++) {
33134	      args.push(arguments[i]);
33135	    }
33136
33137	    var level = 1;
33138	    if (options.hash.level != null) {
33139	      level = options.hash.level;
33140	    } else if (options.data && options.data.level != null) {
33141	      level = options.data.level;
33142	    }
33143	    args[0] = level;
33144
33145	    instance.log.apply(instance, args);
33146	  });
33147	};
33148
33149	module.exports = exports['default'];
33150
33151/***/ }),
33152/* 27 */
33153/***/ (function(module, exports) {
33154
33155	'use strict';
33156
33157	exports.__esModule = true;
33158
33159	exports['default'] = function (instance) {
33160	  instance.registerHelper('lookup', function (obj, field, options) {
33161	    if (!obj) {
33162	      // Note for 5.0: Change to "obj == null" in 5.0
33163	      return obj;
33164	    }
33165	    return options.lookupProperty(obj, field);
33166	  });
33167	};
33168
33169	module.exports = exports['default'];
33170
33171/***/ }),
33172/* 28 */
33173/***/ (function(module, exports, __webpack_require__) {
33174
33175	'use strict';
33176
33177	var _interopRequireDefault = __webpack_require__(2)['default'];
33178
33179	exports.__esModule = true;
33180
33181	var _utils = __webpack_require__(4);
33182
33183	var _exception = __webpack_require__(5);
33184
33185	var _exception2 = _interopRequireDefault(_exception);
33186
33187	exports['default'] = function (instance) {
33188	  instance.registerHelper('with', function (context, options) {
33189	    if (arguments.length != 2) {
33190	      throw new _exception2['default']('#with requires exactly one argument');
33191	    }
33192	    if (_utils.isFunction(context)) {
33193	      context = context.call(this);
33194	    }
33195
33196	    var fn = options.fn;
33197
33198	    if (!_utils.isEmpty(context)) {
33199	      var data = options.data;
33200	      if (options.data && options.ids) {
33201	        data = _utils.createFrame(options.data);
33202	        data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]);
33203	      }
33204
33205	      return fn(context, {
33206	        data: data,
33207	        blockParams: _utils.blockParams([context], [data && data.contextPath])
33208	      });
33209	    } else {
33210	      return options.inverse(this);
33211	    }
33212	  });
33213	};
33214
33215	module.exports = exports['default'];
33216
33217/***/ }),
33218/* 29 */
33219/***/ (function(module, exports, __webpack_require__) {
33220
33221	'use strict';
33222
33223	var _interopRequireDefault = __webpack_require__(2)['default'];
33224
33225	exports.__esModule = true;
33226	exports.registerDefaultDecorators = registerDefaultDecorators;
33227
33228	var _decoratorsInline = __webpack_require__(30);
33229
33230	var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline);
33231
33232	function registerDefaultDecorators(instance) {
33233	  _decoratorsInline2['default'](instance);
33234	}
33235
33236/***/ }),
33237/* 30 */
33238/***/ (function(module, exports, __webpack_require__) {
33239
33240	'use strict';
33241
33242	exports.__esModule = true;
33243
33244	var _utils = __webpack_require__(4);
33245
33246	exports['default'] = function (instance) {
33247	  instance.registerDecorator('inline', function (fn, props, container, options) {
33248	    var ret = fn;
33249	    if (!props.partials) {
33250	      props.partials = {};
33251	      ret = function (context, options) {
33252	        // Create a new partials stack frame prior to exec.
33253	        var original = container.partials;
33254	        container.partials = _utils.extend({}, original, props.partials);
33255	        var ret = fn(context, options);
33256	        container.partials = original;
33257	        return ret;
33258	      };
33259	    }
33260
33261	    props.partials[options.args[0]] = options.fn;
33262
33263	    return ret;
33264	  });
33265	};
33266
33267	module.exports = exports['default'];
33268
33269/***/ }),
33270/* 31 */
33271/***/ (function(module, exports, __webpack_require__) {
33272
33273	'use strict';
33274
33275	exports.__esModule = true;
33276
33277	var _utils = __webpack_require__(4);
33278
33279	var logger = {
33280	  methodMap: ['debug', 'info', 'warn', 'error'],
33281	  level: 'info',
33282
33283	  // Maps a given level value to the `methodMap` indexes above.
33284	  lookupLevel: function lookupLevel(level) {
33285	    if (typeof level === 'string') {
33286	      var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase());
33287	      if (levelMap >= 0) {
33288	        level = levelMap;
33289	      } else {
33290	        level = parseInt(level, 10);
33291	      }
33292	    }
33293
33294	    return level;
33295	  },
33296
33297	  // Can be overridden in the host environment
33298	  log: function log(level) {
33299	    level = logger.lookupLevel(level);
33300
33301	    if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
33302	      var method = logger.methodMap[level];
33303	      // eslint-disable-next-line no-console
33304	      if (!console[method]) {
33305	        method = 'log';
33306	      }
33307
33308	      for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
33309	        message[_key - 1] = arguments[_key];
33310	      }
33311
33312	      console[method].apply(console, message); // eslint-disable-line no-console
33313	    }
33314	  }
33315	};
33316
33317	exports['default'] = logger;
33318	module.exports = exports['default'];
33319
33320/***/ }),
33321/* 32 */
33322/***/ (function(module, exports, __webpack_require__) {
33323
33324	'use strict';
33325
33326	var _Object$create = __webpack_require__(33)['default'];
33327
33328	var _Object$keys = __webpack_require__(12)['default'];
33329
33330	var _interopRequireWildcard = __webpack_require__(1)['default'];
33331
33332	exports.__esModule = true;
33333	exports.createProtoAccessControl = createProtoAccessControl;
33334	exports.resultIsAllowed = resultIsAllowed;
33335	exports.resetLoggedProperties = resetLoggedProperties;
33336
33337	var _createNewLookupObject = __webpack_require__(35);
33338
33339	var _logger = __webpack_require__(31);
33340
33341	var logger = _interopRequireWildcard(_logger);
33342
33343	var loggedProperties = _Object$create(null);
33344
33345	function createProtoAccessControl(runtimeOptions) {
33346	  var defaultMethodWhiteList = _Object$create(null);
33347	  defaultMethodWhiteList['constructor'] = false;
33348	  defaultMethodWhiteList['__defineGetter__'] = false;
33349	  defaultMethodWhiteList['__defineSetter__'] = false;
33350	  defaultMethodWhiteList['__lookupGetter__'] = false;
33351
33352	  var defaultPropertyWhiteList = _Object$create(null);
33353	  // eslint-disable-next-line no-proto
33354	  defaultPropertyWhiteList['__proto__'] = false;
33355
33356	  return {
33357	    properties: {
33358	      whitelist: _createNewLookupObject.createNewLookupObject(defaultPropertyWhiteList, runtimeOptions.allowedProtoProperties),
33359	      defaultValue: runtimeOptions.allowProtoPropertiesByDefault
33360	    },
33361	    methods: {
33362	      whitelist: _createNewLookupObject.createNewLookupObject(defaultMethodWhiteList, runtimeOptions.allowedProtoMethods),
33363	      defaultValue: runtimeOptions.allowProtoMethodsByDefault
33364	    }
33365	  };
33366	}
33367
33368	function resultIsAllowed(result, protoAccessControl, propertyName) {
33369	  if (typeof result === 'function') {
33370	    return checkWhiteList(protoAccessControl.methods, propertyName);
33371	  } else {
33372	    return checkWhiteList(protoAccessControl.properties, propertyName);
33373	  }
33374	}
33375
33376	function checkWhiteList(protoAccessControlForType, propertyName) {
33377	  if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
33378	    return protoAccessControlForType.whitelist[propertyName] === true;
33379	  }
33380	  if (protoAccessControlForType.defaultValue !== undefined) {
33381	    return protoAccessControlForType.defaultValue;
33382	  }
33383	  logUnexpecedPropertyAccessOnce(propertyName);
33384	  return false;
33385	}
33386
33387	function logUnexpecedPropertyAccessOnce(propertyName) {
33388	  if (loggedProperties[propertyName] !== true) {
33389	    loggedProperties[propertyName] = true;
33390	    logger.log('error', 'Handlebars: Access has been denied to resolve the property "' + propertyName + '" because it is not an "own property" of its parent.\n' + 'You can add a runtime option to disable the check or this warning:\n' + 'See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details');
33391	  }
33392	}
33393
33394	function resetLoggedProperties() {
33395	  _Object$keys(loggedProperties).forEach(function (propertyName) {
33396	    delete loggedProperties[propertyName];
33397	  });
33398	}
33399
33400/***/ }),
33401/* 33 */
33402/***/ (function(module, exports, __webpack_require__) {
33403
33404	module.exports = { "default": __webpack_require__(34), __esModule: true };
33405
33406/***/ }),
33407/* 34 */
33408/***/ (function(module, exports, __webpack_require__) {
33409
33410	var $ = __webpack_require__(8);
33411	module.exports = function create(P, D){
33412	  return $.create(P, D);
33413	};
33414
33415/***/ }),
33416/* 35 */
33417/***/ (function(module, exports, __webpack_require__) {
33418
33419	'use strict';
33420
33421	var _Object$create = __webpack_require__(33)['default'];
33422
33423	exports.__esModule = true;
33424	exports.createNewLookupObject = createNewLookupObject;
33425
33426	var _utils = __webpack_require__(4);
33427
33428	/**
33429	 * Create a new object with "null"-prototype to avoid truthy results on prototype properties.
33430	 * The resulting object can be used with "object[property]" to check if a property exists
33431	 * @param {...object} sources a varargs parameter of source objects that will be merged
33432	 * @returns {object}
33433	 */
33434
33435	function createNewLookupObject() {
33436	  for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) {
33437	    sources[_key] = arguments[_key];
33438	  }
33439
33440	  return _utils.extend.apply(undefined, [_Object$create(null)].concat(sources));
33441	}
33442
33443/***/ }),
33444/* 36 */
33445/***/ (function(module, exports) {
33446
33447	// Build out our basic SafeString type
33448	'use strict';
33449
33450	exports.__esModule = true;
33451	function SafeString(string) {
33452	  this.string = string;
33453	}
33454
33455	SafeString.prototype.toString = SafeString.prototype.toHTML = function () {
33456	  return '' + this.string;
33457	};
33458
33459	exports['default'] = SafeString;
33460	module.exports = exports['default'];
33461
33462/***/ }),
33463/* 37 */
33464/***/ (function(module, exports, __webpack_require__) {
33465
33466	'use strict';
33467
33468	var _Object$seal = __webpack_require__(38)['default'];
33469
33470	var _Object$keys = __webpack_require__(12)['default'];
33471
33472	var _interopRequireWildcard = __webpack_require__(1)['default'];
33473
33474	var _interopRequireDefault = __webpack_require__(2)['default'];
33475
33476	exports.__esModule = true;
33477	exports.checkRevision = checkRevision;
33478	exports.template = template;
33479	exports.wrapProgram = wrapProgram;
33480	exports.resolvePartial = resolvePartial;
33481	exports.invokePartial = invokePartial;
33482	exports.noop = noop;
33483
33484	var _utils = __webpack_require__(4);
33485
33486	var Utils = _interopRequireWildcard(_utils);
33487
33488	var _exception = __webpack_require__(5);
33489
33490	var _exception2 = _interopRequireDefault(_exception);
33491
33492	var _base = __webpack_require__(3);
33493
33494	var _helpers = __webpack_require__(9);
33495
33496	var _internalWrapHelper = __webpack_require__(42);
33497
33498	var _internalProtoAccess = __webpack_require__(32);
33499
33500	function checkRevision(compilerInfo) {
33501	  var compilerRevision = compilerInfo && compilerInfo[0] || 1,
33502	      currentRevision = _base.COMPILER_REVISION;
33503
33504	  if (compilerRevision >= _base.LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= _base.COMPILER_REVISION) {
33505	    return;
33506	  }
33507
33508	  if (compilerRevision < _base.LAST_COMPATIBLE_COMPILER_REVISION) {
33509	    var runtimeVersions = _base.REVISION_CHANGES[currentRevision],
33510	        compilerVersions = _base.REVISION_CHANGES[compilerRevision];
33511	    throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
33512	  } else {
33513	    // Use the embedded version info since the runtime doesn't know about this revision yet
33514	    throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
33515	  }
33516	}
33517
33518	function template(templateSpec, env) {
33519	  /* istanbul ignore next */
33520	  if (!env) {
33521	    throw new _exception2['default']('No environment passed to template');
33522	  }
33523	  if (!templateSpec || !templateSpec.main) {
33524	    throw new _exception2['default']('Unknown template object: ' + typeof templateSpec);
33525	  }
33526
33527	  templateSpec.main.decorator = templateSpec.main_d;
33528
33529	  // Note: Using env.VM references rather than local var references throughout this section to allow
33530	  // for external users to override these as pseudo-supported APIs.
33531	  env.VM.checkRevision(templateSpec.compiler);
33532
33533	  // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
33534	  var templateWasPrecompiledWithCompilerV7 = templateSpec.compiler && templateSpec.compiler[0] === 7;
33535
33536	  function invokePartialWrapper(partial, context, options) {
33537	    if (options.hash) {
33538	      context = Utils.extend({}, context, options.hash);
33539	      if (options.ids) {
33540	        options.ids[0] = true;
33541	      }
33542	    }
33543	    partial = env.VM.resolvePartial.call(this, partial, context, options);
33544
33545	    var extendedOptions = Utils.extend({}, options, {
33546	      hooks: this.hooks,
33547	      protoAccessControl: this.protoAccessControl
33548	    });
33549
33550	    var result = env.VM.invokePartial.call(this, partial, context, extendedOptions);
33551
33552	    if (result == null && env.compile) {
33553	      options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
33554	      result = options.partials[options.name](context, extendedOptions);
33555	    }
33556	    if (result != null) {
33557	      if (options.indent) {
33558	        var lines = result.split('\n');
33559	        for (var i = 0, l = lines.length; i < l; i++) {
33560	          if (!lines[i] && i + 1 === l) {
33561	            break;
33562	          }
33563
33564	          lines[i] = options.indent + lines[i];
33565	        }
33566	        result = lines.join('\n');
33567	      }
33568	      return result;
33569	    } else {
33570	      throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
33571	    }
33572	  }
33573
33574	  // Just add water
33575	  var container = {
33576	    strict: function strict(obj, name, loc) {
33577	      if (!obj || !(name in obj)) {
33578	        throw new _exception2['default']('"' + name + '" not defined in ' + obj, {
33579	          loc: loc
33580	        });
33581	      }
33582	      return container.lookupProperty(obj, name);
33583	    },
33584	    lookupProperty: function lookupProperty(parent, propertyName) {
33585	      var result = parent[propertyName];
33586	      if (result == null) {
33587	        return result;
33588	      }
33589	      if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
33590	        return result;
33591	      }
33592
33593	      if (_internalProtoAccess.resultIsAllowed(result, container.protoAccessControl, propertyName)) {
33594	        return result;
33595	      }
33596	      return undefined;
33597	    },
33598	    lookup: function lookup(depths, name) {
33599	      var len = depths.length;
33600	      for (var i = 0; i < len; i++) {
33601	        var result = depths[i] && container.lookupProperty(depths[i], name);
33602	        if (result != null) {
33603	          return depths[i][name];
33604	        }
33605	      }
33606	    },
33607	    lambda: function lambda(current, context) {
33608	      return typeof current === 'function' ? current.call(context) : current;
33609	    },
33610
33611	    escapeExpression: Utils.escapeExpression,
33612	    invokePartial: invokePartialWrapper,
33613
33614	    fn: function fn(i) {
33615	      var ret = templateSpec[i];
33616	      ret.decorator = templateSpec[i + '_d'];
33617	      return ret;
33618	    },
33619
33620	    programs: [],
33621	    program: function program(i, data, declaredBlockParams, blockParams, depths) {
33622	      var programWrapper = this.programs[i],
33623	          fn = this.fn(i);
33624	      if (data || depths || blockParams || declaredBlockParams) {
33625	        programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
33626	      } else if (!programWrapper) {
33627	        programWrapper = this.programs[i] = wrapProgram(this, i, fn);
33628	      }
33629	      return programWrapper;
33630	    },
33631
33632	    data: function data(value, depth) {
33633	      while (value && depth--) {
33634	        value = value._parent;
33635	      }
33636	      return value;
33637	    },
33638	    mergeIfNeeded: function mergeIfNeeded(param, common) {
33639	      var obj = param || common;
33640
33641	      if (param && common && param !== common) {
33642	        obj = Utils.extend({}, common, param);
33643	      }
33644
33645	      return obj;
33646	    },
33647	    // An empty object to use as replacement for null-contexts
33648	    nullContext: _Object$seal({}),
33649
33650	    noop: env.VM.noop,
33651	    compilerInfo: templateSpec.compiler
33652	  };
33653
33654	  function ret(context) {
33655	    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
33656
33657	    var data = options.data;
33658
33659	    ret._setup(options);
33660	    if (!options.partial && templateSpec.useData) {
33661	      data = initData(context, data);
33662	    }
33663	    var depths = undefined,
33664	        blockParams = templateSpec.useBlockParams ? [] : undefined;
33665	    if (templateSpec.useDepths) {
33666	      if (options.depths) {
33667	        depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
33668	      } else {
33669	        depths = [context];
33670	      }
33671	    }
33672
33673	    function main(context /*, options*/) {
33674	      return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
33675	    }
33676
33677	    main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
33678	    return main(context, options);
33679	  }
33680
33681	  ret.isTop = true;
33682
33683	  ret._setup = function (options) {
33684	    if (!options.partial) {
33685	      var mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
33686	      wrapHelpersToPassLookupProperty(mergedHelpers, container);
33687	      container.helpers = mergedHelpers;
33688
33689	      if (templateSpec.usePartial) {
33690	        // Use mergeIfNeeded here to prevent compiling global partials multiple times
33691	        container.partials = container.mergeIfNeeded(options.partials, env.partials);
33692	      }
33693	      if (templateSpec.usePartial || templateSpec.useDecorators) {
33694	        container.decorators = Utils.extend({}, env.decorators, options.decorators);
33695	      }
33696
vendor: 4,565 bytes, lines 33697-33825
33697	      container.hooks = {};
33698	      container.protoAccessControl = _internalProtoAccess.createProtoAccessControl(options);
33699
33700	      var keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7;
33701	      _helpers.moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
33702	      _helpers.moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
33703	    } else {
33704	      container.protoAccessControl = options.protoAccessControl; // internal option
33705	      container.helpers = options.helpers;
33706	      container.partials = options.partials;
33707	      container.decorators = options.decorators;
33708	      container.hooks = options.hooks;
33709	    }
33710	  };
33711
33712	  ret._child = function (i, data, blockParams, depths) {
33713	    if (templateSpec.useBlockParams && !blockParams) {
33714	      throw new _exception2['default']('must pass block params');
33715	    }
33716	    if (templateSpec.useDepths && !depths) {
33717	      throw new _exception2['default']('must pass parent depths');
33718	    }
33719
33720	    return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);
33721	  };
33722	  return ret;
33723	}
33724
33725	function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
33726	  function prog(context) {
33727	    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
33728
33729	    var currentDepths = depths;
33730	    if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) {
33731	      currentDepths = [context].concat(depths);
33732	    }
33733
33734	    return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths);
33735	  }
33736
33737	  prog = executeDecorators(fn, prog, container, depths, data, blockParams);
33738
33739	  prog.program = i;
33740	  prog.depth = depths ? depths.length : 0;
33741	  prog.blockParams = declaredBlockParams || 0;
33742	  return prog;
33743	}
33744
33745	/**
33746	 * This is currently part of the official API, therefore implementation details should not be changed.
33747	 */
33748
33749	function resolvePartial(partial, context, options) {
33750	  if (!partial) {
33751	    if (options.name === '@partial-block') {
33752	      partial = options.data['partial-block'];
33753	    } else {
33754	      partial = options.partials[options.name];
33755	    }
33756	  } else if (!partial.call && !options.name) {
33757	    // This is a dynamic partial that returned a string
33758	    options.name = partial;
33759	    partial = options.partials[partial];
33760	  }
33761	  return partial;
33762	}
33763
33764	function invokePartial(partial, context, options) {
33765	  // Use the current closure context to save the partial-block if this partial
33766	  var currentPartialBlock = options.data && options.data['partial-block'];
33767	  options.partial = true;
33768	  if (options.ids) {
33769	    options.data.contextPath = options.ids[0] || options.data.contextPath;
33770	  }
33771
33772	  var partialBlock = undefined;
33773	  if (options.fn && options.fn !== noop) {
33774	    (function () {
33775	      options.data = _base.createFrame(options.data);
33776	      // Wrapper function to get access to currentPartialBlock from the closure
33777	      var fn = options.fn;
33778	      partialBlock = options.data['partial-block'] = function partialBlockWrapper(context) {
33779	        var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
33780
33781	        // Restore the partial-block from the closure for the execution of the block
33782	        // i.e. the part inside the block of the partial call.
33783	        options.data = _base.createFrame(options.data);
33784	        options.data['partial-block'] = currentPartialBlock;
33785	        return fn(context, options);
33786	      };
33787	      if (fn.partials) {
33788	        options.partials = Utils.extend({}, options.partials, fn.partials);
33789	      }
33790	    })();
33791	  }
33792
33793	  if (partial === undefined && partialBlock) {
33794	    partial = partialBlock;
33795	  }
33796
33797	  if (partial === undefined) {
33798	    throw new _exception2['default']('The partial ' + options.name + ' could not be found');
33799	  } else if (partial instanceof Function) {
33800	    return partial(context, options);
33801	  }
33802	}
33803
33804	function noop() {
33805	  return '';
33806	}
33807
33808	function initData(context, data) {
33809	  if (!data || !('root' in data)) {
33810	    data = data ? _base.createFrame(data) : {};
33811	    data.root = context;
33812	  }
33813	  return data;
33814	}
33815
33816	function executeDecorators(fn, prog, container, depths, data, blockParams) {
33817	  if (fn.decorator) {
33818	    var props = {};
33819	    prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
33820	    Utils.extend(prog, props);
33821	  }
33822	  return prog;
33823	}
33824
33825	function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
33826	  _Object$keys(mergedHelpers).forEach(function (helperName) {
33827	    var helper = mergedHelpers[helperName];
33828	    mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
33829	  });
33830	}
33831
33832	function passLookupPropertyOption(helper, container) {
33833	  var lookupProperty = container.lookupProperty;
33834	  return _internalWrapHelper.wrapHelper(helper, function (options) {
33835	    return Utils.extend({ lookupProperty: lookupProperty }, options);
33836	  });
33837	}
33838
33839/***/ }),
33840/* 38 */
33841/***/ (function(module, exports, __webpack_require__) {
33842
33843	module.exports = { "default": __webpack_require__(39), __esModule: true };
33844
33845/***/ }),
33846/* 39 */
33847/***/ (function(module, exports, __webpack_require__) {
33848
33849	__webpack_require__(40);
33850	module.exports = __webpack_require__(20).Object.seal;
33851
33852/***/ }),
33853/* 40 */
33854/***/ (function(module, exports, __webpack_require__) {
33855
33856	// 19.1.2.17 Object.seal(O)
33857	var isObject = __webpack_require__(41);
33858
33859	__webpack_require__(17)('seal', function($seal){
33860	  return function seal(it){
33861	    return $seal && isObject(it) ? $seal(it) : it;
33862	  };
33863	});
33864
33865/***/ }),
33866/* 41 */
33867/***/ (function(module, exports) {
33868
33869	module.exports = function(it){
33870	  return typeof it === 'object' ? it !== null : typeof it === 'function';
33871	};
33872
33873/***/ }),
33874/* 42 */
33875/***/ (function(module, exports) {
33876
33877	'use strict';
33878
33879	exports.__esModule = true;
33880	exports.wrapHelper = wrapHelper;
33881
33882	function wrapHelper(helper, transformOptionsFn) {
33883	  if (typeof helper !== 'function') {
33884	    // This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639
33885	    // We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
33886	    return helper;
33887	  }
33888	  var wrapper = function wrapper() /* dynamic arguments */{
33889	    var options = arguments[arguments.length - 1];
33890	    arguments[arguments.length - 1] = transformOptionsFn(options);
33891	    return helper.apply(this, arguments);
33892	  };
33893	  return wrapper;
33894	}
33895
33896/***/ }),
33897/* 43 */
33898/***/ (function(module, exports) {
33899
33900	/* WEBPACK VAR INJECTION */(function(global) {'use strict';
33901
33902	exports.__esModule = true;
33903
33904	exports['default'] = function (Handlebars) {
33905	  /* istanbul ignore next */
33906	  var root = typeof global !== 'undefined' ? global : window,
33907	      $Handlebars = root.Handlebars;
33908	  /* istanbul ignore next */
33909	  Handlebars.noConflict = function () {
33910	    if (root.Handlebars === Handlebars) {
33911	      root.Handlebars = $Handlebars;
33912	    }
33913	    return Handlebars;
33914	  };
33915	};
33916
33917	module.exports = exports['default'];
33918	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
33919
33920/***/ })
33921/******/ ])
33922});
33923;
33924Handlebars.registerHelper('I18n',
33925    function (str) {
33926        return (I18n !== undefined ? I18n.t(str) : str);
33927    }
33928);
33929(function() {
33930  (function($) {
33931    var defaults, methods;
33932    defaults = {
33933      zIndex: 500
33934    };
33935    methods = {
33936      close: function(options) {
33937        var $modal, uuid;
33938        $modal = $(this);
33939        uuid = $modal.attr('data-modal-uuid');
33940        $modal.hide().removeAttr('data-modal-uuid').trigger('close');
33941        $('body').removeClass('is-scroll-locked');
33942        $('body').append($modal);
33943        return $(".modal-backdrop[data-modal-uuid='" + uuid + "']").remove();
33944      },
33945      align: function() {
33946        var $modal, left, uuid;
33947        $modal = $(this);
33948        uuid = $modal.attr('data-modal-uuid');
33949        if (uuid != null) {
33950          left = ($(window).width() - $modal.width()) / 2.0;
33951          return $modal.css({
33952            left: left
33953          });
33954        }
33955      },
33956      init: function(options) {
33957        options = $.extend(defaults, options);
33958        return this.each(function() {
33959          var $backDrop, $modal, uuid;
33960          $modal = $(this);
33961          if ($modal.data('modal-uuid') == null) {
33962            defaults.zIndex += 2;
33963            $modal.css({
33964              zIndex: defaults.zIndex
33965            }).addClass('animated fadeInDown').show();
33966            setTimeout(function() {
33967              return $modal.removeClass('animated fadeInDown');
33968            }, 300);
33969            uuid = guid();
33970            $modal.attr('data-modal-uuid', uuid);
33971            $backDrop = $("<div class='modal-backdrop' data-modal-uuid='" + uuid + "'></div>");
33972            $('body').addClass('is-scroll-locked').append($backDrop);
33973            $backDrop.append($modal);
33974            $backDrop.css({
33975              zIndex: defaults.zIndex - 1
33976            }).show();
33977            $modal.makeMeModal('align');
33978            $(window).resize((function(_this) {
33979              return function() {
33980                return $modal.makeMeModal('align');
33981              };
33982            })(this));
33983            return $modal.find('.js-modal-close').one('click', (function(_this) {
33984              return function() {
33985                if (typeof options.onClose === "function") {
33986                  options.onClose();
33987                }
33988                return $(_this).makeMeModal('close');
33989              };
33990            })(this));
33991          }
33992        });
33993      }
33994    };
33995    return $.fn.makeMeModal = function(method) {
33996      if (methods[method]) {
33997        return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
33998      } else if (typeof method === "object" || !method) {
33999        return methods.init.apply(this, arguments);
34000      } else {
34001        return $.error("Method " + method + " does not exist on jQuery.makeMeModal");
34002      }
34003    };
34004  })(jQuery);
34005
34006}
34006).call(this);
34007/*
34008(Built: Tue, May 08, 2018 12:40:15 PM)
34009Marmoset Viewer Code and Tools
34010
34011Copyright (c) 2018 Marmoset LLC.
34012All rights reserved.
34013
34014Redistribution and use of this software are permitted provided
34015that the software remains whole and unmodified and this copyright
34016notice remains attached. Use or inclusion of any portion of this
34017code in other software programs is prohibited, excepting simple
34018embedding of this file in web applications. This software, or any
34019derivatives thereof, may not be resold, rented, leased, or
34020distributed on any other for-charge basis.
34021
34022THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
34023IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
34024WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
34025*/
34026
34027marmoset={};(function(marmoset){'use strict';function AnimatedObject(a,b,c){this.name=b.partName;this.animatedProperties=[];this.sceneObjectType=b.sceneObjectType;this.skinningRigIndex=b.skinningRigIndex;this.id=c;this.mesh=this.skinningRig=0;this.materialIndex=this.lightIndex=this.meshIndex=-1;this.emissiveProperty=this.offsetVProperty=this.offsetUProperty=this.material=0;this.debugMe=b.debugMe;this.debugString="";this.hasTransform=!1;this.modelPartIndex=b.modelPartIndex;this.modelPartFPS=b.modelPartFPS;this.modelPartScale=b.modelPartScale;
34028this.parentIndex=b.parentIndex;this.startTime=b.startTime;this.endTime=b.endTime;this.animationLength=this.endTime-this.startTime;this.totalFrames=b.totalFrames;this.turnTableSpinOffset=this.turnTableSpin=this.spinProperty=this.dispersionProperty=this.lightIllumProperty=this.skyIllumProperty=this.opacityProperty=this.spotSharpnessProperty=this.spotAngleProperty=this.distanceProperty=this.brightnessProperty=this.blueProperty=this.greenProperty=this.redProperty=this.visibleProperty=0;if(b.animatedProperties){c=
34029b.animatedProperties.length;for(var d=0;d<c;++d){var e=b.animatedProperties[d],f=new AnimatedProperty;f.name=e.name;this.animatedProperties.push(f);"Red"!=f.name||this.redProperty||(this.redProperty=f);"Green"!=f.name||this.greenProperty||(this.greenProperty=f);"Blue"!=f.name||this.blueProperty||(this.blueProperty=f);"Brightness"!=f.name||this.brightnessProperty||(this.brightnessProperty=f);"Distance"!=f.name||this.distanceProperty||(this.distanceProperty=f);"Spot Angle"!=f.name||this.spotAngleProperty||
34030(this.spotAngleProperty=f);"Spot Sharpness"!=f.name||this.spotSharpnessProperty||(this.spotSharpnessProperty=f);"Opacity"!=f.name||this.opacityProperty||(this.opacityProperty=f);"Sky Illumination"!=f.name||this.skyIllumProperty||(this.skyIllumProperty=f);"Light Illumination"!=f.name||this.lightIllumProperty||(this.lightIllumProperty=f);"Dispersion"!=f.name||this.dispersionProperty||(this.dispersionProperty=f);"Visible"!=f.name||this.visibleProperty||(this.visibleProperty=f);"Spin Rate"==f.name&&(this.spinProperty=
34031f);"OffsetU"==f.name&&(this.offsetUProperty=f);"OffsetV"==f.name&&(this.offsetVProperty=f);"EmissiveIntensity"==f.name&&(this.emissiveProperty=f)}}this.keyframesSharedBufferUShorts=this.keyframesSharedBufferFloats=this.keyFramesByteStream=0;if(a=a.get(b.file))this.keyFramesByteStream=new ByteStream(a.data),this.unPackKeyFrames();this.animatedLocalTransform=new AnimatedTransform(this);this.hasTransform=this.animatedLocalTransform.hasTranslation||this.animatedLocalTransform.hasRotation||this.animatedLocalTransform.hasScale;
34032this.cachedWorldTransform0=Matrix.identity();this.cachedWorldTransform1=Matrix.identity();this.cachedWorldTransform2=Matrix.identity();this.cachedWorldTransform3=Matrix.identity();this.cachedFrame3=this.cachedFrame2=this.cachedFrame1=this.cachedFrame0=-10;this.cachedFrameUse3=this.cachedFrameUse2=this.cachedFrameUse1=this.cachedFrameUse0=0;this.useFixedLocalTransform=this.useFixedWorldTransform=!1}
34033AnimatedObject.prototype.setFixedWorldTransform=function(a){this.useFixedWorldTransform=!0;Matrix.copy(this.cachedWorldTransform0,a)};AnimatedObject.prototype.setFixedLocalTransform=function(a){this.useFixedLocalTransform=!0;this.animatedLocalTransform.lockTransform=!0;Matrix.copy(this.animatedLocalTransform.cachedmatrix0,a)};
34034AnimatedObject.prototype.getCachedWorldTransform=function(a){return this.useFixedWorldTransform?0:a==this.cachedFrame0?this.cachedmatrix0:a==this.cachedFrame1?this.cachedmatrix1:a==this.cachedFrame2?this.cachedmatrix2:a==this.cachedFrame3?this.cachedmatrix3:0};
34035AnimatedObject.prototype.getFreeCachedWorldTransform=function(a){if
34035(this.useFixedWorldTransform)return 0;this.cachedFrameUse0--;this.cachedFrameUse1--;this.cachedFrameUse2--;this.cachedFrameUse3--;if(this.cachedFrameUse0<=this.cachedFrameUse1&&this.cachedFrameUse0<=this.cachedFrameUse2&&this.cachedFrameUse0<=this.cachedFrameUse3)return this.cachedFrame0=a,this.cachedFrameUse0=0,this.cachedWorldTransform0;if(this.cachedFrameUse1<=this.cachedFrameUse0&&this.cachedFrameUse1<=this.cachedFrameUse2&&this.cachedFrameUse1<=
34036this.cachedFrameUse3)return this.cachedFrame1=a,this.cachedFrameUse1=0,this.cachedWorldTransform1;if(this.cachedFrameUse2<=this.cachedFrameUse0&&this.cachedFrameUse2<=this.cachedFrameUse1&&this.cachedFrameUse2<=this.cachedFrameUse3)return this.cachedFrame2=a,this.cachedFrameUse2=0,this.cachedWorldTransform2;this.cachedFrame3=a;this.cachedFrameUse3=0;return this.cachedWorldTransform3};
34037AnimatedObject.prototype.unPackKeyFrames=function(){if(this.keyFramesByteStream){var a=new Float32Array(this.keyFramesByteStream.bytes.buffer),b=new Uint32Array(this.keyFramesByteStream.bytes.buffer),c=new Uint16Array(this.keyFramesByteStream.bytes.buffer),d=new Uint8Array(this.keyFramesByteStream.bytes.buffer);this.keyframesSharedBufferFloats=a;this.keyframesSharedBufferUShorts=b;for(var a=0,b=b[0],a=1+b,b=this.animatedProperties.length,e=0;e<b;e++){var f=this.animatedProperties[e],g=2+2*e,h=2*g;
34038f.keyframeBufferStartIndexFloat=a;f.numKeyframes=c[g];f.keyframePackingType=d[h+2];f.interpolationType=d[h+3];f.indexFloatSkip=0;f.indexUShortSkip=0;0<f.numKeyframes&&(0==f.keyframePackingType?(f.bytesPerKeyFrame=16,f.indexFloatSkip=4,f.indexUShortSkip=8,f.valueOffsetFloat=0,f.weighInOffsetFloat=1,f.weighOutOffsetFloat=2,f.frameIndexOffsetUShort=6,f.interpolationOffsetUShort=7):1==f.keyframePackingType?(f.bytesPerKeyFrame=8,f.indexFloatSkip=2,f.indexUShortSkip=4,f.valueOffsetFloat=0,f.weighInOffsetFloat=
340390,f.weighOutOffsetFloat=0,f.frameIndexOffsetUShort=2,f.interpolationOffsetUShort=3):2==f.keyframePackingType&&(f.bytesPerKeyFrame=4,f.indexFloatSkip=1,f.indexUShortSkip=2,f.valueOffsetFloat=0,f.weighInOffsetFloat=0,f.weighOutOffsetFloat=0,f.frameIndexOffsetUShort=0,f.interpolationOffsetUShort=0));a+=f.numKeyframes*f.indexFloatSkip}}};
34040AnimatedObject.prototype.setupSkinningRig=function(a,b,c,d){var e=Matrix.identity(),f=Matrix.identity(),g=a.animatedObjects[b],h=c*g.modelPartFPS,h=h-Math.floor(h);c=Math.floor(a.getObjectAnimationFramePercent(g,c));var g=c+1,k=1-h,l=d.skinningClusters.length;if(0<l)for(var n=0;n<l;n++){var m=d.skinningClusters[n];m.solveClusterTransformAtFrame(a,b,c,e);m.solveClusterTransformAtFrame(a,b,g,f);for(var m=m.matrix,p=0;16>p;p++)m[p]=e[p]*k+f[p]*h}};
34041AnimatedObject.prototype.evaluateLocalTransformAtFramePercent=function(a,b,c,d){if(this.useFixedLocalTransform)Matrix.copy(b,this.animatedLocalTransform.cachedmatrix0);else{var e=0;d&&(e=this.animatedLocalTransform.getCachedTransform(a));e?Matrix.copy(b,e):((e=this.animatedLocalTransform.getFreeCachedTransform(a))?(this.animatedLocalTransform.evaluateMatrix(e,this.totalFrames,a,c),Matrix.copy(b,e)):this.animatedLocalTransform.evaluateMatrix(b,this.totalFrames,a,c),0!=this.turnTableSpin&&(a=Matrix.rotation(Matrix.empty(),
34042this.turnTableSpin,1),Matrix.mul(b,b,a)))}};AnimatedObject.prototype.hasAnimatedTransform=function(){var a=this.animatedLocalTransform;return a.TX&&1<a.TX.numKeyframes||a.TY&&1<a.TY.numKeyframes||a.TZ&&1<a.TZ.numKeyframes||a.RX&&1<a.RX.numKeyframes||a.RY&&1<a.RY.numKeyframes||a.RZ&&1<a.RZ.numKeyframes||a.SX&&1<a.SX.numKeyframes||a.SY&&1<a.SY.numKeyframes||a.SZ&&1<a.SZ.numKeyframes?!0:!1};function AnimatedProperty(){this.currentValue=0;this.keyframeBufferStartIndexFloat=-1;this.lastValue=this.interpolationOffsetUShort=this.frameIndexOffsetUShort=this.weighOutOffsetFloat=this.weighInOffsetFloat=this.valueOffsetFloat=this.indexUShortSkip=this.indexFloatSkip=this.interpolationType=this.bytesPerKeyFrame=this.keyframePackingType=0;this.lastFramePercent=-10;this.enable=!0;this.name="NONE";this.splineKF0=new KeyFrame(0,0);this.splineKF1=new KeyFrame(0,0);this.splineKF2=new KeyFrame(0,0);
34043this.splineKF3=new KeyFrame(0,0);this.debugMe=!0;this.debugString="";this.lastSearchIndex=1;this.savedSearchIndex=0;this.splineKF0.frameIndex=0;this.splineKF1.frameIndex=1;this.splineKF2.frameIndex=2;this.splineKF3.frameIndex=3;
34043this.numKeyframes=0}
34044AnimatedProperty.prototype.evaluateCurve=function(a,b){var c=this.splineKF1.frameIndex,d=this.splineKF2.frameIndex,e=this.splineKF1.value,f=this.splineKF2.value,g=c-(this.splineKF2.frameIndex-this.splineKF0.frameIndex),h=d-(this.splineKF1.frameIndex-this.splineKF3.frameIndex),k=e-(this.splineKF2.value-this.splineKF0.value)*this.splineKF1.weighOut,l=f-(this.splineKF1.value-this.splineKF3.value)*this.splineKF2.weighIn;3==this.splineKF1.interpolation&&(g=c-(this.splineKF2.frameIndex-this.splineKF1.frameIndex),
34045k=e-this.splineKF1.weighOut);3==this.splineKF2.interpolation&&(h=d-(this.splineKF1.frameIndex-this.splineKF2.frameIndex),l=f+this.splineKF2.weighIn);g=(a-g)/(c-g);c=(a-c)/(d-c);d=(a-d)/(h-d);h=e*(1-c)+f*c;return((k*(1-g)+e*g)*(1-c)+h*c)*(1-c)+((f*(1-d)+l*d)*c+h*(1-c))*c};
34046AnimatedProperty.prototype.evaluate=function(a,b,c){if(!c)return b;if(0==this.numKeyframes)return this.lastValue=b;if(1==this.numKeyframes)return this.lastValue=2==this.keyframePackingType?c.keyframesSharedBufferFloats[this.keyframeBufferStartIndexFloat]:c.keyframesSharedBufferFloats[this.keyframeBufferStartIndexFloat+this.valueOffsetFloat];if(this.lastFramePercent==a)return this.lastValue;var d=this.keyframeBufferStartIndexFloat,e=2*this.keyframeBufferStartIndexFloat;this.lastValue=b;this.lastFramePercent=
34047a;if(2==this.keyframePackingType){b=Math.floor(a);var f=a-b;a>=this.numKeyframes&&(b-=Math.floor(a/this.numKeyframes)*this.numKeyframes);if(b>=this.numKeyframes-1)return this.lastValue=c.keyframesSharedBufferFloats[d+(this.numKeyframes-1)];if(0>b)return this.lastValue=c.keyframesSharedBufferFloats[d];if(0==f)return this.lastValue=c.keyframesSharedBufferFloats[d+b];a=e=c.keyframesSharedBufferFloats[d+b];b++;b>=this.numKeyframes&&(b-=this.numKeyframes);0<=b&&b<this.numKeyframes?a=e*(1-f)+c.keyframesSharedBufferFloats[d+
34048b]*f:c.debugString+="<br>bad lerp frame "+b+" of "+this.numKeyframes;return this.lastValue=a}var g=this.numKeyframes,f=c.keyframesSharedBufferUShorts[e+this.frameIndexOffsetUShort];if(a>=c.keyframesSharedBufferUShorts[e+(g-1)*this.indexUShortSkip+this.frameIndexOffsetUShort])return this.lastValue=c.keyframesSharedBufferFloats[d+(g-1)*this.indexFloatSkip+this.valueOffsetFloat];if(a<f)return this.lastValue=c.keyframesSharedBufferFloats[d+this.valueOffsetFloat];this.lastSearchIndex<this.numKeyframes&&
34049a>c.keyframesSharedBufferUShorts[e+this.lastSearchIndex*this.indexUShortSkip+this.frameIndexOffsetUShort]&&(this.lastSearchIndex=1);for(var h=this.lastSearchIndex;h<g;h++){var f=d+h*this.indexFloatSkip,k=d+(h-1)*this.indexFloatSkip,l=e+h*this.indexUShortSkip,n=e+(h-1)*this.indexUShortSkip;if(a>=c.keyframesSharedBufferUShorts[n+this.frameIndexOffsetUShort]&&a<=c.keyframesSharedBufferUShorts[l+this.frameIndexOffsetUShort]){this.lastSearchIndex=h;var m=c.keyframesSharedBufferUShorts[n+this.interpolationOffsetUShort];
34050if(2==m){this.lastValue=a=a>=c.keyframesSharedBufferUShorts[l+this.frameIndexOffsetUShort]?c.keyframesSharedBufferFloats[f+this.valueOffsetFloat]:c.keyframesSharedBufferFloats[k+this.valueOffsetFloat];break}if(0==m){d=c.keyframesSharedBufferUShorts[n+this.frameIndexOffsetUShort];b=c.keyframesSharedBufferFloats[k+this.valueOffsetFloat];e=c.keyframesSharedBufferFloats[f+this.valueOffsetFloat];f=(a-d)/(c.keyframesSharedBufferUShorts[l+this.frameIndexOffsetUShort]-d);this.lastValue=a=b*(1-f)+e*f;break}if(1==
34051m||3==m){var p=m=!1,r=0,s=c.keyframesSharedBufferFloats[k+this.valueOffsetFloat],u=c.keyframesSharedBufferFloats[f+this.valueOffsetFloat],q=0,x=0,n=c.keyframesSharedBufferUShorts[n+this.frameIndexOffsetUShort],l=c.keyframesSharedBufferUShorts[l+this.frameIndexOffsetUShort],w=0,v=1,t=1,y=1,E=1,F=1,A=1,B=1,z=1;0!=this.weighInOffsetFloat&&(t=c.keyframesSharedBufferFloats[k+this.weighInOffsetFloat],y=c.keyframesSharedBufferFloats[f+this.weighInOffsetFloat],A=c.keyframesSharedBufferFloats[k+this.weighOutOffsetFloat],
34052B=c.keyframesSharedBufferFloats[f+this.weighOutOffsetFloat]);1<h&&(m=!0,r=c.keyframesSharedBufferFloats[d+(h-2)*this.indexFloatSkip+this.valueOffsetFloat],x=c.keyframesSharedBufferUShorts[e+(h-2)*this.indexUShortSkip+this.frameIndexOffsetUShort],0!=this.weighInOffsetFloat&&(v=c.keyframesSharedBufferFloats[d+(h-2)*this.indexFloatSkip+this.weighInOffsetFloat],F=c.keyframesSharedBufferFloats[d+(h-2)*this.indexFloatSkip+this.weighOutOffsetFloat]));h<g-1&&(p=!0,q=c.keyframesSharedBufferFloats[d+(h+1)*
34053this.indexFloatSkip+this.valueOffsetFloat],w=c.keyframesSharedBufferUShorts[e+(h+1)*this.indexUShortSkip+this.frameIndexOffsetUShort],0!=this.weighInOffsetFloat&&(E=c.keyframesSharedBufferFloats[d+(h+1)*this.indexFloatSkip+this.weighInOffsetFloat],z=c.keyframesSharedBufferFloats[d+(h+1)*this.indexFloatSkip+this.weighOutOffsetFloat]));m&&p?(this.splineKF0.value=r,this.splineKF1.value=s,this.splineKF2.value=u,this.splineKF3.value=q,this.splineKF0.frameIndex=x,this.splineKF1.frameIndex=n,this.splineKF2.frameIndex=
34054l,this.splineKF3.frameIndex=w,this.splineKF0.weighIn=v,this.splineKF0.weighOut=F,this.splineKF1.weighIn=t,this.splineKF1.weighOut=A,this.splineKF2.weighIn=y,this.splineKF2.weighOut=B,this.splineKF3.weighIn=E,this.splineKF3.weighOut=z):(this.splineKF0.value=s,this.splineKF1.value=s,this.splineKF2.value=u,this.splineKF3.value=u,this.splineKF0.frameIndex=n,this.splineKF1.frameIndex=n,this.splineKF2.frameIndex=l,this.splineKF3.frameIndex=l,this.splineKF1.weighIn=t,this.splineKF2.weighIn=y,this.splineKF1.weighOut=
34055A,this.splineKF2.weighOut=B,p?(this.splineKF3.value=q,this.splineKF3.frameIndex=w,this.splineKF3.weighIn=E,this.splineKF3.weighOut=z):(this.splineKF3.frameIndex++,this.splineKF3.value=this.splineKF1.value,this.splineKF3.weighIn=1,this.splineKF3.weighOut=1),m?(this.splineKF0.value=r,this.splineKF0.frameIndex=x,this.splineKF0.weighIn=v,this.splineKF0.weighOut=F):(this.splineKF0.value=this.splineKF2.value,this.splineKF0.weighIn=1,this.splineKF0.weighOut=1,0<this.splineKF0.frameIndex?this.splineKF0.frameIndex--:
34056(this.splineKF1.frameIndex++,this.splineKF2.frameIndex++,this.splineKF3.frameIndex++,a++)));this.lastValue=a=this.evaluateCurve(a,b);break}}}return this.lastValue};function AnimatedTransform(a){var b=a.animatedProperties;this.TX=this.TY=this.TZ=this.RX=this.RY=this.RZ=this.SX=this.SY=this.SZ=0;this.hostObject=a;this.matrix=Matrix.identity();this.cachedmatrix0=Matrix.identity();this.cachedmatrix1=Matrix.identity();this.cachedmatrix2=Matrix.identity();this.cachedmatrix3=Matrix.identity();this.cachedFrame3=this.cachedFrame2=this.cachedFrame1=this.cachedFrame0=-1;this.cachedFrameUse3=this.cachedFrameUse2=this.cachedFrameUse1=this.cachedFrameUse0=0;this.debugString=
34057"";for(a=0;a<b.length;a++){var c=b[a];"Translation X"==c.name?this.TX=c:"Translation Y"==c.name?this.TY=c:"Translation Z"==c.name?this.TZ=c:"Rotation X"==c.name?this.RX=c:"Rotation Y"==c.name?this.RY=c:"Rotation Z"==c.name?this.RZ=c:"Scale X"==c.name?this.SX=c:"Scale Y"==c.name?this.SY=c:"Scale Z"==c.name&&(this.SZ=c)}this.hasTranslation=this.TX&&this.TY&&this.TZ;this.hasRotation=this.RX&&this.RY&&this.RZ;this.hasScale=this.SX&&this.SY&&this.SZ;this.lockTransform=!1}
34058AnimatedTransform.prototype.getTRSValue=function(a,b,c){if(!b)return c;b.evaluate(a,c,this.hostObject);""!=b.debugString&&(this.debugString+=b.debugString);return b.lastValue};
34059AnimatedTransform.prototype.evaluateMatrix=function(a,b,c,d){if(this.lockTransform)Matrix.copy(a,this.cachedmatrix0);else{var e=0,f=b=0,e=f=b=0,e=f=b=1;this.hasRotation?(e=this.getTRSValue(c,this.RX,0),b=this.getTRSValue(c,this.RY,0),f=this.getTRSValue(c,this.RZ,0),d?(this.matrix=Matrix.rotation(Matrix.empty(),f,2),d=Matrix.rotation(Matrix.empty(),e,0),Matrix.mul(d,d,this.matrix
34059),this.matrix=Matrix.rotation(Matrix.empty(),b,1)):(this.matrix=Matrix.rotation(Matrix.empty(),e,0),d=Matrix.rotation(Matrix.empty(),
34060b,1),Matrix.mul(d,d,this.matrix),this.matrix=Matrix.rotation(Matrix.empty(),f,2)),Matrix.mul(this.matrix,this.matrix,d)):Matrix.copy(this.matrix,Matrix.identity());this.hasTranslation&&(b=this.getTRSValue(c,this.TX,0),f=this.getTRSValue(c,this.TY,0),e=this.getTRSValue(c,this.TZ,0),this.matrix[12]=b,this.matrix[13]=f,this.matrix[14]=e);this.hasScale&&(b=this.getTRSValue(c,this.SX,1),f=this.getTRSValue(c,this.SY,1),e=this.getTRSValue(c,this.SZ,1),this.matrix[0]*=b,this.matrix[4]*=f,this.matrix[8]*=
34061e,this.matrix[1]*=b,this.matrix[5]*=f,this.matrix[9]*=e,this.matrix[2]*=b,this.matrix[6]*=f,this.matrix[10]*=e,this.matrix[3]*=b,this.matrix[7]*=f,this.matrix[11]*=e);Matrix.copy(a,this.matrix)}};
34062AnimatedTransform.prototype.clearCachedTransforms=function(){this.cachedFrame3=this.cachedFrame2=this.cachedFrame1=this.cachedFrame0=-1;this.cachedFrameUse3=this.cachedFrameUse2=this.cachedFrameUse1=this.cachedFrameUse0=0;this.TX&&(this.TX.lastFramePercent=-10);this.TY&&(this.TY.lastFramePercent=-10);this.TZ&&(this.TZ.lastFramePercent=-10);this.RX&&(this.RX.lastFramePercent=-10);this.RY&&(this.RY.lastFramePercent=-10);this.RZ&&(this.RZ.lastFramePercent=-10);this.SX&&(this.SX.lastFramePercent=-10);
34063this.SY&&(this.SY.lastFramePercent=-10);this.SZ&&(this.SZ.lastFramePercent=-10);this.lockTransform=!1};AnimatedTransform.prototype.getCachedTransform=function(a){return this.lockTransform?0:this.cachedFrame0==a?this.cachedmatrix0:this.cachedFrame1==a?this.cachedmatrix1:this.cachedFrame2==a?this.cachedmatrix2:this.cachedFrame3==a?this.cachedmatrix3:0};
34064AnimatedTransform.prototype.getFreeCachedTransform=function(a){if(this.lockTransform)return 0;this.cachedFrameUse0--;this.cachedFrameUse1--;this.cachedFrameUse2--;this.cachedFrameUse3--;if(this.cachedFrameUse0<=this.cachedFrameUse1&&this.cachedFrameUse0<=this.cachedFrameUse2&&this.cachedFrameUse0<=this.cachedFrameUse3||this.cachedFrame0==a)return this.cachedFrame0=a,this.cachedFrameUse0=0,this.cachedmatrix0;if(this.cachedFrameUse1<=this.cachedFrameUse0&&this.cachedFrameUse1<=this.cachedFrameUse2&&
34065this.cachedFrameUse1<=this.cachedFrameUse3||this.cachedFrame1==a)return this.cachedFrame1=a,this.cachedFrameUse1=0,this.cachedmatrix1;if(this.cachedFrameUse2<=this.cachedFrameUse0&&this.cachedFrameUse2<=this.cachedFrameUse1&&this.cachedFrameUse2<=this.cachedFrameUse3||this.cachedFrame2==a)return this.cachedFrame2=a,this.cachedFrameUse2=0,this.cachedmatrix2;this.cachedFrame3=a;this.cachedFrameUse3=0;return this.cachedmatrix3};function Animation(a,b){this.originalFPS=1;this.name=b.name;this.totalSeconds=b.length;this.originalFPS=b.originalFPS;this.totalFrames=b.totalFrames;this.expectedNumAnimatedObjects=b.numAnimatedObjects;this.animatedObjects=[];this.sceneTransform=Matrix.identity();this.debugString="";if(b.animatedObjects)for(var c=b.animatedObjects.length,d=0;d<c;++d){var e=new AnimatedObject(a,b.animatedObjects[d],d);this.animatedObjects.push(e);this.debugString+=e.debugString}this.meshObjects=[];this.lightObjects=
34066[];this.materialObjects=[];this.turnTableObjects=[];this.cameraObjects=[]}
34067Animation.prototype.evaluateModelPartTransformAtFrame=function(a,b,c,d){Matrix.copy(c,Matrix.identity());for(var e=0;100>e;e++){var f=this.animatedObjects[a];if(a==f.parentIndex)break;if(f.useFixedWorldTransform){Matrix.mul(c,f.cachedWorldTransform0,c);break}else{var g=0;d&&(g=f.getCachedWorldTransform(b));if(g){Matrix.mul(c,g,c);break}else g=Matrix.identity(),f.evaluateLocalTransformAtFramePercent(b,g,!1,d),Matrix.mul(c,g,c),a==f.parentIndex&&(e=100),a=f.parentIndex}}};
34068Animation.prototype.lerpModelPartTransform=function(a,b,c,d){var e=this.animatedObjects[a];if(e.useFixedWorldTransform)Matrix.copy(c,e.cachedWorldTransform0);else{var f=b*e.modelPartFPS,f=f-Math.floor(f),g=Math.floor(this.getObjectAnimationFramePercent(e,b)),h=g+1,k=b=0;d&&(b=e.getCachedWorldTransform(g),k=e.getCachedWorldTransform(h));b||((b=e.getFreeCachedWorldTransform(g))||(b=Matrix.identity()),this.evaluateModelPartTransformAtFrame(a,g,b,d));k||((k=e.getFreeCachedWorldTransform(h))||(k=Matrix.identity()),
34069this.evaluateModelPartTransformAtFrame(a,h,k,d));a=1-f;for(d=0;16>d;d++)c[d]=b[d]*a+k[d]*f}};Animation.prototype.getModelPartTransform=function(a,b,c,d){this.lerpModelPartTransform(a,b,c,d)};
34070Animation.prototype.getAnimatedLocalTransform=function(a,b,c,d){a=this.animatedObjects[a];var e=this.animatedObjects[a.parentIndex],f=e.modelPartIndex!=e.id,g=Matrix.identity();this.getModelPartTransform(a.modelPartIndex,b,g,d);if(f){var f=Matrix.identity(),h=Matrix.identity();this.getModelPartTransform(e.modelPartIndex,b,f,d);Matrix.invert(h,f);
34070Matrix.mul(c,h,g);c[12]*=a.modelPartScale;c[13]*=a.modelPartScale;c[14]*=a.modelPartScale}else Matrix.copy(c,g)};
34071Animation.prototype.isVisibleAtFramePercent=function(a,b){for(var c=a,d=0,e=0;100>e;e++){d=this.animatedObjects[c];if(d.visibleProperty){d.visibleProperty.evaluate(b,1,d);if(""!=d.debugString||""!=d.visibleProperty.debugString)return this.debugString+=d.debugString,this.debugString+=d.visibleProperty.debugString,!1;if(0==d.visibleProperty.lastValue)return!1}c==d.parentIndex&&(e=100);c=d.parentIndex}return!0};
34072Animation.prototype.getWorldTransform=function(a,b,c,d,e){a=this.animatedObjects[a];if(a.useFixedWorldTransform)Matrix.copy(c,a.cachedWorldTransform0);else{var f=this.getObjectAnimationFramePercent(a,b),g=Matrix.identity();a.evaluateLocalTransformAtFramePercent(f,g,!0,e);if(f=a.modelPartIndex!=a.id){var f=Matrix.identity(),h=Matrix.identity();Matrix.copy(h,g);this.getAnimatedLocalTransform(a.id,b,f);Matrix.mul(g,f,h)}Matrix.copy(c,g);if(a.parentIndex!=a.id)for(var k=a.parentIndex,l=0;100>l;l++)a=
34073this.animatedObjects[k],f=this.getObjectAnimationFramePercent(a,b),g=Matrix.identity(),a.evaluateLocalTransformAtFramePercent(f,g,!0,e),(f=a.modelPartIndex!=a.id)?(f=Matrix.identity(),this.getAnimatedLocalTransform(a.id,b,f),h=Matrix.identity(),Matrix.mul(h,g,c),Matrix.mul(c,f,h)):(h=Matrix.identity(),Matrix.copy(h,c),Matrix.mul(c,g,h)),k==a.parentIndex&&(l=100),k=a.parentIndex;c[12]*=d;c[13]*=d;c[14]*=d}};
34074Animation.prototype.hasParentInHierarchy=function(a,b){for(var c=a.parentIndex,d=0;100>d;d++){a=this.animatedObjects[c];if(a.id==b)return!0;c==a.parentIndex&&(d=100);c=a.parentIndex}return!1};Animation.prototype.hasParentTypeInHierarchy=function(a,b){for(var c=a.parentIndex,d=0;100>d;d++){a=this.animatedObjects[c];if(a.sceneObjectType==b)return!0;c==a.parentIndex&&(d=100);c=a.parentIndex}return!1};
34075Animation.prototype.searchAnimationUpHierarchy=function(a){for(var b=a.id,c=0;100>c;c++){a=this.animatedObjects[b];if(a.animatedLocalTransform&&(a.hasAnimatedTransform()||a.id!=a.modelPartIndex&&this.searchAnimationUpHierarchy(this.animatedObjects[a.modelPartIndex])))return!0;b==a.parentIndex&&(c=100);b=a.parentIndex}return!1};
34076Animation.prototype.hasAnimationInHierarchy=function(a){return this.searchAnimationUpHierarchy(a)||a.id!=a.modelPartIndex&&this.searchAnimationUpHierarchy(this.animatedObjects[a.modelPartIndex])||this.hasParentTypeInHierarchy(a,"TurnTableSO")||this.hasParentTypeInHierarchy(a,"CameraSO")||"CameraSO"==a.sceneObjectType?!0:!1};
34077Animation.prototype.getObjectAnimationFramePercent=function(a,b){if(0==this.totalFrames||0==a.animationLength)return 0;if(a.endTime==this.totalSeconds)return b*a.modelPartFPS;var c=b/a.animationLength,c=Math.floor(c);b-=a.animationLength*c;c=b*a.modelPartFPS;c>=a.totalFrames+1&&(c=a.totalFrames);return c};function Archive(a){this.files=[];for(a=new ByteStream(a);!a.empty();){var b={};b.name=a.readCString();b.type=a.readCString();var c=a.readUint32(),d=a.readUint32(),e=a.readUint32();b.data=a.readBytes(d);if(!(b.data.length<d)){if(c&1&&(b.data=this.decompress(b.data,e),null===b.data))continue;this.files[b.name]=b}}}Archive.prototype.get=function(a){return this.files[a]};Archive.prototype.extract=function(a){var b=this.files[a];delete this.files[a];return b};
34078Archive.prototype.checkSignature=function(a){if(!a)return!1;var b=this.get(a.name+".sig");if(!b)return!1;b=JSON.parse(String.fromCharCode.apply(null,b.data));if(!b)return!1;for(var c=5381,d=0;d<a.data.length;++d)c=33*c+a.data[d]&4294967295;a=new BigInt;a.setBytes([0,233,33,170,116,86,29,195,228,46,189,3,185,31,245,19,159,105,73,190,158,80,175,38,210,116,221,229,171,134,104,144,140,5,99,255,208,78,248,215,172,44,79,83,5,244,152,19,92,137,112,10,101,142,209,100,244,92,190,125,28,0,185,54,143,247,49,
3407937,15,254,142,180,185,232,50,219,11,186,106,116,78,212,10,105,53,26,14,181,80,47,87,213,182,19,126,151,86,109,182,224,37,135,80,59,22,93,125,68,214,106,209,152,235,157,249,245,48,76,203,0,0,95,200,246,243,229,85,79,169],!0);d=new BigInt;d.setBytes(b[0]);return d.powmod(65537,a).toInt32()!=c?!1:!0};
34080Archive.prototype.decompress=function(a,b){var c=new Uint8Array(b),d=0,e=new Uint32Array(4096),f=new Uint32Array(4096),g=256,h=a.length,k=0,l=1,n=0,m=1;c[d++]=a[0];for(var p=1;;p++){m=p+(p>>1);if(m+1>=h)break;var n=a[m+1],m=a[m],r=p&1?n<<4|m>>4:(n&15)<<8|m;if(r<g)if(256>r)n=d,m=1,c[d++]=r;else for(var n=d,m=f[r],r=e[r],s=r+m;r<s;)c[d++]=c[r++];else if(r==g){n=d;m=l+1;r=k;for(s=k+l;r<s;)c[d++]=c[r++];c[d++]=c[k]}else break;e[g]=k;f[g++]=l+1;k=n;l=m;g=4096<=g?256:g}return d==b?c:null};function BigInt(a){this.digits=new Uint16Array(a||0)}BigInt.prototype.setBytes=function(a,b){var c=(a.length+1)/2|0;this.digits=new Uint16Array(c);if(b)for(var d=0,c=a.length-1;0<=c;c-=2)this.digits[d++]=a[c]+(0<c?256*a[c-1]:0);else for(d=0;d<c;++d)this.digits[d]=a[2*d]+256*a[2*d+1];this.trim()};BigInt.prototype.toInt32=function(){var a=0;0<this.digits.length&&(a=this.digits[0],1<this.digits.length&&(a|=this.digits[1]<<16));return a};
34081BigInt.prototype.lessThan=function(a){if(this.digits.length==a.digits.length)for(var b=this.digits.length-1;0<=b;--b){var c=this.digits[b],d=a.digits[b];if(c!=d)return c<d}return this.digits.length<a.digits.length};BigInt.prototype.shiftRight=function(){for(var a=0,b=this.digits,c=b.length-1;0<=c;--c){var d=b[c];b[c]=d>>1|a<<15;a=d}this.trim()};
34082BigInt.prototype.shiftLeft=function(a){if(0<a){var b=a/16|0;a%=16;for(var c=16-a,d=this.digits.length+b+1,e=new BigInt(d),f=0;f<d;++f)e.digits[f]=((f<b||f>=this.digits.length+b?0:this.digits[f-b])<<a|(f<b+1?0:this.digits[f-b-1])>>>c)&65535;e.trim();return e}return new BigInt(this)};BigInt.prototype.bitCount=function(){var a=0;if(0<this.digits.length)for(var a=16*(this.digits.length-1),b=this.digits[this.digits.length-1];b;)b>>>=1,++a;return a};
34083BigInt.prototype.sub=function(a){var b=this.digits,c=a.digits,d=this.digits.length;a=a.digits.length;for(var e=0,f=0;f<d;++f){var g=b[f],h=f<a?c[f]:0,h=h+e,e=h>g?1:0,g=g+(e<<16);b[f]=g-h&65535}this.trim()};BigInt.prototype.mul=function(a){for(var b=new BigInt(this.digits.length+a.digits.length),c=b.digits,d=0;d<this.digits.length;++d)for(var e=this.digits[d],f=0;f<a.digits.length;++f)for(var g=e*a.digits[f],h=d+f;g;){var k=(g&65535)+c[h];c[h]=k&65535;g>>>=16;g+=k>>>16;++h}b.trim();return b};
34084BigInt.prototype.mod=function(a){if(0>=this.digits.length||0>=a.digits.length)return new BigInt(0);var b=new BigInt(this.digits);if(!this.lessThan(a)){for(var c=new BigInt(a.digits),c=c.shiftLeft(b.bitCount()-c.bitCount());!b.lessThan(a);)c.lessThan(b)&&b.sub(c),c.shiftRight();b.trim()}return b};BigInt.prototype.powmod=function(a,b){for(var c=new BigInt([1]),d=this.mod(b);a;)a&1&&(c=c.mul(d).mod(b)),a>>>=1,d=d.mul(d).mod(b);return c};
34085BigInt.prototype.trim=function(){for(var a=this.digits.length;0<a&&0==this.digits[a-1];)--a;a!=this.digits.length&&(this.digits=this.digits.subarray(0,a))};function Bounds(a){for(var b=0;b<a.length;++b){var c=a[b].bounds;if(void 0===this.min)this.min=[c.min[0],c.min[1],c.min[2]],this.max=[c.max[0],c.max[1],c.max[2]];else for(var d=0;3>d;++d)this.min[d]=Math.min(c.min[d],this.min[d]),this.max[d]=Math.max(c.max[d],this.max[d])}this.min=this.min?this.min:[0,0,0];this.max=this.max?this.max:[0,0,0];this.center=[0.5*(this.min[0]+this.max[0]),0.5*(this.min[1]+this.max[1]),0.5*(this.min[2]+this.max[2])];this.radius=[this.max[0]-this.center[0],this.max[1]-this.center[1],
34086this.max[2]-this.center[2]];this.radiusDiagonal=Math.sqrt(this.radius[0]*this.radius[0]+this.radius[1]*this.radius[1]+this.radius[2]*this.radius[2])};function Button(a){this.name="none";this.text="default text";this.title="none";this.debugString=this.imagePath="";this.controlRect=new ControlRect(a);this.defaultAlpha=0.5;this.focusAlpha=1;this.updateAlphas=!0;this.linkedBackground=this.backgroundOffsetY=this.backgroundOffsetX=this.edgePixelsY=this.edgePixelsX=this.backgroundBottomMiddle=this.backgroundBottomRight=this.backgroundBottomLeft=this.backgroundMiddleMiddle=this.backgroundMiddleRight=this.backgroundMiddleLeft=this.backgroundTopMiddle=this.backgroundTopRight=
34087this.backgroundTopLeft=this.backgroundMiddle=this.backgroundRight=this.backgroundLeft=0}Button.prototype.setBackground3x1=function(a,b,c,d,e,f,g){this.backgroundOffsetX=b;this.backgroundOffsetY=c;this.edgePixelsX=g;this.backgroundLeft=a.addImage(d);this.backgroundMiddle=a.addImage(e);this.backgroundRight=a.addImage(f);this.backgroundLeft.linkedControl.style.zIndex="0";this.backgroundMiddle.linkedControl.style.zIndex="0";this.backgroundRight.linkedControl.style.zIndex="0";this.setOpacity(this.defaultAlpha)};
34088Button.prototype.setBackground3x3=function(a,b,c,d,e,f,g,h,k,l,n,m,p,r){this.backgroundOffsetX=b;this.backgroundOffsetY=c;this.edgePixelsX=p;this.edgePixelsY=r;this.backgroundTopLeft=a.addImage(d);this.backgroundMiddleLeft=a.addImage(g);this.backgroundBottomLeft=a.addImage(l);this.backgroundTopMiddle=a.addImage(e);this.backgroundMiddleMiddle=a.addImage(h);this.backgroundBottomMiddle=a.addImage(n);this.backgroundTopRight=a.addImage(f);this.backgroundMiddleRight=a.addImage(k);this.backgroundBottomRight=
34089a.addImage(m);this.backgroundTopLeft.linkedControl.style.zIndex="0";this.backgroundTopRight.linkedControl.style.zIndex="0";this.backgroundTopMiddle.linkedControl.style.zIndex="0";this.backgroundMiddleLeft.linkedControl.style.zIndex="0";this.backgroundMiddleRight.linkedControl.style.zIndex="0";this.backgroundMiddleMiddle.linkedControl.style.zIndex="0";this.backgroundBottomLeft.linkedControl.style.zIndex="0";this.backgroundBottomRight.linkedControl.style.zIndex="0";this.backgroundBottomMiddle.linkedControl.style.zIndex=
34090"0";this.setOpacity(this.defaultAlpha)};
34091Button.prototype.alignBackground=function(){var a=this.controlRect,b=a.guiScreen,c=b.left*(1-a.getScreenXPercent()),d=b.bottom*(1-a.getScreenYPercent()),e=b.width*a.getScreenWidthPercent(),a=b.height*a.getScreenHeightPercent(),d=d+this.backgroundOffsetY,c=c+this.backgroundOffsetX;if(this.backgroundTopLeft&&this.backgroundTopRight&&this.backgroundTopMiddle&&this.backgroundMiddleLeft&&this.backgroundMiddleRight&&this.backgroundMiddleMiddle&&this.backgroundBottomLeft&&this.backgroundBottomRight&&this.backgroundBottomMiddle){var b=
34092e-2*this.edgePixelsX,f=a-2*this.edgePixelsY;this.backgroundTopLeft.linkedControl.style.height=this.edgePixelsY+"px";this.backgroundTopMiddle.linkedControl.style.height=this.edgePixelsY+"px";this.backgroundTopRight.linkedControl.style.height=this.edgePixelsY+"px";this.backgroundBottomLeft.linkedControl.style.height=this.edgePixelsY+"px";this.backgroundBottomMiddle.linkedControl.style.height=this.edgePixelsY+"px";this.backgroundBottomRight.linkedControl.style.height=this.edgePixelsY+"px";this.backgroundMiddleLeft.linkedControl.style.height=
34093f+"px";this.backgroundMiddleMiddle.linkedControl.style.height=f+"px";this.backgroundMiddleRight.linkedControl.style.height=f+"px";this.backgroundTopLeft.linkedControl.style.width=this.edgePixelsX+"px";this.backgroundBottomLeft.linkedControl.style.width=this.edgePixelsX+"px";this.backgroundMiddleLeft.linkedControl.style.width=this.edgePixelsX+"px";this.backgroundTopRight.linkedControl.style.width=this.edgePixelsX+"px";this.backgroundBottomRight.linkedControl.style.width=this.edgePixelsX+"px";this.backgroundMiddleRight.linkedControl.style.width=
34094this.edgePixelsX+"px";this.backgroundTopMiddle.linkedControl.style.width=b+"px";this.backgroundBottomMiddle.linkedControl.style.width=b+"px";this.backgroundMiddleMiddle.linkedControl.style.width=b+"px";this.backgroundTopLeft.linkedControl.style.left=c+"px";this.backgroundBottomLeft.linkedControl.style.left=c+"px";this.backgroundMiddleLeft.linkedControl.style.left=c+"px";c+=this.edgePixelsX;this.backgroundTopMiddle.linkedControl.style.left=c+"px";this.backgroundBottomMiddle.linkedControl.style.left=
34095c+"px";this.backgroundMiddleMiddle.linkedControl.style.left=c+"px";c+=b;this.backgroundTopRight.linkedControl.style.left=c+"px";this.backgroundBottomRight.linkedControl.style.left=c+"px";this.backgroundMiddleRight.linkedControl.style.left=c+"px";this.backgroundBottomLeft.linkedControl.style.bottom=d+"px";this.backgroundBottomMiddle.linkedControl.style.bottom=d+"px";this.backgroundBottomRight.linkedControl.style.bottom=d+"px";d+=this.edgePixelsY;this.backgroundMiddleLeft.linkedControl.style.bottom=
34096d+"px";this.backgroundMiddleRight.linkedControl.style.bottom=d+"px";this.backgroundMiddleMiddle.linkedControl.style.bottom=d+"px";d+=f;this.backgroundTopLeft.linkedControl.style.bottom=d+"px";this.backgroundTopMiddle.linkedControl.style.bottom=d+"px";this.backgroundTopRight.linkedControl.style.bottom=d+"px"}this.backgroundLeft&&this.backgroundRight&&this.backgroundMiddle&&(e-=2*this.edgePixelsX,this.backgroundLeft.linkedControl.style.bottom=d+"px",this.backgroundMiddle.linkedControl.style.bottom=
34097d+"px",this.backgroundRight.linkedControl.style.bottom=d+"px",this.backgroundLeft.linkedControl.style.height=a+"px",this.backgroundMiddle.linkedControl.style.height=a+"px",this.backgroundRight.linkedControl.style.height=a+"px",this.backgroundLeft.linkedControl.style.width=this.edgePixelsX+"px",this.backgroundMiddle.linkedControl.style.width=e+"px",this.backgroundRight.linkedControl.style.width=this.edgePixelsX+"px",this.backgroundLeft.linkedControl.style.left=c+"px",c+=this.edgePixelsX,this.backgroundMiddle.linkedControl.style.left=
34098c+"px",this.backgroundRight.linkedControl.style.left=c+e+"px")};
34099Button.prototype.setOpacity=function(a){this.controlRect.linkedControl.style.opacity=a;this.backgroundLeft&&(this.backgroundLeft.linkedControl.style.opacity=a);this.backgroundRight&&(this.backgroundRight.linkedControl.style.opacity=a);this.backgroundMiddle&&(this.backgroundMiddle.linkedControl.style.opacity=a);this.backgroundTopLeft&&(this.backgroundTopLeft.linkedControl.style.opacity=a);this.backgroundTopRight&&(this.backgroundTopRight.linkedControl.style.opacity=a);this.backgroundTopMiddle&&(this.backgroundTopMiddle.linkedControl.style.opacity=
34100a);this.backgroundMiddleLeft&&(this.backgroundMiddleLeft.linkedControl.style.opacity=a);this.backgroundMiddleRight&&(this.backgroundMiddleRight.linkedControl.style.opacity=a);this.backgroundMiddleMiddle&&(this.backgroundMiddleMiddle.linkedControl.style.opacity=a);this.backgroundBottomLeft&&(this.backgroundBottomLeft.linkedControl.style.opacity=a);this.backgroundBottomRight&&(this.backgroundBottomRight.linkedControl.style.opacity=a);this.backgroundBottomMiddle&&(this.backgroundBottomMiddle.linkedControl.style.opacity=
34101a)};
34102Button.prototype.setBackgroundVisible=function(a){this.backgroundLeft&&this.backgroundLeft.showControl(a);this.backgroundRight&&this.backgroundRight.showControl(a);this.backgroundMiddle&&this.backgroundMiddle.showControl(a);this.backgroundTopLeft&&this.backgroundTopLeft.showControl(a);this.backgroundTopRight&&this.backgroundTopRight.showControl(a);this.backgroundTopMiddle&&this.backgroundTopMiddle.showControl(a);this.backgroundMiddleLeft&&this.backgroundMiddleLeft.showControl(a);this.backgroundMiddleRight&&this.backgroundMiddleRight.showControl(a);
34103this.backgroundMiddleMiddle&&this.backgroundMiddleMiddle.showControl(a);this.backgroundBottomLeft&&this.backgroundBottomLeft.showControl(a);this.backgroundBottomRight&&this.backgroundBottomRight.showControl(a);this.backgroundBottomMiddle&&this.backgroundBottomMiddle.showControl(a)};Button.prototype.setVisible=function(a){this.controlRect.showControl(a);this.setBackgroundVisible(a)};
34104Button.prototype.linkControl=function(a){this.controlRect.linkedControl=a;a.onmouseover=function(){this.updateAlphas&&(this.setOpacity(this.focusAlpha),this.controlRect.mouseOver=!0,this.linkedBackground&&this.linkedBackground.setOpacity(this.focusAlpha))}.bind(this);a.onmouseout=function(){this.updateAlphas&&(this.setOpacity(this.defaultAlpha),this.controlRect.mouseOver=!1,this.linkedBackground&&this.linkedBackground.setOpacity(this.defaultAlpha))}.bind(this)};function ByteStream(a){this.bytes=new Uint8Array(a)}ByteStream.prototype.empty=function(){return 0>=this.bytes.length};ByteStream.prototype.readCString=function(){for(var a=this.bytes,b=a.length,c=0;c<b;++c)if(0==a[c])return a=String.fromCharCode.apply(null,this.bytes.subarray(0,c)),this.bytes=this.bytes.subarray(c+1),a;return null};ByteStream.prototype.asString=function(){for(var a="",b=0;b<this.bytes.length;++b)a+=String.fromCharCode(this.bytes[b]);return a};
34105ByteStream.prototype.readBytes=function(a){var b=this.bytes.subarray(0,a);this.bytes=this.bytes.subarray(a);return b};ByteStream.prototype.readUint32=function(){var a=this.bytes,b=a[0]|a[1]<<8|a[2]<<16|a[3]<<24;this.bytes=a.subarray(4);return b};ByteStream.prototype.readUint8=function(){var a=this.bytes,b=a[0];this.bytes=a.subarray(1);return b};ByteStream.prototype.readUint16=function(){var a=this.bytes,b=a[0]|a[1]<<8;this.bytes=a.subarray(2);return b};
34106ByteStream.prototype.readFloat32=function(){var a=new Uint8Array(this.bytes),a=new Float32Array(a.buffer);this.bytes=this.bytes.subarray(4);return a[0]};ByteStream.prototype.seekUint32=function(a){a=this.bytes.subarray(4*a);return a[0]|a[1]<<8|a[2]<<16|a[3]<<24};ByteStream.prototype.seekFloat32=function(a){a=new Uint8Array(this.bytes.subarray(4*a));return(new Float32Array(a.buffer))[0]};ByteStream.prototype.getMatrix=function(a){return new Float32Array(this.bytes.buffer,64*a,16)};function ControlRect(a){this.name="none";this.title="frame";this.yPercent=this.xPercent=0;this.heightPercent=this.widthPercent=1;this.debugString="";this.parentControlRect=0;this.childControlRects=[];this.clicked=this.mouseDown=this.mouseOver=!1;this.localMouseYPercent=this.localMouseXPercent=0;this.enabled=this.visible=!0;this.opacity=1;this.guiScreen=a;this.id=this.callBack=this.linkedControl=0}ControlRect.prototype.getScreenWidth=function(){if(this.linkedControl)return this.guiScreen.width*this.getScreenWidthPercent()};
34107ControlRect.prototype.getScreenHeight=function(){if(this.linkedControl)return this.guiScreen.height*this.getScreenHeightPercent()};
34108ControlRect.prototype.updateElement=function(){var a=this.linkedControl;if(a){var b=this.guiScreen.left*(1-this.getScreenXPercent()),c=this.guiScreen.bottom*(1-this.getScreenYPercent()),d=this.guiScreen.width*this.getScreenWidthPercent(),e=this.guiScreen.height*this.getScreenHeightPercent();a.style.left=b+"px";a.style.bottom=c+"px";a.style.width=d+"px";a.style.height=e+"px"}};
34109ControlRect.prototype.updateElement=function(){var a=this.linkedControl;if(a){var b=this.guiScreen.left*(1-this.getScreenXPercent()),c=this.guiScreen.bottom*(1-this.getScreenYPercent()),d=this.guiScreen.width*this.getScreenWidthPercent(),e=this.guiScreen.height*this.getScreenHeightPercent();a.style.left=b+"px";a.style.bottom=c+"px";a.style.width=d+"px";a.style.height=e+"px"}};ControlRect.prototype.updateChildElements=function(){this.updateElement();for(var a=0;a<this.childControlRects.length;a++)this.childControlRects[a].updateChildElements()};
34110ControlRect.prototype.set=function(a,b,c,d){this.xPercent=a;this.yPercent=b;this.widthPercent=c;this.heightPercent=d};
34110ControlRect.prototype.linkControl=function(a){this.linkedControl=a;a.onmouseover=function(){this.mouseOver=!0}.bind(this);a.onmouseout=function(){this.mouseOver=!1}.bind(this);a.onmousedown=function(){this.mouseDown=!0}.bind(this);a.onmouseup=function(){this.mouseDown=!1}.bind(this);a.onclick=function(){this.callBack&&this.callBack(this);this.clicked=!0}.bind(this)};
34111ControlRect.prototype.showControl=function(a){this.visible=a;this.linkedControl&&(this.linkedControl.style.display=a?"block":"none")};ControlRect.prototype.setOpacity=function(a){this.opacity=a;this.linkedControl&&(this.linkedControl.style.opacity=a)};ControlRect.prototype.hasChildControlRect=function(a){for(var b=0;b<this.childControlRects.length;b++)if(this.childControlRects[b]==a)return!0;return!1};
34112ControlRect.prototype.registerChildControlRect=function(a){this.hasChildControlRect(a)||(this.childControlRects.push(a),a.parentControlRect=this)};ControlRect.prototype.getScreenWidthPercent=function(){var a=this.widthPercent;this.parentControlRect&&(a*=this.parentControlRect.getScreenWidthPercent());return a};ControlRect.prototype.getScreenHeightPercent=function(){var a=this.heightPercent;this.parentControlRect&&(a*=this.parentControlRect.getScreenHeightPercent());return a};
34113ControlRect.prototype.getScreenXPercent=function(){var a=this.xPercent;this.parentControlRect&&(a*=this.parentControlRect.getScreenWidthPercent(),a+=this.parentControlRect.getScreenXPercent());return a};ControlRect.prototype.getScreenYPercent=function(){var a=this.yPercent;this.parentControlRect&&(a*=this.parentControlRect.getScreenHeightPercent(),a+=this.parentControlRect.getScreenYPercent());return a};var prepareEmbedParams=function(a){a=a||{};if(document.location.search)for(var b=document.location.search.substring(1).split("&"),c=0;c<b.length;++c){var d=b[c].split("=");a[d[0]]=d[1]}b=function(a){if(a|0)return!0;for(var c="true True TRUE yes Yes YES".split(" "),b=0;b<c.length;++b)if(a===c[b])return!0;return!1};a.width=a.width||800;a.height=a.height||600;a.autoStart=b(a.autoStart);a.pagePreset=b(a.pagePreset);a.fullFrame=b(a.fullFrame)||b(a.bare);a.fullFrame=!a.pagePreset&&a.fullFrame;return a},
34114embed=function(a,b){var c;b=prepareEmbedParams(b);var d=b.thumbnailURL;if(b.pagePreset){c=new WebViewer(b.width,b.height,a,!!d);document.body.style.backgroundColor="#d7e4da";var e=document.createElement("div");e.style.position="relative";e.style.backgroundColor="#e4e7e4";e.style.width=b.width+12+"px";e.style.height=b.height+6+16+"px";e.style.margin="auto";e.style.boxShadow="3px 5px 12px 0px grey";document.body.appendChild(e);var f=document.createElement("div");f.style.position="relative";f.style.left=
34115"6px";f.style.top="6px";e.appendChild(f);f.appendChild(c.domRoot);if(!c.mobile){e.style.resize="both";e.style.overflow="hidden";var g=[e.style.width,e.style.height],h=function(){if(FullScreen.active())e.style.resize="none";else if(e.style.resize="both",g[0]!=e.style.width||g[1]!=e.style.height)g[0]=e.style.width,g[1]=e.style.height,c.resize(e.clientWidth-12,e.clientHeight-6-16);window.setTimeout(h,100)};h()}}else c=new WebViewer(b.fullFrame?window.innerWidth:b.width,b.fullFrame?window.innerHeight:
34116b.height,a,!!d),document.body.appendChild(c.domRoot),b.fullFrame&&(c.domRoot.style.position="absolute",c.domRoot.style.left=c.domRoot.style.top=0,window.addEventListener("resize",function(){FullScreen.active()||c.resize(window.innerWidth,window.innerHeight)}));c.ui.setThumbnailURL(d);b.autoStart&&c.loadScene();return c},fetchThumbnail=function(a,b,c,d){var e=!1,f=a+(-1==a.indexOf("?")?"?":"&")+"thumb=1",g=function(a){(a=(new Archive(a)).extract("thumbnail.jpg"))?TextureCache.parseFile(a,b,d):e?c&&
34117c():(e=!0,Network.fetchBinaryIncremental(f,g,c,394240));return 0};Network.fetchBinaryIncremental(f,g,c,65536)},marmoset="undefined"==typeof marmoset?{}:marmoset;marmoset.embed=embed;marmoset.fetchThumbnail=fetchThumbnail;function Fog(a,b){this.desc=b;this.gl=a;this.iblShader=a.shaderCache.fromURLs("fogvert.glsl","fogfrag.glsl",["#define FOG_IBL"]);var c=["#define FOG_DIR"];this.dirShader=a.shaderCache.fromURLs("fogvert.glsl","fogfrag.glsl",c);c.push("#define FOG_SHADOWS");this.dirShaderShadow=a.shaderCache.fromURLs("fogvert.glsl","fogfrag.glsl",c);c=["#define FOG_SPOT"];this.spotShader=a.shaderCache.fromURLs("fogvert.glsl","fogfrag.glsl",c);c.push("#define FOG_SHADOWS");this.spotShaderShadow=a.shaderCache.fromURLs("fogvert.glsl",
34118"fogfrag.glsl",c);c=["#define FOG_OMNI"];this.omniShaderShadow=this.omniShader=a.shaderCache.fromURLs("fogvert.glsl","fogfrag.glsl",c);this.fullscreenTriangle=a.createBuffer();a.bindBuffer(a.ARRAY_BUFFER,this.fullscreenTriangle);c=new Float32Array([0,0,2,0,0,2]);a.bufferData(a.ARRAY_BUFFER,c,a.STATIC_DRAW);a.bindBuffer(a.ARRAY_BUFFER,null)}
34119Fog.prototype.draw=function(a,b){var c=this.gl,d=a.view,e=d.projectionMatrix,f=Matrix.empty();Matrix.mul(f,d.viewMatrix,d.projectionMatrix);Matrix.invert(f,d.viewProjectionMatrix);f=[e[10]+e[11],-e[14],-2*e[11]];e=[-2/e[0],-2/e[5],(1-e[8])/e[0],(1-e[9])/e[5]];c.enable(c.BLEND);c.blendFunc(c.ONE,c.ONE_MINUS_SRC_ALPHA);for(var g=0;g<a.lights.count+1;++g){var h=g-1,k=h<a.lights.shadowCount,l;l=0==g?this.iblShader:0<a.lights.spot[3*h]?k?this.spotShaderShadow:this.spotShader:0<a.lights.getLightPos(h)[3]?
34120this.omniShader:k?this.dirShaderShadow:this.dirShader;l.bind();var n=l.params;c.uniform3fv(n.uDepthToZ,f);c.uniform4fv(n.uUnproject,e);c.uniformMatrix4fv(n.uInvViewMatrix,!1,d.transform);c.uniform1f(n.uFogInvDistance,1/this.desc.distance);c.uniform1f(n.uFogOpacity,this.desc.opacity*(1-a.stripData.activeFade()));c.uniform1f(n.uFogDispersion,1-this.desc.dispersion);var m=[0,0,0];m[this.desc.type]=1;c.uniform3fv(n.uFogType,m);c.uniform3fv(n.uFogColor,this.desc.color);c.uniform1f(n.uFogIllum,0==g?this.desc.skyIllum:
34121this.desc.lightIllum);c.uniformMatrix4fv(n.uLightMatrix,!1,a.lights.invMatrix);if(0==g){h=new Float32Array(a.sky.diffuseCoefficients);for(k=4;16>k;++k)h[k]*=1-this.desc.dispersion;for(k=16;36>k;++k)h[k]*=1-this.desc.dispersion*this.desc.dispersion;c.uniform4fv(n.uFogLightSphere,h)}else{var p=a.lights.getLightPos(h),p=Matrix.mul4(Vect.empty(),a.lights.invMatrix,p[0],p[1],p[2],p[3]),m=a.lights.getLightDir(h),m=Matrix.mulVec(Vect.empty(),a.lights.invMatrix,m[0],m[1],m[2]);c.uniform4fv(n.uLightPosition,
34122p);c.uniform3fv(n.uLightColor,a.lights.getColor(h));
34122var p=0.01745329251*a.lights.spot[3*h],r=Math.cos(0.5*p);c.uniform4fv(n.uSpotParams,[-m[0],-m[1],-m[2],0<p?r*r:0]);c.uniform4fv(n.uLightAttenuation,[a.lights.parameters[3*h+0],a.lights.parameters[3*h+1],a.lights.parameters[3*h+2],r]);k&&(k=Matrix.mul(Matrix.empty(),a.lights.finalTransformBuffer.subarray(16*h),a.lights.matrix),c.uniformMatrix4fv(n.uShadowProj,!1,k),a.shadow.depthTextures[h].bind(l.samplers.uShadowMap),h=0,1<a.postRender.sampleCount&&
34123(h=a.postRender.currentSample()/a.postRender.sampleCount),c.uniform1f(n.uDitherOffset,h),c.uniform3fv(n.uAABBMin,a.bounds.min),c.uniform3fv(n.uAABBMax,a.bounds.max),h=Vect.lerp(Vect.empty(),a.bounds.min,a.bounds.max,0.5),k=Vect.distance(h,a.bounds.min),c.uniform4f(n.uCylinder,h[0],h[1],h[2],k*k))}b.bind(l.samplers.tDepth);l=l.attribs.vCoord;c.bindBuffer(c.ARRAY_BUFFER,this.fullscreenTriangle);c.enableVertexAttribArray(l);c.vertexAttribPointer(l,2,c.FLOAT,!1,0,0);c.drawArrays(c.TRIANGLES,0,3);c.disableVertexAttribArray(l);
34124c.bindBuffer(c.ARRAY_BUFFER,null)}c.disable(c.BLEND)};Fog.prototype.complete=function(){return this.iblShader.complete()&&this.dirShader.complete()&&this.dirShaderShadow.complete()&&this.spotShader.complete()&&this.spotShaderShadow.complete()&&this.omniShader.complete()&&this.omniShaderShadow.complete()};function Framebuffer(a,b){this.gl=a;this.fbo=a.createFramebuffer();a.bindFramebuffer(a.FRAMEBUFFER,this.fbo);b&&(this.width=b.width,this.height=b.height,b.color0&&(this.color0=b.color0,a.framebufferTexture2D(a.FRAMEBUFFER,a.COLOR_ATTACHMENT0,a.TEXTURE_2D,this.color0.id,0),this.width=b.color0.desc.width,this.height=b.color0.desc.height),b.depth?(this.depth=b.depth,a.framebufferTexture2D(a.FRAMEBUFFER,a.DEPTH_ATTACHMENT,a.TEXTURE_2D,this.depth.id,0)):(this.depthBuffer=b.depthBuffer,b.createDepth&&!this.depthBuffer&&
34125(this.depthBuffer=Framebuffer.createDepthBuffer(a,this.width,this.height)),this.depthBuffer&&(a.bindRenderbuffer(a.RENDERBUFFER,this.depthBuffer),a.framebufferRenderbuffer(a.FRAMEBUFFER,a.DEPTH_ATTACHMENT,a.RENDERBUFFER,this.depthBuffer),a.bindRenderbuffer(a.RENDERBUFFER,null))));this.valid=b&&b.ignoreStatus||a.checkFramebufferStatus(a.FRAMEBUFFER)==a.FRAMEBUFFER_COMPLETE;a.bindFramebuffer(a.FRAMEBUFFER,null)}
34126Framebuffer.createDepthBuffer=function(a,b,c){var d=a.createRenderbuffer();a.bindRenderbuffer(a.RENDERBUFFER,d);a.renderbufferStorage(a.RENDERBUFFER,a.DEPTH_COMPONENT16,b,c);a.bindRenderbuffer(a.RENDERBUFFER,null);return d};Framebuffer.prototype.bind=function(){this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,this.fbo);this.gl.viewport(0,0,this.width,this.height)};Framebuffer.bindNone=function(a){a.bindFramebuffer(a.FRAMEBUFFER,null)};var FullScreen={support:function(){return!!(document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled)},begin:function(a,b){var c=a.requestFullscreen||a.webkitRequestFullScreen||a.mozRequestFullScreen||a.msRequestFullscreen;if(c){var d=function(){FullScreen.active()||(document.removeEventListener("fullscreenchange",d),document.removeEventListener("webkitfullscreenchange",d),document.removeEventListener("mozfullscreenchange",d),document.removeEventListener("MSFullscreenChange",
34127d));b&&b()};document.addEventListener("fullscreenchange",d);document.addEventListener("webkitfullscreenchange",d);document.addEventListener("mozfullscreenchange",d);document.addEventListener("MSFullscreenChange",d);c.bind(a)()}},end:function(){var a=document.exitFullscreen||document.webkitExitFullscreen||document.mozCancelFullScreen||document.msExitFullscreen;a&&a.bind(document)()},active:function(){return!!(document.fullscreenElement||document.webkitIsFullScreen||document.mozFullScreenElement||document.msFullscreenElement)}};function GUIManager(a){this.init=!1;this.ui=a;this.bottom=this.left=this.height=this.width=0;this.clicked=this.mouseDown=!1;this.playbackControls=0;a=1;window.devicePixelRatio&&(2<window.devicePixelRatio?a=4:1<window.devicePixelRatio&&(a=2));this.imageSetNumber=a}GUIManager.prototype.setSize=function(a,b){this.width=a;this.height=b;this.left=-a;this.bottom=-b;this.playbackControls&&this.playbackControls.resize(this)};
34128GUIManager.prototype.setupActiveView=function(a){this.init||(this.init=!0,this.ui=a,a.viewer.scene.sceneAnimator&&(this.playbackControls=new PlaybackControls(this),this.playbackControls.resize(this)))};
34129GUIManager.prototype.updateElement=function(a){var b=a.linkedControl;if(b){var c=this.left*(1-a.getScreenXPercent()),d=this.bottom*(1-a.getScreenYPercent()),e=this.width*a.getScreenWidthPercent();a=this.height*a.getScreenHeightPercent();b.style.left=c+"px";b.style.bottom=d+"px";b.style.width=e+"px";b.style.height=a+"px"}};function GUIRegion(a){this.debugString="GUIRegion";this.name="Default";this.controlRect=new ControlRect(a);this.yPercent=this.xPercent=0;this.heightPercent=this.widthPercent=1;this.guiScreen=a}
34130GUIRegion.prototype.addImageElement=function(a,b){var c=this.guiScreen.ui.menuCluster.contents,d=document.createElement("input");a.linkControl(d);this.guiScreen.updateElement(a);d.type="image";d.src=marmoset.dataLocale+b;d.style.position="absolute";d.style.border="none";d.style.outline="0px";d.style.zIndex="1";d.title=b;d.style.opacity=a.opacity;var e=new XMLHttpRequest;e.open("HEAD",d.src,!0);e.onload=function(a){a.appendChild(this)}.bind(d,c);e.send();return d};
34131GUIRegion.prototype.addImage=function(a){var b=new ControlRect(this.guiScreen);this.addImageElement(b,a);return b};
34132GUIRegion.prototype.addTextButton=function(a,b,c,d,e,f){var g=new Button(this.guiScreen);g.name="none";g.text=a;g.controlRect.set(b,c,d,e);g.controlRect.opacity=f;this.controlRect.registerChildControlRect(g.controlRect);b=this.guiScreen.ui.menuCluster.contents;c=document.createElement("text");c.style.color="white";c.style.fontFamily="Arial";c.style.fontSize=marmoset.largeUI?"14px":"12px";c.style.textShadow="2px 2px 3px #000000";b.appendChild(c);g.controlRect.linkControl(c);this.guiScreen.updateElement(g.controlRect);
34133c.type="text";c.name="text";c.style.position="absolute";c.style.border="none";c.style.outline="0px";c.style.zIndex="2";c.innerHTML=a;c.style.opacity=g.controlRect.opacity;g.linkControl(c);return g};function GUIScreen(a){this.init=!1;this.ui=a;this.bottom=this.left=this.height=this.width=0;this.clicked=this.mouseDown=!1;this.playbackControls=0;a=1;window.devicePixelRatio&&(2<window.devicePixelRatio?a=4:1<window.devicePixelRatio&&(a=2));this.imageSetNumber=a}GUIScreen.prototype.setSize=function(a,b){this.width=a;this.height=b;this.left=-a;this.bottom=-b;this.playbackControls&&this.playbackControls.resize(this)};
34134GUIScreen.prototype.setupActiveView=function(a){this.init||(this.init=!0,this.ui=a,a.viewer.scene.sceneAnimator&&(this.playbackControls=new PlaybackControls(this),this.playbackControls.resize(this)))};
34135GUIScreen.prototype.updateElement=function(a){var b=a.linkedControl;if(b){var c=this.left*(1-a.getScreenXPercent()),d=this.bottom*(1-a.getScreenYPercent()),e=this.width*a.getScreenWidthPercent();a=this.height*a.getScreenHeightPercent();b.style.left=c+"px";b.style.bottom=d+"px";b.style.width=e+"px";b.style.height=a+"px"}};function Input(a){this.onTap=[];this.onSingleTap=[];this.onDoubleTap=[];this.onDrag=[];this.onZoom=[];this.onPan=[];this.onPan2=[];this.onAnything=[];this.mouseDownCount=0;this.macHax=0<=navigator.platform.toUpperCase().indexOf("MAC");a&&this.attach(a)}
34136Input.prototype.attach=function(a){this.element=a;var b=function(a){for(var b=0;b<this.onAnything.length;++b)this.onAnything[b]();a.preventDefault()}.bind(this);this.mouseStates=[{pressed:!1,position:[0,0],downPosition:[0,0]},{pressed:!1,position:[0,0],downPosition:[0,0]},{pressed:!1,position:[0,0],downPosition:[0,0]}];this.lastTapPos=[0,0];
34136a=function(a){if(a.target===this.element){this.mouseDownCount++;var d=this.mouseStates[a.button];if(d){d.pressed=!0;var e=this.element.getBoundingClientRect();
34137d.position[0]=d.downPosition[0]=a.clientX-e.left;d.position[1]=d.downPosition[1]=a.clientY-e.top;b(a)}}}.bind(this);this.element.addEventListener("mousedown",a);a=function(a){var d=this.mouseStates[a.button];if(d){var e=this.element.getBoundingClientRect(),f=a.clientX-e.left,e=a.clientY-e.top;d.pressed=!1;d.position[0]=f;d.position[1]=e;if(0==a.button&&a.target==this.element&&10>Math.abs(d.position[0]-d.downPosition[0])+Math.abs(d.position[1]-d.downPosition[1])){for(var g=0;g<this.onTap.length;++g)this.onTap[g](f,
34138e);this.needSingleClick=!0;window.setTimeout(function(a,c){if(this.needSingleClick){for(var b=0;b<this.onSingleTap.length;++b)this.onSingleTap[b](a,c);this.needSingleClick=!1}}.bind(this,f,e),301);d=!1;if(void 0!==this.doubleClickTimer&&(g=8>Math.abs(f-this.lastTapPos[0])+Math.abs(e-this.lastTapPos[1]),300>Date.now()-this.doubleClickTimer&&g))for(d=!0,this.needSingleClick=!1,g=0;g<this.onDoubleTap.length;++g)this.onDoubleTap[g](f,e);this.doubleClickTimer=Date.now();d&&(this.doubleClickTimer=-1E9);
34139this.lastTapPos[0]=f;this.lastTapPos[1]=e}}b(a)}.bind(this);this.element.addEventListener("mouseup",a);a=function(a){for(var d=!1,e=this.element.getBoundingClientRect(),f=0;3>f;++f){var g=this.mouseStates[f];if(g.pressed){var d=a.clientX-e.left,h=a.clientY-e.top,k=d-g.position[0],l=h-g.position[1];g.position[0]=d;g.position[1]=h;if(2==f&&a.altKey)for(g=0;g<this.onZoom.length;++g)this.onZoom[g](2*l);else if(1<=f||a.ctrlKey)for(g=0;g<this.onPan.length;++g)this.onPan[g](k,l);else if(0==f)if(a.shiftKey)for(g=
341400;g<this.onPan2.length;++g)this.onPan2[g](k,l);else for(g=0;g<this.onDrag.length;++g)this.onDrag[g](d,h,k,l);d=!0}}d&&b(a)}.bind(this);this.element.addEventListener("mousemove",a);a=function(a){var d=0;a.deltaY?(d=-0.4*a.deltaY,1==a.deltaMode?d*=16:2==a.deltaMode&&(d*=this.element.clientHeight)):a.wheelDelta?d=this.macHax&&120==Math.abs(a.wheelDelta)?0.08*a.wheelDelta:0.4*a.wheelDelta:a.detail&&(d=-10*a.detail);for(var e=0;e<this.onZoom.length;++e)this.onZoom[e](d);b(a)}.bind(this);this.element.addEventListener("mousewheel",
34141a);this.element.addEventListener("DOMMouseScroll",a);this.element.addEventListener("wheel",a);a=function(a){for(var b=0;b<this.mouseStates.length;++b)this.mouseStates[b].pressed=!1;a.preventDefault()}.bind(this);this.element.addEventListener("mouseleave",a);this.element.addEventListener("contextmenu",function(a){a.preventDefault()});this.touches={};this.tapPossible=!1;this.touchCountFloor=0;a=function(a){for(var d=this.element.getBoundingClientRect(),e=!1,f=0;f<a.changedTouches.length;++f)if(a.target===
34142this.element){var g=a.changedTouches[f],e={x:g.clientX-d.left,y:g.clientY-d.top};e.startX=e.x;e.startY=e.y;this.touches[g.identifier]=e;e=!0}this.tapPossible=1==a.touches.length;for(g=d=0;g<this.touches.length;++g)d++;d>this.touchCountFloor&&(this.touchCountFloor=d);e&&b(a)}.bind(this);this.element.addEventListener("touchstart",a);a=function(a){for(var d=!1,e=0;e<a.changedTouches.length;++e){var f=a.changedTouches[e],g=this.touches[f.identifier];if(g){if(this.tapPossible){var h=this.element.getBoundingClientRect(),
34143d=f.clientX-h.left,h=f.clientY-h.top;if(24>Math.max(Math.abs(d-g.startX),Math.abs(h-g.startY))){for(e=0;e<this.onTap.length;++e)this.onTap[e](d,h);this.needSingleTap=!0;window.setTimeout(function(a,b){if(this.needSingleTap){for(var c=0;c<this.onSingleTap.length;++c)this.onSingleTap[c](a,b);this.needSingleTap=!1}}.bind(this,d,h),501);g=!1;if(void 0!==this.doubleTapTimer){var k=24>Math.max(Math.abs(d-this.lastTapPos[0]),Math.abs(h-this.lastTapPos[1])),l=500>Date.now()-this.doubleTapTimer;if(k&&l)for(g=
34144!0,e=0;e<this.onDoubleTap.length;++e)this.onDoubleTap[e](d,h)}this.doubleTapTimer=Date.now();g&&(this.doubleTapTimer=-1E9);this.lastTapPos[0]=d;this.lastTapPos[1]=h}this.tapPossible=!1}delete this.touches[f.identifier];d=!0}}for(f=e=0;f<this.touches.length;++f)e++;0>=e&&(this.touchCountFloor=0);d&&b(a)}.bind(this);this.element.addEventListener("touchend",a);this.element.addEventListener("touchcancel",a);this.element.addEventListener("touchleave",a);a=function(a){for(var d=[],e=0;e<a.touches.length;++e)a.touches[e].target===
34145this.element&&d.push(a.touches[e]);var f=this.element.getBoundingClientRect();if(1==d.length&&1>=this.touchCountFloor){var g=d[0],h=this.touches[g.identifier];if(h){var k=g.clientX-f.left,g=g.clientY-f.top,f=k-h.x,l=g-h.y;h.x=k;h.y=g;for(e=0;e<this.onDrag.length;++e)this.onDrag[e](k,g,f,l,a.shiftKey)}}else if(2==d.length&&2>=this.touchCountFloor){if(l=d[0],e=this.touches[l.identifier],g=d[1],h=this.touches[g.identifier],e&&h){var k=l.clientX-f.left,l=l.clientY-f.top,n=g.clientX-f.left,m=g.clientY-
34146f.top,p=Math.sqrt((k-n)*(k-n)+(l-m)*(l-m)),r=Math.sqrt((e.x-h.x)*(e.x-h.x)+(e.y-h.y)*(e.y-h.y)),s=Math.abs(p-r),f=(k-e.x+n-h.x)/2,g=(l-e.y+m-h.y)/2,u=Math.sqrt(f*f+g*g);e.x=k;e.y=l;h.x=n;h.y=m;if(0<s)for(h=s/(s+u),e=0;e<this.onZoom.length;++e)this.onZoom[e](2*(p-r)*h);if(0<u)for(h=u/(s+u),e=0;e<this.onDrag.length;++e)this.onPan[e](f*h,g*h)}}
34146else if(3<=d.length){for(e=r=p=n=l=0;e<d.length;++e)g=d[e],h=this.touches[g.identifier],k=g.clientX-f.left,g=g.clientY-f.top,p+=k,r+=g,h&&(l+=h.x,n+=h.y,h.x=k,
34147h.y=g);l/=d.length;n/=d.length;p/=d.length;r/=d.length;for(e=0;e<this.onPan2.length;++e)this.onPan2[e](p-l,r-n)}0<d.length&&b(a)}.bind(this);this.element.addEventListener("touchmove",a)};function KeyFrame(a,b){a&&b?(this.frameIndex=b.frameIndex,this.value=b.value,this.interpolation=b.interpolation,this.weighIn=b.weighIn,this.weighOut=b.weighOut):(this.interpolation=this.value=this.frameIndex=0,this.weighOut=this.weighIn=1)};function Lights(a,b){this.rotation=this.shadowCount=this.count=0;this.positions=[];this.directions=[];this.matrixWeights=[];this.matrix=Matrix.identity();this.invMatrix=Matrix.identity();this.defaultmatrix=Matrix.identity();this.defaultviewmatrix=Matrix.identity();for(var c in a)this[c]=a[c];this.count=this.positions.length/4;this.count=Math.min(6,this.count);this.shadowCount=Math.min(3,this.shadowCount);this.positions=new Float32Array(this.positions);this.positionBuffer=new Float32Array(this.positions);
34148this.directions=new Float32Array(this.directions);this.directionBuffer=new Float32Array(this.directions);this.colors=new Float32Array(this.colors);this.colorsBuffer=new Float32Array(this.colors);this.modelViewBuffer=new Float32Array(16*this.shadowCount);this.projectionBuffer=new Float32Array(16*this.shadowCount);this.finalTransformBuffer=new Float32Array(16*this.shadowCount);this.inverseTransformBuffer=new Float32Array(16*this.shadowCount);this.shadowTexelPadProjections=new Float32Array(4*this.shadowCount);
34149this.shadowsNeedUpdate=new Uint8Array(this.shadowCount);for(var d=0;d<this.shadowsNeedUpdate.length;++d)this.shadowsNeedUpdate[d]=1;Matrix.rotation(this.matrix,this.rotation,1);Matrix.transpose(this.invMatrix,this.matrix);Matrix.copy(this.defaultmatrix,this.matrix);Matrix.copy(this.defaultviewmatrix,b.viewMatrix);for(d=0;d<this.count;++d){c=this.positions.subarray(4*d,4*d+4);var e=this.directions.subarray(3*d,3*d+3);1==this.matrixWeights[d]?(Matrix.mul4(c,this.matrix,c[0],c[1],c[2],c[3]),Matrix.mulVec(e,
34150this.matrix,e[0],e[1],e[2])):2==this.matrixWeights[d]&&(Matrix.mul4(c,b.viewMatrix,c[0],c[1],c[2],c[3]),Matrix.mulVec(e,b.viewMatrix,e[0],e[1],e[2]))}}Lights.prototype.getLightPos=function(a){return this.positionBuffer.subarray(4*a,4*a+4)};Lights.prototype.setLightDistance=function(a,b){0>=b&&(b=1E-5);this.parameters[3*a+2]=1/b};Lights.prototype.setLightSpotAngle=function(a,b){0>=b&&(b=1E-6);this.spot[3*a]=b;var c=Math.sin(3.1415926/180*b/2);this.spot[3*a+2]=1/(c*c)*this.spot[3*a+1]};
34151Lights.prototype.setLightSpotSharpness=function(a,b){this.spot[3*a+1]=b;this.setLightSpotAngle(this.spot[3*a])};Lights.prototype.setLightPos=function(a,b){this.positions[4*a+0]=b[0];this.positions[4*a+1]=b[1];this.positions[4*a+2]=b[2];var c=this.positions.subarray(4*a,4*a+4);1==this.matrixWeights[a]?Matrix.mul4(c,this.defaultmatrix,b[0],b[1],b[2],c[3]):2==this.matrixWeights[a]&&Matrix.mul4(c,this.defaultviewmatrix,b[0],b[1],b[2],c[3])};
34152Lights.prototype.setLightDir=function(a,b){this.directions[3*a+0]=b[0];this.directions[3*a+1]=b[1];this.directions[3*a+2]=b[2];var c=this.directions.subarray(3*a,3*a+3);1==this.matrixWeights[a]?Matrix.mulVec(c,this.defaultmatrix,b[0],b[1],b[2]):2==this.matrixWeights[a]&&Matrix.mulVec(c,this.defaultviewmatrix,b[0],b[1],b[2])};Lights.prototype.getLightColor=function(a){return this.colors.subarray(3*a,3*a+3)};
34153Lights.prototype.setLightColor=function(a,b){this.colors[3*a+0]=b[0];this.colors[3*a+1]=b[1];this.colors[3*a+2]=b[2]};Lights.prototype.getLightDir=function(a){return this.directionBuffer.subarray(3*a,3*a+3)};Lights.prototype.getColor=function(a){a*=3;return[this.colors[a],this.colors[a+1],this.colors[a+2]]};
34154Lights.prototype.update=function(a,b){var c=new Matrix.type(this.matrix);Matrix.rotation(this.matrix,this.rotation,1);Matrix.transpose(this.invMatrix,this.matrix);for(var d=0;d<this.count;++d){var e=this.positions.subarray(4*d,4*d+4),f=this.directions.subarray(3*d,3*d+3),g=this.getLightPos(d),h=this.getLightDir(d);1==this.matrixWeights[d]?(g[0]=e[0],g[1]=e[1],g[2]=e[2],g[3]=e[3],h[0]=f[0],h[1]=f[1],h[2]=f[2]):2==this.matrixWeights[d]?(Matrix.mul4(g,a.transform,e[0],e[1],e[2],e[3]),Matrix.mulVec(h,
34155a.transform,f[0],f[1],f[2]),Matrix.mul4(g,this.matrix,g[0],g[1],g[2],g[3]),Matrix.mulVec(h,this.matrix,h[0],h[1],h[2])):(Matrix.mul4(g,this.matrix,e[0],e[1],e[2],e[3]),Matrix.mulVec(h,this.matrix,f[0],f[1],f[2]));Vect.normalize(h,h)}for(var f=new Float32Array(this.finalTransformBuffer),g=Matrix.empty(),h=Matrix.empty(),k=Matrix.empty(),l=Vect.empty(),n=Vect.empty(),m=Vect.empty(),p=Vect.empty(),e=Vect.empty(),r=[],s=[],u=Matrix.create(0.5,0,0,0.5,0,0.5,0,0.5,0,0,0.5,0.5,0,0,0,1),d=0;d<this.count;++d){l=
34156this.getLightPos(d);n=this.getLightDir(d);0.99<Math.abs(n[1])?Vect.set(m,1,0,0):Vect.set(m,0,1,0);Vect.cross(p,m,n);Vect.normalize(p,p);Vect.cross(m,n,p);Vect.normalize(m,m);Matrix.set(g,p[0],p[1],p[2],-Vect.dot(p,l),m[0],m[1],m[2],-Vect.dot(m,l),n[0],n[1],n[2],-Vect.dot(n,l),0,0,0,1);for(l=0;8>l;++l)e[0]=l&1?b.max[0]:b.min[0],e[1]=l&2?b.max[1]:b.min[1],e[2]=l&4?b.max[2]:b.min[2],Matrix.mulPoint(e,this.matrix,1.005*e[0],1.005*e[1],1.005*e[2]),Matrix.mulPoint(e,g,e[0],e[1],e[2]),0==l?(r[0]=s[0]=e[0],
34157r[1]=s[1]=e[1],r[2]=s[2]=e[2]):(r[0]=Math.min(r[0],e[0]),r[1]=Math.min(r[1],e[1]),r[2]=Math.min(r[2],e[2]),s[0]=Math.max(s[0],e[0]),s[1]=Math.max(s[1],e[1]),s[2]=Math.max(s[2],e[2]));var l=-r[2],n=-s[2],q=this.spot[3*d];0<q?(l=Math.min(l,1/this.parameters[3*d+2]),n=Math.max(0.04*l,n),Matrix.perspective(h,q,1,n,l),d<this.shadowCount&&(l=2*-Math.tan(0.00872664625*q),this.shadowTexelPadProjections[4*d+0]=this.modelViewBuffer[16*d+2]*l,this.shadowTexelPadProjections[4*d+1]=this.modelViewBuffer[16*d+6]*
34158l,this.shadowTexelPadProjections[4*d+2]=this.modelViewBuffer[16*d+10]*l,this.shadowTexelPadProjections[4*d+3]=this.modelViewBuffer[16*d+14]*l)):(Matrix.ortho(h,r[0],s[0],r[1],s[1],n,l),d<this.shadowCount&&(this.shadowTexelPadProjections[4*d+0]=this.shadowTexelPadProjections[4*d+1]=this.shadowTexelPadProjections[4*d+2]=0,this.shadowTexelPadProjections[4*d+3]=Math.max(s[0]-r[0],s[1]-r[1])));Matrix.mul(k,h,g);
34158Matrix.mul(k,u,k);Matrix.copyToBuffer(this.modelViewBuffer,16*d,g);Matrix.copyToBuffer(this.projectionBuffer,
3415916*d,h);Matrix.copyToBuffer(this.finalTransformBuffer,16*d,k);Matrix.invert(k,k);Matrix.copyToBuffer(this.inverseTransformBuffer,16*d,k)}e=!1;for(d=0;d<c.length;++d)if(c[d]!=this.matrix[d]){e=!0;break}for(d=0;d<this.shadowCount;d++)if(e&&1==this.matrixWeights[d])this.shadowsNeedUpdate[d]=1;else for(c=16*d;c<16*d+16;++c)if(f[c]!=this.finalTransformBuffer[c]){this.shadowsNeedUpdate[d]=1;break}};
34160Lights.prototype.flagUpdateAnimatedLighting=function(){for(var a=0;a<this.shadowCount;a++)this.shadowsNeedUpdate[a]=1};function ListBox(a){this.name="none";this.text="default text";this.title="none";this.debugString=this.imagePath="";this.controlRect=new ControlRect(a);this.textEntries=[];this.textOffsetsX=[];this.textOffsetsY=[];this.buttons=[];this.listBoxEntryHeight=20;this.selectedItemText="";this.selectedIndex=-1;this.localPixelsY=0;this.localPixelsX=100;this.labelPixelDrop=0;this.labelPixelInset=10;this.labelTextHeight=16;this.closed=!1;this.defaultButtonText=this.spacerMiddle=this.spacerRight=this.spacerLeft=
34161this.spacerControl=0;this.listBoxButtons=[];this.listBoxRegion=new GUIRegion(a);this.guiScreen=a;this.lastMouseOverIndex=-1;this.selectionChangedCallback=0;this.debugString=""}ListBox.prototype.linkControl=function(a){this.controlRect.linkControl(a)};
34162ListBox.prototype.spawnControl=function(a,b,c,d,e,f){var g=this.guiScreen.imageSetNumber,h="backgroundTopLE"+g+"x.png",k="backgroundTopM"+g+"x.png",l="backgroundTopRE"+g+"x.png",n="backgroundMiddleLE"+g+"x.png",m="backgroundMiddleM"+g+"x.png",p="backgroundMiddleRE"+g+"x.png",r="backgroundBottomLE"+g+"x.png",s="backgroundBottomM"+g+"x.png",u="backgroundBottomRE"+g+"x.png",q=3*g,x="backgroundLE"+g+"x.png",w="backgroundM"+g+"x.png",v="backgroundRE"+g+"x.png",t=2*g,y="spacerLE"+g+"x.png",E="spacerM"+
34163g+"x.png",F="spacerRE"+g+"x.png",g=2*g,A=this.controlRect.guiScreen.width,B=this.controlRect.guiScreen.height;if(e){e=this.textEntries.length;var z=c;for(c=0;c<e;c++){var C=8*(this.textEntries[c]?this.textEntries[c].length:0);z<C&&(z=C)}c=z+f}e=this.textEntries.length+1;f=1/e;this.localPixelsX=c;this.listBoxEntryHeight=d;this.localPixelsY=(this.textEntries.length+1)*this.listBoxEntryHeight;c=8/this.localPixelsY;d=6/this.localPixelsX;z=4/this.localPixelsX;C=f-c/4;this.labelTextHeight=marmoset.largeUI?
3416420:16;this.labelPixelDrop=(this.listBoxEntryHeight-this.labelTextHeight)/2;this.listBoxRegion.controlRect.widthPercent=this.localPixelsX/A;this.listBoxRegion.controlRect.heightPercent=this.localPixelsY/B;this.listBoxRegion.controlRect.xPercent=a/A;this.listBoxRegion.controlRect.yPercent=b/B;this.openBackground=this.listBoxRegion.addTextButton("",0,0,1,1+c,1);this.openBackground.setBackground3x3(this.listBoxRegion,0,0,h,k,l,n,m,p,r,s,u,q,q);this.closedBackground=this.listBoxRegion.addTextButton("",
341650,0,1,f,1);this.closedBackground.setBackground3x1(this.listBoxRegion,0,0,x,w,v,t);a=this.labelPixelInset+this.textOffsetsX[0];b=this.labelPixelDrop+this.textOffsetsY[0];b/=this.localPixelsY;a/=this.localPixelsX;this.defaultButton=this.listBoxRegion.addTextButton("Selected",a,-b,1,f,0.5);this.selectedIndex=0;this.defaultButton.controlRect.linkedControl.innerHTML=this.textEntries[this.selectedIndex];this.defaultButton.linkedBackground=this.closedBackground;this.spacerControl=this.listBoxRegion.addTextButton("",
34166d,C,1-(d+z),c,1);this.spacerControl.defaultAlpha=1;this.spacerControl.setBackground3x1(this.listBoxRegion,0,0,y,E,F,g);this.spacerControl.setVisible(!1);this.spacerControl.linkedBackground=this.openBackground;for(c=1;c<e;c++)a=this.labelPixelInset+this.textOffsetsX[c-1],b=this.labelPixelDrop+this.textOffsetsY[c-1]-4,a/=this.localPixelsX,b/=this.localPixelsY,y=this.listBoxRegion.addTextButton(this.textEntries[c-1],a,f*c-b,1-a,f,0.5),this.listBoxButtons.push(y),y.linkedBackground=this.openBackground;
34167this.showList(!1);this.setupCallbacks()};
34168ListBox.prototype.setControl=function(a,b,c,d,e,f){var g=this.controlRect.guiScreen.width,h=this.controlRect.guiScreen.height;if(e){e=this.textEntries.length;for(var k=0;k<e;k++){var l=8*(this.textEntries[k]?this.textEntries[k].length:0);c<l&&(c=l)}c+=f}this.localPixelsX=c;this.listBoxEntryHeight=d;this.localPixelsY=(this.textEntries.length+1)*this.listBoxEntryHeight;this.listBoxRegion.controlRect.widthPercent=this.localPixelsX/g;this.listBoxRegion.controlRect.heightPercent=this.localPixelsY/h;this.listBoxRegion.controlRect.xPercent=
34169a/g;this.listBoxRegion.controlRect.yPercent=b/h;this.listBoxRegion.controlRect.updateChildElements();
34169this.spacerControl.alignBackground();this.openBackground.alignBackground();this.closedBackground.alignBackground()};ListBox.prototype.addItem=function(a,b,c){this.textEntries.push(a);this.textOffsetsX.push(b);this.textOffsetsY.push(c)};
34170ListBox.prototype.showList=function(a){for(var b=this.listBoxButtons.length,c=0;c<b;c++)this.listBoxButtons[c].setVisible(a);this.closed=!a;this.spacerControl&&this.spacerControl.setVisible(a);this.openBackground&&this.openBackground.setVisible(a);this.closedBackground&&this.closedBackground.setVisible(!a);a?(this.defaultButton.linkedBackground=this.openBackground,this.openBackground.setOpacity(1),this.closedBackground.setOpacity(0.5)):this.defaultButton.linkedBackground=this.closedBackground};
34171ListBox.prototype.selectItem=function(a){this.selectedItemText=this.textEntries[a];this.selectedIndex=a;this.defaultButton.controlRect.linkedControl.innerHTML=this.textEntries[this.selectedIndex];a=(this.labelTextHeight-this.listBoxEntryHeight+3)/this.localPixelsY;this.defaultButton.controlRect.xPercent=(this.labelPixelInset+this.textOffsetsX[this.selectedIndex])/this.localPixelsX;this.defaultButton.controlRect.yPercent=a;this.defaultButton.controlRect.updateElement()};
34172ListBox.prototype.setupCallbacks=function(){var a=function(a){if(this.closed){var b=this.closedBackground.controlRect.linkedControl,b=b.getBoundingClientRect(),c=a.clientX-b.left;a=a.clientY-b.top;c/=b.width;b=a/b.height;0<=c&&1>=c&&0<=b&&1>=b?this.closedBackground.setOpacity(1):this.closedBackground.setOpacity(0.5)}else b=this.openBackground.controlRect.linkedControl,b=b.getBoundingClientRect(),c=a.clientX-b.left,a=a.clientY-b.top,c/=b.width,b=a/b.height,0<=c&&1>=c&&0<=b&&1>=b?this.openBackground.setOpacity(1):
34173this.openBackground.setOpacity(0.5)}.bind(this);this.defaultButton.controlRect.linkedControl.onclick=function(){this.closed?this.showList(!0):(this.showList(this.closed),this.closedBackground.setOpacity(1),this.defaultButton.setOpacity(1))}.bind(this);for(var b=function(a){this.selectItem(a.id);this.showList(!1);this.defaultButton.setOpacity(0.5);this.selectionChangedCallback&&this.selectionChangedCallback(this)}.bind(this),c=function(a){a=this.listBoxButtons.length;for(var b=0;b<a;b++)this.listBoxButtons[b].controlRect.mouseOver&&
34174(this.selectItem(b),b=a,this.selectionChangedCallback&&this.selectionChangedCallback(this));this.showList(!1)}.bind(this),d=this.listBoxButtons.length,e=0;e<d;e++)this.listBoxButtons[e].controlRect.callBack=b,this.listBoxButtons[e].controlRect.id=e,this.listBoxButtons[e].controlRect.linkedControl.addEventListener("mousemove",a);this.guiScreen.ui.viewer.input.element.addEventListener("mousemove",a);this.openBackground.controlRect.linkedControl.addEventListener("mousemove",a);this.closedBackground.controlRect.linkedControl.addEventListener("mousemove",
34175a);this.guiScreen.ui.viewer.input.element.addEventListener("mousedown",c)};function Material(a,b,c){this.gl=a;this.name=c.name;var d={mipmap:!0,aniso:a.hints.mobile?0:4,clamp:!!c.textureWrapClamp,mirror:!!c.textureWrapMirror},e={mipmap:d.mipmap,clamp:d.clamp,mirror:d.mirror,nofilter:c.textureFilterNearest||!1};e.nofilter||(e.aniso=a.hints.mobile?2:4);this.textures={albedo:a.textureCache.fromFilesMergeAlpha(b.get(c.albedoTex),b.get(c.alphaTex),e),reflectivity:a.textureCache.fromFilesMergeAlpha(b.get(c.reflectivityTex),b.get(c.glossTex),d),normal:a.textureCache.fromFile(b.get(c.normalTex),
34176d),extras:a.textureCache.fromFilesMergeAlpha(b.get(c.extrasTex),b.get(c.extrasTexA),d)};this.extrasTexCoordRanges={};if(c.extrasTexCoordRanges)for(var f in c.extrasTexCoordRanges)this.extrasTexCoordRanges[f]=new Float32Array(c.extrasTexCoordRanges[f].scaleBias);this.textures.extras||(b=new Texture(a,{width:1,height:1}),b.loadArray(new Uint8Array([255,255,255,255])),this.textures.extras=b);var g=c.blendTint||[1,1,1];b={none:function(){a.disable(a.BLEND)},alpha:function(){a.enable(a.BLEND);a.blendFuncSeparate(a.SRC_ALPHA,
34177a.ONE_MINUS_SRC_ALPHA,a.ONE_MINUS_DST_ALPHA,a.ONE)},add:function(){a.enable(a.BLEND);a.blendColor(g[0],g[1],g[2],1);a.blendFunc(a.ONE,a.CONSTANT_COLOR)}};this.blend=b[c.blend]||b.none;this.alphaTest=c.alphaTest||0;this.usesBlending=this.blend!==b.none;this.usesRefraction=!!c.refraction;this.shadowAlphaTest=this.alphaTest;0>=this.shadowAlphaTest&&this.blend===b.alpha&&(this.shadowAlphaTest=0.5);this.castShadows=this.blend!==b.add;this.horizonOcclude=c.horizonOcclude||0;this.fresnel=new Float32Array(c.fresnel?
34178c.fresnel:[1,1,1]);this.emissiveIntensity=c.emissiveIntensity||1;d=[];e=!1;0<c.lightCount&&d.push("#define LIGHT_COUNT "+c.lightCount);0<c.shadowCount&&(f=Math.min(c.lightCount,c.shadowCount),this.usesRefraction&&8>=a.limits.textureCount&&(f=2<f?2:f),d.push("#define SHADOW_COUNT "+f));0<c.alphaTest&&d.push("#define ALPHA_TEST");this.blend===b.alpha?d.push("#define TRANSPARENCY_DITHER"):this.blend===b.none&&d.push("#define NOBLEND");a.hints.mobile&&d.push("#define MOBILE");a.ext.textureDepth&&d.push("#define SHADOW_NATIVE_DEPTH");
34179f=function(a){return 1/(2/3*3.1415962*(a*a+a+1))};c.useSkin&&(d.push("#define SKIN"),this.skinParams=c.skinParams||{subdermisColor:[1,1,1],transColor:[1,0,0,1],fresnelColor:[0.2,0.2,0.2,0.5],fresnelOcc:1,fresnelGlossMask:1,transSky:0.5,shadowBlur:0.5,normalSmooth:0.5,transScatter:0,transDepth:0,millimeterScale:1},this.extrasTexCoordRanges.subdermisTex||d.push("#define SKIN_NO_SUBDERMIS_TEX"),this.extrasTexCoordRanges.translucencyTex||d.push("#define SKIN_NO_TRANSLUCENCY_TEX"),this.extrasTexCoordRanges.fuzzTex||
34180d.push("#define SKIN_NO_FUZZ_TEX"),void 0===this.skinParams.version&&(this.skinParams.version=1),2==this.skinParams.version?(d.push("#define SKIN_VERSION_2"),this.skinParams.shadowBlur*=4,this.skinParams.shadowBlur=Math.min(this.skinParams.shadowBlur,40),this.skinParams.transIntegral=f(0.5*this.skinParams.transScatter),this.skinParams.fresnelIntegral=1/3.14159*(1-0.5*this.skinParams.fresnelColor[3]),this.skinParams.transSky=0):(d.push("#define SKIN_VERSION_1"),this.skinParams.shadowBlur=8*Math.min(this.skinParams.shadowBlur,
341811),this.skinParams.transDepth=0,this.skinParams.transScatter=this.skinParams.transColor[3],this.skinParams.transIntegral=1/3.14159*(1-0.5*this.skinParams.transScatter),this.skinParams.fresnelIntegral=1/3.14159*(1-0.5*this.skinParams.fresnelColor[3]),this.skinParams.transSky*=1.25,this.skinParams.transIntegral*=1.25));c.aniso&&(d.push("#define ANISO"),this.anisoParams=c.anisoParams||{strength:1,tangent:[1,0,0],integral:0.5},this.extrasTexCoordRanges.anisoTex||d.push("#define ANISO_NO_DIR_TEX"));c.microfiber&&
34182(d.push("#define MICROFIBER"),this.microfiberParams=c.microfiberParams||{fresnelColor:[0.2,0.2,0.2,0.5],fresnelOcc:1,fresnelGlossMask:1},this.microfiberParams.fresnelIntegral=1/3.14159*(1-0.5*this.microfiberParams.fresnelColor[3]),this.extrasTexCoordRanges.fuzzTex||d.push("#define MICROFIBER_NO_FUZZ_TEX"));c.refraction&&(d.push("#define REFRACTION"),this.refractionParams=c.refractionParams||{distantBackgroun
34182d:!1,tint:[1,1,1],useAlbedoTint:!1,IOR:1.5},this.extrasTexCoordRanges.refractionMaskTex||d.push("#define REFRACTION_NO_MASK_TEX"));
34183c.vertexColor&&(d.push("#define VERTEX_COLOR"),c.vertexColorsRGB&&d.push("#define VERTEX_COLOR_SRGB"),c.vertexColorAlpha&&d.push("#define VERTEX_COLOR_ALPHA"));this.horizonSmoothing=c.horizonSmoothing||0;0<this.horizonSmoothing&&d.push("#define HORIZON_SMOOTHING");c.unlitDiffuse&&d.push("#define DIFFUSE_UNLIT");this.extrasTexCoordRanges.emissiveTex&&(d.push("#define EMISSIVE"),c.emissiveSecondaryUV&&(d.push("#define EMISSIVE_SECONDARY_UV"),e=!0));this.extrasTexCoordRanges.aoTex&&(d.push("#define AMBIENT_OCCLUSION"),
34184c.aoSecondaryUV&&(d.push("#define AMBIENT_OCCLUSION_SECONDARY_UV"),e=!0));c.tangentOrthogonalize&&d.push("#define TSPACE_ORTHOGONALIZE");c.tangentNormalize&&d.push("#define TSPACE_RENORMALIZE");c.tangentGenerateBitangent&&d.push("#define TSPACE_COMPUTE_BITANGENT");e&&d.push("#define TEXCOORD_SECONDARY");this.vOffset=this.uOffset=0;d.push("#define UV_OFFSET ");this.shader=a.shaderCache.fromURLs("matvert.glsl","matfrag.glsl",d);d.push("#define STRIPVIEW");this.stripShader=a.shaderCache.fromURLs("matvert.glsl",
34185"matfrag.glsl",d);this.wireShader=a.shaderCache.fromURLs("wirevert.glsl","wirefrag.glsl");this.blend===b.alpha&&(this.prepassShader=a.shaderCache.fromURLs("alphaprepassvert.glsl","alphaprepassfrag.glsl"))}
34186Material.prototype.bind=function(a,b){if(!this.complete())return!1;var c=a.view,d=a.lights,e=a.sky,f=a.shadow,g=a.stripData.active()?this.stripShader:this.shader,h=this.skinParams,k=this.anisoParams,l=this.microfiberParams,n,m=this.gl,p=g.params,r=this.textures,s=g.samplers;g.bind();this.blend();var u=b.mesh.displayMatrix,q=Matrix.mul(Matrix.empty(),c.viewMatrix,u),x=Matrix.mul(Matrix.empty(),c.projectionMatrix,c.viewMatrix),q=Matrix.mul(Matrix.empty(),c.projectionMatrix,q),u=Matrix.mul(Matrix.empty(),
34187d.matrix,u);m.uniformMatrix4fv(p.uModelViewProjectionMatrix,!1,q);m.uniformMatrix4fv(p.uSkyMatrix,!1,u);u=Matrix.mulPoint(Vect.empty(),d.matrix,c.transform[12],c.transform[13],c.transform[14]);m.uniform3f(p.uCameraPosition,u[0],u[1],u[2]);m.uniform3fv(p.uFresnel,this.fresnel);m.uniform1f(p.uAlphaTest,this.alphaTest);m.uniform1f(p.uHorizonOcclude,this.horizonOcclude);m.uniform1f(p.uHorizonSmoothing,this.horizonSmoothing);m.uniform4fv(p.uDiffuseCoefficients,e.diffuseCoefficients);0<d.count&&(m.uniform4fv(p.uLightPositions,
34188d.positionBuffer),m.uniform3fv(p.uLightDirections,d.directionBuffer),m.uniform3fv(p.uLightColors,d.colors),m.uniform3fv(p.uLightParams,d.parameters),m.uniform3fv(p.uLightSpot,d.spot),u=0.392699*a.postRender.currentSample(),m.uniform2f(p.uShadowKernelRotation,0.5*Math.cos(u),0.5*Math.sin(u)),0<d.shadowCount&&(u=f.depthTextures[0].desc.width,m.uniform2f(p.uShadowMapSize,u,1/u),m.uniformMatrix4fv(p.uShadowMatrices,!1,d.finalTransformBuffer),m.uniformMatrix4fv(p.uInvShadowMatrices,!1,d.inverseTransformBuffer),
34189m.uniform4fv(p.uShadowTexelPadProjections,d.shadowTexelPadProjections),f.bindDepthTexture(s.tDepth0,0),f.bindDepthTexture(s.tDepth1,1),f.bindDepthTexture(s.tDepth2,2)));h&&(m.uniform3fv(p.uSubdermisColor,h.subdermisColor),m.uniform4fv(p.uTransColor,h.transColor),m.uniform1f(p.uTransScatter,h.transScatter),m.uniform4fv(p.uFresnelColor,h.fresnelColor),m.uniform1f(p.uFresnelOcc,h.fresnelOcc),m.uniform1f(p.uFresnelGlossMask,h.fresnelGlossMask),m.uniform1f(p.uFresnelIntegral,h.fresnelIntegral),m.uniform1f(p.uTransIntegral,
34190h.transIntegral),m.uniform1f(p.uSkinTransDepth,h.transDepth),m.uniform1f(p.uTransSky,h.transSky),m.uniform1f(p.uSkinShadowBlur,h.shadowBlur),m.uniform1f(p.uNormalSmooth,h.normalSmooth),(n=this.extrasTexCoordRanges.subdermisTex)&&m.uniform4fv(p.uTexRangeSubdermis,n),(n=this.extrasTexCoordRanges.translucencyTex)&&m.uniform4fv(p.uTexRangeTranslucency,n),(n=this.extrasTexCoordRanges.fuzzTex)&&m.uniform4fv(p.uTexRangeFuzz,n));l&&(m.uniform4fv(p.uFresnelColor,l.fresnelColor),m.uniform1f(p.uFresnelOcc,l.fresnelOcc),
34191m.uniform1f(p.uFresnelGlossMask,l.fresnelGlossMask),m.uniform1f(p.uFresnelIntegral,l.fresnelIntegral),(n=this.extrasTexCoordRanges.fuzzTex)&&m.uniform4fv(p.uTexRangeFuzz,n));k&&(m.uniform3fv(p.uAnisoTangent,k.tangent),m.uniform1f(p.uAnisoStrength,k.strength),m.uniform1f(p.uAnisoIntegral,k.integral),(n=this.extrasTexCoordRanges.anisoTex)&&m.uniform4fv(p.uTexRangeAniso,n));this.usesRefraction&&(a.refractionSurface&&a.refractionSurface.bind(s.tRefraction),d=Matrix.mul(Matrix.empty(),x,d.invMatrix),m.uniformMatrix4fv(p.uRefractionViewProjection,
34192!1,d),m.uniform1f(p.uRefractionRayDistance,this.refractionParams.distantBackgroun
34192d?1E10:4*b.mesh.bounds.maxExtent),m.uniform3fv(p.uRefractionTint,this.refractionParams.tint),m.uniform1f(p.uRefractionAlbedoTint,this.refractionParams.useAlbedoTint?1:0),m.uniform1f(p.uRefractionIOREntry,1/this.refractionParams.IOR),(n=this.extrasTexCoordRanges.refractionMaskTex)&&m.uniform4fv(p.uTexRangeRefraction,n));if(n=this.extrasTexCoordRanges.emissiveTex)m.uniform4fv(p.uTexRangeEmissive,n),m.uniform1f(p.uEmissiveScale,
34193this.emissiveIntensity);(n=this.extrasTexCoordRanges.aoTex)&&m.uniform4fv(p.uTexRangeAO,n);r.albedo.bind(s.tAlbedo);r.reflectivity.bind(s.tReflectivity);r.normal.bind(s.tNormal);r.extras.bind(s.tExtras);e.specularTexture.bind(s.tSkySpecular);g===this.stripShader&&(m.uniform1fv(p.uStrips,a.stripData.strips),m.uniform2f(p.uStripRes,2/c.size[0],2/c.size[1]));m.uniform2f(p.uUVOffset,this.uOffset,this.vOffset);return!0};
34194Material.prototype.bindAlphaPrepass=function(a,b){if(!this.complete()||!this.prepassShader)return!1;var c=this.gl,d=this.prepassShader.params,e=this.prepassShader.samplers;this.prepassShader.bind();var f=Matrix.mul(Matrix.empty(),a.view.viewMatrix,b.mesh.displayMatrix),f=Matrix.mul(Matrix.empty(),a.view.projectionMatrix,f);c.uniformMatrix4fv(d.uModelViewProjectionMatrix,!1,f);c.uniform2f(d.uUVOffset,this.uOffset,this.vOffset);this.textures.albedo.bind(e.tAlbedo);return!0};
34195Material.prototype.bindWire=function(a,b){if(!this.complete())return!1;var c=this.gl,d=this.wireShader.params,e=a.view;c.enable(c.BLEND);c.blendFunc(c.SRC_ALPHA,c.ONE_MINUS_SRC_ALPHA);c.depthMask(!1);this.wireShader.bind();var f=Matrix.mul(Matrix.empty(),a.view.viewMatrix,b.mesh.displayMatrix),f=Matrix.mul(Matrix.empty(),a.view.projectionMatrix,f);c.uniformMatrix4fv(d.uModelViewProjectionMatrix,!1,f);c.uniform4f(d.uStripParams,2/e.size[0],2/e.size[1],a.stripData.strips[3],a.stripData.strips[4]);return!0};
34196Material.prototype.complete=function(){return this.wireShader.complete()&&this.shader.complete()&&this.stripShader.complete()&&(!this.prepassShader||this.prepassShader.complete())&&(!this.refractionShader||this.refractionShader.complete())&&this.textures.albedo.complete()&&this.textures.reflectivity.complete()&&this.textures.normal.complete()};var Matrix={type:Float32Array,create:function(a,b,c,d,e,f,g,h,k,l,n,m,p,r,s,u){var q=new Matrix.type(16);q[0]=a;q[4]=b;q[8]=c;q[12]=d;q[1]=e;q[5]=f;q[9]=g;q[13]=h;q[2]=k;q[6]=l;q[10]=n;q[14]=m;q[3]=p;q[7]=r;q[11]=s;q[15]=u;return q},empty:function(){return new Matrix.type(16)},identity:function(){var a=new Matrix.type(16);a[0]=1;a[4]=0;a[8]=0;a[12]=0;a[1]=0;a[5]=1;a[9]=0;a[13]=0;a[2]=0;a[6]=0;a[10]=1;a[14]=0;a[3]=0;a[7]=0;a[11]=0;a[15]=1;return a}
34196,set:function(a,b,c,d,e,f,g,h,k,l,n,m,p,r,s,u,q){a[0]=
34197b;a[4]=c;a[8]=d;a[12]=e;a[1]=f;a[5]=g;a[9]=h;a[13]=k;a[2]=l;a[6]=n;a[10]=m;a[14]=p;a[3]=r;a[7]=s;a[11]=u;a[15]=q},translation:function(a,b,c,d){Matrix.set(a,1,0,0,b,0,1,0,c,0,0,1,d,0,0,0,1);return a},rotation:function(a,b,c){a[0]=1;a[4]=0;a[8]=0;a[12]=0;a[1]=0;a[5]=1;a[9]=0;a[13]=0;a[2]=0;a[6]=0;a[10]=1;a[14]=0;a[3]=0;a[7]=0;a[11]=0;a[15]=1;var d=0.0174532925*b;b=Math.sin(d);d=Math.cos(d);switch(c){case 0:a[5]=d;a[9]=-b;a[6]=b;a[10]=d;break;case 1:a[0]=d;a[8]=b;a[2]=-b;a[10]=d;break;case 2:a[0]=d,
34198a[4]=-b,a[1]=b,a[5]=d}return a},mul:function(a,b,c){var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],k=b[5],l=b[6],n=b[7],m=b[8],p=b[9],r=b[10],s=b[11],u=b[12],q=b[13],x=b[14];b=b[15];var w=c[0],v=c[1],t=c[2],y=c[3];a[0]=w*d+v*h+t*m+y*u;a[1]=w*e+v*k+t*p+y*q;a[2]=w*f+v*l+t*r+y*x;a[3]=w*g+v*n+t*s+y*b;w=c[4];v=c[5];t=c[6];y=c[7];a[4]=w*d+v*h+t*m+y*u;a[5]=w*e+v*k+t*p+y*q;a[6]=w*f+v*l+t*r+y*x;a[7]=w*g+v*n+t*s+y*b;w=c[8];v=c[9];t=c[10];y=c[11];a[8]=w*d+v*h+t*m+y*u;a[9]=w*e+v*k+t*p+y*q;a[10]=w*f+v*l+t*r+y*x;a[11]=
34199w*g+v*n+t*s+y*b;w=c[12];v=c[13];t=c[14];y=c[15];a[12]=w*d+v*h+t*m+y*u;a[13]=w*e+v*k+t*p+y*q;a[14]=w*f+v*l+t*r+y*x;a[15]=w*g+v*n+t*s+y*b;return a},invert:function(a,b){var c=b[0],d=b[1],e=b[2],f=b[3],g=b[4],h=b[5],k=b[6],l=b[7],n=b[8],m=b[9],p=b[10],r=b[11],s=b[12],u=b[13],q=b[14],x=b[15],w=c*h-d*g,v=c*k-e*g,t=c*l-f*g,y=d*k-e*h,E=d*l-f*h,F=e*l-f*k,A=n*u-m*s,B=n*q-p*s,z=n*x-r*s,C=m*q-p*u,G=m*x-r*u,H=p*x-r*q,D=w*H-v*G+t*C+y*z-E*B+F*A;if(!D)return null;D=1/D;a[0]=(h*H-k*G+l*C)*D;a[1]=(e*G-d*H-f*C)*D;
34200a[2]=(u*F-q*E+x*y)*D;a[3]=(p*E-m*F-r*y)*D;a[4]=(k*z-g*H-l*B)*D;a[5]=(c*H-e*z+f*B)*D;a[6]=(q*t-s*F-x*v)*D;a[7]=(n*F-p*t+r*v)*D;a[8]=(g*G-h*z+l*A)*D;a[9]=(d*z-c*G-f*A)*D;a[10]=(s*E-u*t+x*w)*D;a[11]=(m*t-n*E-r*w)*D;a[12]=(h*B-g*C-k*A)*D;a[13]=(c*C-d*B+e*A)*D;a[14]=(u*v-s*y-q*w)*D;a[15]=(n*y-m*v+p*w)*D;return a},transpose:function(a,b){a[0]=b[0];a[4]=b[1];a[8]=b[2];a[12]=b[3];a[1]=b[4];a[5]=b[5];a[9]=b[6];a[13]=b[7];a[2]=b[8];a[6]=b[9];a[10]=b[10];a[14]=b[11];a[3]=b[12];a[7]=b[13];a[11]=b[14];a[15]=b[15];
34201return a},mul4:function(a,b,c,d,e,f){a[0]=b[0]*c+b[4]*d+b[8]*e+b[12]*f;a[1]=b[1]*c+b[5]*d+b[9]*e+b[13]*f;a[2]=b[2]*c+b[6]*d+b[10]*e+b[14]*f;a[3]=b[3]*c+b[7]*d+b[11]*e+b[15]*f;return a},mulPoint:function(a,b,c,d,e){a[0]=b[0]*c+b[4]*d+b[8]*e+b[12];a[1]=b[1]*c+b[5]*d+b[9]*e+b[13];a[2]=b[2]*c+b[6]*d+b[10]*e+b[14];return a},mulVec:function(a,b,c,d,e){a[0]=b[0]*c+b[4]*d+b[8]*e;a[1]=b[1]*c+b[5]*d+b[9]*e;a[2]=b[2]*c+b[6]*d+b[10]*e;return a},perspective:function(a,b,c,d,e,f){f=f||0;b=1/Math.tan(0.00872664625*
34202b);a[0]=b/c;a[1]=a[2]=a[3]=0;a[5]=b;a[4]=a[6]=a[7]=0;a[8]=a[9]=0;a[10]=(e+d)/(d-e)-3.0518044E-5*f;a[11]=-1;a[14]=2*e*d/(d-e);a[12]=a[13]=a[15]=0;return a},perspectiveInfinite:function(a,b,c,d,e){e=e||0;b=1/Math.tan(0.00872664625*b);a[0]=b/c;a[1]=a[2]=a[3]=0;a[5]=b;a[4]=a[6]=a[7]=0;a[8]=a[9]=0;a[10]=a[11]=-1-3.0518044E-5*e;a[14]=-2*d;a[12]=a[13]=a[15]=0;return a},ortho:function(a,b,c,d,e,f,g,h){var k=1/(c-b),l=1/(e-d),n=1/(g-f);a[0]=k+k;a[1]=a[2]=a[3]=0;a[5]=l+l;a[4]=a[6]=a[7]=0;a[12]=-(c+b)*k;a[13]=
34203-(e+d)*l;a[10]=-(n+n)-3.0518044E-5*(h||0);a[14]=-(g+f)*n;a[8]=a[9]=a[11]=0;a[15]=1;return a},lookAt:function(a,b,c,d){var e=a.subarray(0,3),f=a.subarray(4,7),g=a.subarray(8,11);Vect.sub(g,b,c);Vect.cross(e,d,g);Vect.normalize(g,g);Vect.normalize(e,e);Vect.cross(f,g,e);Matrix.set(a,e[0],e[1],e[2],-Vect.dot(e,b),f[0],f[1],f[2],-Vect.dot(f,b),g[0],g[1],g[2],-Vect.dot(g,b),0,0,0,1)},copy:function(a,b){for(var c=0;16>c;++c)a[c]=b[c]},copyToBuffer:function(a,b,c){for(var d=0;16>d;++d)a[b+d]=c[d]}};function Mesh(a,b,c){this.gl=a;this.desc=b;var d=b.isDynamicMesh;this.numSubMeshes=this.dynamicVertexData=0;this.displayMatrix=Matrix.identity();this.name=b.name;this.modelMatrix=Matrix.identity();this.origin=b.transform?Vect.create(b.transform[12],b.transform[13],b.transform[14],1):Vect.create(0,5,0,1);this.stride=32;if(this.vertexColor=b.vertexColor)this.stride+=4;if(this.secondaryTexCoord=b.secondaryTexCoord)this.stride+=8;c=new ByteStream(c.data);this.indexCount=b.indexCount;this.indexTypeSize=
34204b.indexTypeSize;this.indexType=4==this.indexTypeSize?a.UNSIGNED_INT:a.UNSIGNED_SHORT;this.indexBuffer=a.createBuffer();a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer);var e=c.readBytes(this.indexCount*this.indexTypeSize);a.bufferData(a.ELEMENT_ARRAY_BUFFER,e,a.STATIC_DRAW);this.wireCount=b.wireCount;this.wireBuffer=a.createBuffer();a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.wireBuffer);e=c.readBytes(this.wireCount*this.indexTypeSize);a.bufferData(a.ELEMENT_ARRAY_BUFFER,e,a.STATIC_DRAW);a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,
34205null);this.vertexCount=b.vertexCount;this.vertexBuffer=a.createBuffer();a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffer);c=c.readBytes(this.vertexCount*this.stride);d?(this.dynamicVertexData=new Uint8Array(c),a.bufferData(a.ARRAY_BUFFER,c,a.DYNAMIC_DRAW)):a.bufferData(a.ARRAY_BUFFER,c,a.STATIC_DRAW);a.bindBuffer(a.ARRAY_BUFFER,null);this.bounds=void 0===b.minBound||void 0===b.maxBound?{min:Vect.create(-10,-10,-10,1),max:Vect.create(10,
3420510,-0,1)}:{min:Vect.create(b.minBound[0],b.minBound[1],b.minBound[2],
342061),max:Vect.create(b.maxBound[0],b.maxBound[1],b.maxBound[2],1)};this.bounds.maxExtent=Math.max(Math.max(b.maxBound[0]-b.minBound[0],b.maxBound[1]-b.minBound[1]),b.maxBound[2]-b.minBound[2]);this.bounds.averageExtent=(b.maxBound[0]-b.minBound[0]+(b.maxBound[1]-b.minBound[1])+(b.maxBound[2]-b.minBound[2]))/3};function MeshRenderable(a,b,c){this.mesh=a;this.gl=this.mesh.gl;this.indexOffset=b.firstIndex*a.indexTypeSize;this.indexCount=b.indexCount;this.wireIndexOffset=b.firstWireIndex*a.indexTypeSize;this.wireIndexCount=b.wireIndexCount;this.material=c;this.visible=!0}
34207MeshRenderable.prototype.draw=function(a){var b=this.gl;if(this.material.bind(a,this)){a=this.material.shader.attribs;var c=this.mesh.stride;this.mesh.desc.cullBackFaces?(b.enable(b.CULL_FACE),b.cullFace(b.BACK)):b.disable(b.CULL_FACE);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,this.mesh.indexBuffer);b.bindBuffer(b.ARRAY_BUFFER,this.mesh.vertexBuffer);b.enableVertexAttribArray(a.vPosition);b.enableVertexAttribArray(a.vTexCoord);b.enableVertexAttribArray(a.vTangent);b.enableVertexAttribArray(a.vBitangent);
34208b.enableVertexAttribArray(a.vNormal);var d=this.mesh.vertexColor&&void 0!==a.vColor;d&&b.enableVertexAttribArray(a.vColor);var e=this.mesh.secondaryTexCoord&&void 0!==a.vTexCoord2;e&&b.enableVertexAttribArray(a.vTexCoord2);var f=0;b.vertexAttribPointer(a.vPosition,3,b.FLOAT,!1,c,f);f+=12;b.vertexAttribPointer(a.vTexCoord,2,b.FLOAT,!1,c,f);f+=8;this.mesh.secondaryTexCoord&&(e&&b.vertexAttribPointer(a.vTexCoord2,2,b.FLOAT,!1,c,f),f+=8);b.vertexAttribPointer(a.vTangent,2,b.UNSIGNED_SHORT,!0,c,f);f+=
342094;b.vertexAttribPointer(a.vBitangent,2,b.UNSIGNED_SHORT,!0,c,f);f+=4;b.vertexAttribPointer(a.vNormal,2,b.UNSIGNED_SHORT,!0,c,f);d&&b.vertexAttribPointer(a.vColor,4,b.UNSIGNED_BYTE,!0,c,f+4);b.drawElements(b.TRIANGLES,this.indexCount,this.mesh.indexType,this.indexOffset);b.disableVertexAttribArray(a.vPosition);b.disableVertexAttribArray(a.vTexCoord);b.disableVertexAttribArray(a.vTangent);b.disableVertexAttribArray(a.vBitangent);b.disableVertexAttribArray(a.vNormal);d&&b.disableVertexAttribArray(a.vColor);
34210e&&b.disableVertexAttribArray(a.vTexCoord2)}};MeshRenderable.prototype.drawShadow=function(a){var b=this.gl;this.mesh.desc.cullBackFaces?(b.enable(b.CULL_FACE),b.cullFace(b.BACK)):b.disable(b.CULL_FACE);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,this.mesh.indexBuffer);b.bindBuffer(b.ARRAY_BUFFER,this.mesh.vertexBuffer);b.enableVertexAttribArray(a);b.vertexAttribPointer(a,3,b.FLOAT,!1,this.mesh.stride,0);b.drawElements(b.TRIANGLES,this.indexCount,this.mesh.indexType,this.indexOffset);b.disableVertexAttribArray(a)};
34211MeshRenderable.prototype.drawAlphaShadow=function(a,b){var c=this.gl;this.mesh.desc.cullBackFaces?(c.enable(c.CULL_FACE),c.cullFace(c.BACK)):c.disable(c.CULL_FACE);c.bindBuffer(c.ELEMENT_ARRAY_BUFFER,this.mesh.indexBuffer);c.bindBuffer(c.ARRAY_BUFFER,this.mesh.vertexBuffer);c.enableVertexAttribArray(a);c.enableVertexAttribArray(b);c.vertexAttribPointer(a,3,c.FLOAT,!1,this.mesh.stride,0);c.vertexAttribPointer(b,2,c.FLOAT,!1,this.mesh.stride,12);c.drawElements(c.TRIANGLES,this.indexCount,this.mesh.indexType,
34212this.indexOffset);c.disableVertexAttribArray(a);c.disableVertexAttribArray(b)};
34213MeshRenderable.prototype.drawAlphaPrepass=function(a){var b=this.gl;if(this.material.bindAlphaPrepass(a,this)){a=this.material.prepassShader.attribs;var c=this.mesh.stride;this.mesh.desc.cullBackFaces?(b.enable(b.CULL_FACE),b.cullFace(b.BACK)):b.disable(b.CULL_FACE);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,this.mesh.indexBuffer);b.bindBuffer(b.ARRAY_BUFFER,this.mesh.vertexBuffer);b.enableVertexAttribArray(a.vPosition);b.enableVertexAttribArray(a.vTexCoord);b.vertexAttribPointer(a.vPosition,3,b.FLOAT,!1,
34214c,0);b.vertexAttribPointer(a.vTexCoord,2,b.FLOAT,!1,c,12);b.drawElements(b.TRIANGLES,this.indexCount,this.mesh.indexType,this.indexOffset);b.disableVertexAttribArray(a.vPosition);b.disableVertexAttribArray(a.vTexCoord)}};
34215MeshRenderable.prototype.drawWire=function(a){var b=this.material.wireShader.attribs,c=this.gl;this.material.bindWire(a,this)&&(c.enableVertexAttribArray(b.vPosition),c.bindBuffer(c.ELEMENT_ARRAY_BUFFER,this.mesh.wireBuffer),c.bindBuffer(c.ARRAY_BUFFER,this.mesh.vertexBuffer),c.vertexAttribPointer(b.vPosition,3,c.FLOAT,!1,this.mesh.stride,0),c.drawElements(c.LINES,this.wireIndexCount,this.mesh.indexType,this.wireIndexOffset),c.disableVertexAttribArray(b.vPosition))};
34216MeshRenderable.prototype.complete=function(){return this.material.complete()};var Network={fetchImage:function(a,b,c){var d=new Image;d.crossOrigin="Anonymous";d.onload=function(){0<d.width&&0<d.height?b(d):c&&c()};c&&(req.onerror=function(){c()});d.src=a},fetchText:function(a,b,c,d){var e=new XMLHttpRequest;e.open("GET",a,!0);e.onload=function(){200==e.status?b(e.responseText):c&&c()};c&&(e.onerror=function(){c()});d&&(e.onprogress=function(a){d(a.loaded,a.total)});e.send()},fetchBinary:function(a,b,c,d){var e=new XMLHttpRequest;e.open("GET",a,!0);e.responseType="arraybuffer";
34217e.onload=function(){200==e.status?b(e.response):c&&c()};c&&(e.onerror=function(){c()});d&&(e.onprogress=function(a){d(a.loaded,a.total)});e.send()},fetchBinaryIncremental:function(a,b,c,d){var e=new XMLHttpRequest;e.open("HEAD",a,!0);e.onload=function(){if(200==e.status){var f=e.getResponseHeader("Accept-Ranges");if(f&&"none"!=f){var g=e.getResponseHeader("Content-Length")|0,h=function(c,e){var f=new XMLHttpRequest;f.open("GET",a,!0);f.setRequestHeader("Range","bytes="+c+"-"+e);f.responseType="arraybuffer";
34218f.onload=function(){(206==f.status||200==f.status)&&b(f.response)&&e<g&&(c+=d,e+=d,e=e<g-1?e:g-1,h(c,e))};f.send()};h(0,d-1)}else c&&c()}else c&&c()};c&&(e.onerror=function(){c()});e.send()}};function PlaybackControls(a){this.debugString="";this.init=!1;this.speedList=this.cameraList=this.animationList=this.playButton=this.timelineSlider=this.playbackRegion=this.previousFrameButton=this.nextFrameButton=this.pauseButton=this.playButton=0;this.visible=!1;this.backgroundRegion=this.screenButton=0;this.guiScreen=a;this.playbackRegion=new GUIRegion(a);this.idealSliderWidth=650;this.totalListBoxPixelsX=0;this.minWidth=500;this.compactMode=!1;this.ui=a.ui;
34218var b="animationpause"+a.imageSetNumber+
34219"x.png",c="animationplay"+a.imageSetNumber+"x.png",d="timelineLE"+a.imageSetNumber+"x.png",e="timelineM"+a.imageSetNumber+"x.png",f="timelineRE"+a.imageSetNumber+"x.png",g=a.ui.viewer.scene.sceneAnimator.animations.length;if(0!=g){var h=this.idealSliderWidth;this.bottomOffset=85;this.centerOffset=60;var k=a.width/2+this.centerOffset,l=k-h/2,k=k+h/2,n=l-14-32,m=k-n,p=32/a.height,r=this.bottomOffset/a.height,s=this.playbackRegion;s.controlRect.widthPercent=m/a.width;s.controlRect.heightPercent=p;s.controlRect.xPercent=
34220n/a.width;s.controlRect.yPercent=r;p=32/m;this.pauseButton=new Button(this.guiScreen);this.pauseButton.controlRect.set(0,0.125,p,0.75);this.pauseButton.controlRect.opacity=0.5;s.controlRect.registerChildControlRect(this.pauseButton.controlRect);this.pauseButton.linkControl(s.addImageElement(this.pauseButton.controlRect,b));this.playButton=new Button(this.guiScreen);this.playButton.controlRect.set(0,0.125,p,0.75);this.playButton.controlRect.opacity=0.5;s.controlRect.registerChildControlRect(this.playButton.controlRect);
34221this.playButton.linkControl(s.addImageElement(this.playButton.controlRect,c));b=h/m;m=(l-n)/m;this.timelineSlider=new TimelineSlider(this.guiScreen,s);this.timelineSlider.controlRect.set(m,0.03125,b,1);s.controlRect.registerChildControlRect(this.timelineSlider.controlRect);this.timelineSlider.setBackground3x1(s,d,e,f);this.pauseButton.controlRect.showControl(!a.ui.viewer.scene.sceneAnimator.paused);this.playButton.controlRect.showControl(a.ui.viewer.scene.sceneAnimator.paused);d=k+14;e=this.bottomOffset+
342224;f=a.ui.viewer.scene.sceneAnimator.animations[0].cameraObjects.length;a.ui.viewer.scene.sceneAnimator.selectDefaultCamera();a.ui.viewer.scene.sceneAnimator.setViewFromSelectedCamera();this.maxListPixelsX=0;if(1<f){this.cameraList=new ListBox(a);for(m=0;m<f;m++)this.cameraList.addItem(a.ui.viewer.scene.sceneAnimator.animations[0].cameraObjects[m].name,0,0);this.cameraList.spawnControl(d,e,10,24,!0,8);this.cameraList.selectItem(a.ui.viewer.scene.sceneAnimator.selectedCameraIndex);this.maxListPixelsX=
34223this.cameraList.localPixelsX;this.totalListBoxPixelsX+=this.cameraList.localPixelsX+14}if(1<g){this.animationList=new ListBox(a);for(m=0;m<g;m++)this.animationList.addItem(a.ui.viewer.scene.sceneAnimator.animations[m].name,0,0);this.animationList.spawnControl(d,e,10,24,!0,8);this.maxListPixelsX<this.animationList.localPixelsX&&(this.maxListPixelsX=this.animationList.localPixelsX);this.totalListBoxPixelsX+=this.animationList.localPixelsX+14;this.animationList.selectItem(a.ui.viewer.scene.sceneAnimator.selectedAnimationIndex)}d=
34224n-44-14;m=k-d+this.totalListBoxPixelsX;this.speedList=new ListBox(a);this.speedList.addItem("4.0x",4,0);this.speedList.addItem("2.0x",4,0);this.speedList.addItem("1.0x",4,0);this.speedList.addItem("0.5x",4,0);this.speedList.addItem("0.25x",-2,0);this.speedList.spawnControl(d,e,44,24,!1,0);this.speedList.selectItem(2);m>a.width&&(this.idealSliderWidth=a.width-(118+(this.totalListBoxPixelsX+14))-this.centerOffset,a=0,this.cameraList&&a++,this.animationList&&a++,1==a&&(this.idealSliderWidth+=56,this.centerOffset-=
3422514),2==a&&(this.idealSliderWidth+=63,this.centerOffset-=63));this.setupCallbacks()}}
34226PlaybackControls.prototype.resize=function(a){a.ui.viewer.scene.sceneAnimator.showPlayControls||(a.width=1,a.height=1);this.compactMode=a.width<this.minWidth;var b=this.bottomOffset,c=this.bottomOffset+4,d=0;this.cameraList&&this.animationList?d+=42+this.cameraList.localPixelsX+this.animationList.localPixelsX:this.cameraList?d+=28+this.cameraList.localPixelsX:this.animationList&&(d+=28+this.animationList.localPixelsX);var e=a.width-d-72;0==d&&(e-=14);var f=116,g=f+e+14;this.compactMode&&(f=58,e+=
3422744+d,0<d&&(b+=32),!d&&(c+=32));var d=32/e,h=d+14/e,k=1-h,l=this.playbackRegion;l.controlRect.widthPercent=e/a.width;l.controlRect.heightPercent=32/a.height;l.controlRect.xPercent=f/a.width;l.controlRect.yPercent=b/a.height;this.pauseButton.controlRect.set(0,0.125,d,0.75);this.playButton.controlRect.set(0,0.125,d,0.75);this.timelineSlider.controlRect.set(h,0.03125,k,1);this.timelineSlider.setSize(e-46,32);l.controlRect.updateElement();l.controlRect.updateChildElements();this.speedList.setControl(58,
34228c,44,24,!1);this.cameraList&&(this.cameraList.setControl(g,c,10,24,!0,8),g+=this.cameraList.localPixelsX+14);this.animationList&&this.animationList.setControl(g,c,10,24,!0,8);
34228this.timelineSlider.backgroundControl.alignBackground()};
34229PlaybackControls.prototype.setupCallbacks=function(){var a=function(a){"0.01x"==this.speedList.selectedItemText&&this.ui.viewer.scene.sceneAnimator.setPlaybackSpeed(0.01);"0.05x"==this.speedList.selectedItemText&&this.ui.viewer.scene.sceneAnimator.setPlaybackSpeed(0.05);"0.25x"==this.speedList.selectedItemText&&this.ui.viewer.scene.sceneAnimator.setPlaybackSpeed(0.25);"0.5x"==this.speedList.selectedItemText&&this.ui.viewer.scene.sceneAnimator.setPlaybackSpeed(0.5);"1.0x"==this.speedList.selectedItemText&&
34230this.ui.viewer.scene.sceneAnimator.setPlaybackSpeed(1);"2.0x"==this.speedList.selectedItemText&&this.ui.viewer.scene.sceneAnimator.setPlaybackSpeed(2);"4.0x"==this.speedList.selectedItemText&&this.ui.viewer.scene.sceneAnimator.setPlaybackSpeed(4)}.bind(this),b=function(a){this.ui.viewer.scene.sceneAnimator.selectCamera(this.cameraList.selectedIndex);this.ui.viewer.wake()}.bind(this),c=function(a){this.ui.viewer.scene.sceneAnimator.selectAnimation(this.animationList.selectedIndex);this.ui.viewer.wake()}.bind(this);
34231this.speedList&&(this.speedList.selectionChangedCallback=a);this.cameraList&&(this.cameraList.selectionChangedCallback=b);this.animationList&&(this.animationList.selectionChangedCallback=c);this.playButton.controlRect.linkedControl.onclick=function(){this.ui.viewer.scene.sceneAnimator.pause(!1);this.playButton.controlRect.showControl(!1);this.pauseButton.controlRect.showControl(!0);this.ui.viewer.wake()}.bind(this);this.pauseButton.controlRect.linkedControl.onclick=function(){this.ui.viewer.scene.sceneAnimator.pause(!0);
34232this.playButton.controlRect.showControl(!0);this.pauseButton.controlRect.showControl(!1)}.bind(this)};function PostRender(a,b,c){this.gl=a;this.desc=b;b=[];0!=this.desc.sharpen&&b.push("#define SHARPEN");(this.useBloom=0<this.desc.bloomColor[0]*this.desc.bloomColor[3]||0<this.desc.bloomColor[1]*this.desc.bloomColor[3]||0<this.desc.bloomColor[2]*this.desc.bloomColor[3])&&b.push("#define BLOOM");0!=this.desc.vignette[3]&&b.push("#define VIGNETTE");1==this.desc.saturation[0]*this.desc.saturation[3]&&1==this.desc.saturation[1]*this.desc.saturation[3]&&1==this.desc.saturation[2]*this.desc.saturation[3]||
34233b.push("#define SATURATION");1==this.desc.contrast[0]*this.desc.contrast[3]&&1==this.desc.contrast[1]*this.desc.contrast[3]&&1==this.desc.contrast[2]*this.desc.contrast[3]&&1==this.desc.brightness[0]*this.desc.brightness[3]&&1==this.desc.brightness[1]*this.desc.brightness[3]&&1==this.desc.brightness[2]*this.desc.brightness[3]||b.push("#define CONTRAST");0!=this.desc.grain&&b.push("#define GRAIN");1==this.desc.toneMap?b.push("#define REINHARD"):2==this.desc.toneMap&&b.push("#define HEJL");this.desc.colorLUT&&
34234b.push("#define COLOR_LUT");this.sampleIndex=0;this.sampleCount=1;c&&(this.sampleCount=4,this.sampleOffsets=[[-0.5,-0.5],[0.5,-0.5],[-0.5,0.5],[0.5,0.5]]);this.aaShader=a.shaderCache.fromURLs("postvert.glsl","postaa.glsl");this.shader=a.shaderCache.fromURLs("postvert.glsl","postfrag.glsl",b);this.plainShader=a.shaderCache.fromURLs("postvert.glsl","postfrag.glsl",[]);this.fullscreenTriangle=a.createBuffer();a.bindBuffer(a.ARRAY_BUFFER,this.fullscreenTriangle);c=new Float32Array([0,0,2,0,0,2]);a.bufferData(a.ARRAY_BUFFER,
34235c,a.STATIC_DRAW);a.bindBuffer(a.ARRAY_BUFFER,null);if(this.useBloom){this.bloomTextures=[];this.bloomTargets=[];for(c=0;2>c;++c)b={width:256,height:256,clamp:!0},this.bloomTextures[c]=new Texture(a,b),this.bloomTextures[c].loadArray(null,a.RGBA,a.ext.textureHalf&&a.ext.textureHalfLinear?a.ext.textureHalf.HALF_FLOAT_OES:a.UNSIGNED_BYTE),this.bloomTargets[c]=new Framebuffer(a,{width:b.width,height:b.height,color0:this.bloomTextures[c]});for(this.bloomSamples=64;this.bloomSamples+16>=a.limits.fragmentUniforms;)this.bloomSamples/=
342362;this.bloomShader=a.shaderCache.fromURLs("postvert.glsl","bloom.glsl",["#define BLOOM_SAMPLES "+this.bloomSamples]);this.shrinkShader=a.shaderCache.fromURLs("postvert.glsl","bloomshrink.glsl")}a=new Uint8Array(16384);for(c=0;16384>c;c++){b=255*Math.random();var d=255*Math.random();a[c]=0.5*(b+d)}this.noiseTexture=new Texture(this.gl,{width:128,height:128});this.noiseTexture.loadArray(a,this.gl.LUMINANCE);this.desc.colorLUT&&(a=this.desc.colorLUT,this.colorLUT=new Texture(this.gl,{width:a.length/
342373|0,height:1,clamp:!0}),this.colorLUT.loadArray(new Uint8Array(a),this.gl.RGB));this.blackTexture=new Texture(this.gl,{width:1,height:1});this.blackTexture.loadArray(new Uint8Array([0,0,0,0]));this.bloomResult=this.blackTexture}
34238PostRender.prototype.prepareBloom=function(a){if(this.useBloom&&this.bloomShader.complete()&&this.shrinkShader.complete()){this.shrinkShader.bind();this.bloomTargets[1].bind();a.bind(this.shrinkShader.samplers.tInput);this.fillScreen(this.shrinkShader.attribs.vCoord);this.bloomShader.bind();var b=[];this.bloomTargets[0].bind();this.bloomTextures[1].bind(this.bloomShader.samplers.tInput);for(var c=0,d=0;d<this.bloomSamples;++d){var e=-1+2*d/(this.bloomSamples-1),f;f=4*e;f=Math.exp(-0.5*f*f/1)/2.50662827463;
34239c+=f;b[4*d+0]=e*this.desc.bloomSize;b[4*d+1]=0;b[4*d+2]=f;b[4*d+3]=0}for(d=0;d<this.bloomSamples;++d)b[4*d+2]/=c;this.gl.uniform4fv(this.bloomShader.params.uKernel,b);this.fillScreen(this.bloomShader.attribs.vCoord);this.bloomTargets[1].bind();this.bloomTextures[0].bind(this.bloomShader.samplers.tInput);for(d=0;d<this.bloomSamples;++d)c=b[4*d+0],c*=a.desc.width/a.desc.height,b[4*d+0]=0,b[4*d+1]=c;this.gl.uniform4fv(this.bloomShader.params.uKernel,b);this.fillScreen(this.bloomShader.attribs.vCoord);
34240this.bloomResult=this.bloomTextures[1]}else this.bloomResult=this.blackTexture};
34241PostRender.prototype.computeParams=function(a,b){var c=this.desc,d={};d.scale=[c.contrast[0]*c.contrast[3],c.contrast[1]*c.contrast[3],c.contrast[2]*c.contrast[3]];d.bias=[c.bias[0]*c.bias[3],c.bias[1]*c.bias[3],c.bias[2]*c.bias[3]];d.bias=[-d.bias[0]*d.scale[0]+d.bias[0],-d.bias[1]*d.scale[1]+d.bias[1],-d.bias[2]*d.scale[2]+d.bias[2]];var e=[c.brightness[0]*c.brightness[3],c.brightness[1]*c.brightness[3],c.brightness[2]*c.brightness[3]];d.scale=[d.scale[0]*e[0],d.scale[1]*e[1],d.scale[2]*e[2]];d.bias=
34242[d.bias[0]*e[0],d.bias[1]*e[1],d.bias[2]*e[2]];d.saturation=[c.saturation[0]*c.saturation[3],c.saturation[1]*c.saturation[3],c.saturation[2]*c.saturation[3]];d.bloomColor=[c.bloomColor[0]*c.bloomColor[3],c.bloomColor[1]*c.bloomColor[3],c.bloomColor[2]*c.bloomColor[3]];d.sharpen=[c.sharpen,0.25*c.sharpen,c.sharpenLimit];d.sharpenKernel=[1/a,0,0,1/b];e=a>b?a:b;d.vignetteAspect=[a/e,b/e,0.5*a/e,0.5*b/e];d.vignette=[2*(1-c.vignette[0])*c.vignette[3],2*(1-c.vignette[1])*c.vignette[3],2*(1-c.vignette[2])*
34243c.vignette[3],c.vignetteCurve];var e=1/this.noiseTexture.desc.width,f=1/this.noiseTexture.desc.height,g=1-c.grainSharpness;d.grainCoord=[e*a,f*b,0.5*g*e,0.5*g*f];d.grainScaleBias=[2*c.grain,-c.grain];return d};
34244PostRender.prototype.present=function(a,b,c,d){d||this.prepareBloom(a);1<this.sampleCount&&this.allocAABuffer(b,c);d=d?this.plainShader:this.shader;if(d.bind()){var e=this.gl,f=d.samplers,g=d.params,h=this.computeParams(b,c);a.bind(f.tInput);this.bloomResult.bind(f.tBloom);this.noiseTexture.bind(f.tGrain);this.colorLUT&&this.colorLUT.bind(f.tLUT);e.uniform3fv(g.uScale,h.scale);e.uniform3fv(g.uBias,h.bias);e.uniform3fv(g.uSaturation,h.saturation);e.uniform4fv(g.uSharpenKernel,h.sharpenKernel);e.uniform3fv(g.uSharpness,
34245h.sharpen);e.uniform3fv(g.uBloomColor,h.bloomColor);e.uniform4fv(g.uVignetteAspect,h.vignetteAspect);e.uniform4fv(g.uVignette,h.vignette);e.uniform4fv(g.uGrainCoord,h.grainCoord);e.uniform2fv(g.uGrainScaleBias,h.grainScaleBias);if(a=1<this.sampleCount&&0<=this.sampleIndex){var k=1/(1+this.sampleIndex);this.sampleIndex+=1;1>k&&(e.enable(e.BLEND),e.blendColor(k,k,k,k),e.blendFunc(e.CONSTANT_ALPHA,e.ONE_MINUS_CONSTANT_ALPHA));this.aaTarget.bind()}else Framebuffer.bindNone(e),1<this.sampleCount&&(this.sampleIndex+=
342461);e.viewport(0,0,b,c);this.fillScreen(d.attribs.vCoord);a&&(1>k&&e.disable(e.BLEND),Framebuffer.bindNone(e),this.aaShader.bind(),this.aaBuffer.bind(this.aaShader.samplers.tInput),this.fillScreen(this.aaShader.attribs.vCoord))}};
34247PostRender.prototype.allocAABuffer=function(a,b){this.aaBuffer&&this.aaBuffer.desc.width==a&&this.aaBuffer.desc.height==b||(this.aaBuffer&&this.aaBuffer.destroy(),this.aaBuffer=new Texture(this.gl,{width:a,height:b,clamp:!0}),this.aaBuffer.loadArray(),this.aaTarget=new Framebuffer(this.gl,{color0:this.aaBuffer,ignoreStatus:!0}))};
34248PostRender.prototype.adjustProjectionForSupersampling=function(a){if(1<this.sampleCount){var b=this.currentSample(),c=this.sampleOffsets[b][0]/a.size[0],b=this.sampleOffsets[b][1]/a.size[1],c=Matrix.translation(Matrix.empty(),c,b,0);Matrix.mul(a.projectionMatrix,c,a.projectionMatrix)}};PostRender.prototype.discardAAHistory=function(){this.sampleIndex=-1};PostRender.prototype.currentSample=function(){return(0>this.sampleIndex?0:this.sampleIndex)%this.sampleCount};
34249PostRender.prototype.fillScreen=function(a){var b=this.gl;b.bindBuffer(b.ARRAY_BUFFER,this.fullscreenTriangle);b.enableVertexAttribArray(a);b.vertexAttribPointer(a,2,b.FLOAT,!1,0,0);b.drawArrays(b.TRIANGLES,0,3);b.disableVertexAttribArray(a);b.bindBuffer(b.ARRAY_BUFFER,null)};PostRender.prototype.blitTexture=function(a){this.aaShader.bind();
34249a.bind(this.aaShader.samplers.tInput);this.fillScreen(this.aaShader.attribs.vCoord)};function Scene(a){this.gl=a;this.name="untitled";this.meshes=[];this.meshRenderables=[];this.materials={};this.sky=this.view=null;this.selectedPartIndex=0;this.soloPart=!1;this.miscnotes="";this.nextView=null;this.viewFade=0;this.refractionSurface=this.shadow=this.stripData=this.lights=null;this.sceneAnimator=this.frameCounter=0;this.sceneLoaded=!1;this.debugString=""}
34250Scene.prototype.load=function(a){var b=this.gl,c,d=a.extract("scene.json");if(void 0!==d){if(!a.checkSignature(d))return!1;d=(new ByteStream(d.data)).asString();if(null==d||0>=d.length)return!1;try{c=JSON.parse(d)}catch(e){return console.error(e),!1}}else return!1;this.metaData=c.metaData;this.view=new View(c.mainCamera.view);this.sky=new Sky(this.gl,a,c.sky);this.lights=new Lights(c.lights,this.view);this.materialsList=[];this.materials={};for(var f in c.materials){var g=c.materials[f];g.lightCount=
34251this.lights.count;g.shadowCount=this.lights.shadowCount;d=new Material(this.gl,a,g);this.materials[g.name]=d;this.materialsList.push(d)}if(c.meshes)for(g=0;g<c.meshes.length;++g){f=c.meshes[g];f=new Mesh(this.gl,f,a.extract(f.file));this.meshes.push(f);for(var h=0;h<f.desc.subMeshes.length;++h){var k=f.desc.subMeshes[h];if(d=this.materials[k.material])f.numSubMeshes++,this.meshRenderables.push(new MeshRenderable(f,k,d))}}this.bounds=new Bounds(this.meshes);this.postRender=new PostRender(this.gl,c.mainCamera.post,
34252!0);this.shadow=new ShadowCollector(b,this.lights.shadowCount);this.cameras=c.Cameras;c.AnimData&&(this.sceneAnimator=new SceneAnimator(this,a,c.AnimData));c.fog&&(this.fog=new Fog(b,c.fog));c.shadowFloor&&(this.shadowFloor=new ShadowFloor(b,c.shadowFloor,this.shadow,this.lights));return this.sceneLoaded=!0};
34253Scene.prototype.update=function(){this.sceneAnimator&&(this.frameCounter++,this.lights.flagUpdateAnimatedLighting(),this.sceneAnimator.drawAnimated&&(1==this.frameCounter?this.sceneAnimator.resetPlayback():this.sceneAnimator.updateAnimationPlayback()));this.lights.update(this.view,this.bounds)};Scene.prototype.collectShadows=function(a){this.shadow.collect(this,a)};
34254Scene.prototype.draw=function(a){var b=this.gl;if(this.sceneLoaded){this.sky.setClearColor();b.clear(b.COLOR_BUFFER_BIT|b.DEPTH_BUFFER_BIT|b.STENCIL_BUFFER_BIT);b.enable(b.DEPTH_TEST);this.sky.draw(this);this.shadowFloor&&this.shadowFloor.draw(this);for(var c=0;c<this.meshRenderables.length;++c)this.meshRenderables[c].material.usesBlending||this.meshRenderables[c].material.usesRefraction||!this.meshRenderables[c].visible||this.meshRenderables[c].draw(this);b.enable(b.POLYGON_OFFSET_FILL);b.polygonOffset(1,
342551);b.colorMask(!1,!1,!1,!1);for(c=0;c<this.meshRenderables.length;++c)this.meshRenderables[c].drawAlphaPrepass(this);b.colorMask(!0,!0,!0,!0);b.disable(b.POLYGON_OFFSET_FILL);b.depthFunc(b.LEQUAL);b.depthMask(!1);for(c=0;c<this.meshRenderables.length;++c)this.meshRenderables[c].material.usesBlending&&this.meshRenderables[c].visible&&this.meshRenderables[c].draw(this);b.disable(b.BLEND);b.depthMask(!0);b.depthFunc(b.LESS);for(var d=!1,c=0;c<this.meshRenderables.length;++c)if(this.meshRenderables[c].material.usesRefraction){d=
34256!0;break}if(d)for(this.refractionSurface&&this.refractionSurface.desc.width==a.color0.desc.width&&this.refractionSurface.desc.height==a.color0.desc.height||(this.refractionSurface=new Texture(b,a.color0.desc),this.refractionSurface.loadArray(null,a.color0.format,a.color0.componentType),this.refractionBuffer=new Framebuffer(this.gl,{color0:this.refractionSurface})),this.refractionBuffer.bind(),this.postRender.blitTexture(a.color0),a.bind(),c=0;c<this.meshRenderables.length;++c)this.meshRenderables[c].material.usesRefraction&&
34257this.meshRenderables[c].visible&&this.meshRenderables[c].draw(this);if(this.stripData.activeWireframe()&&0<this.meshRenderables.length){for(c=0;c<this.meshRenderables.length;++c)this.meshRenderables[c].visible&&this.meshRenderables[c].drawWire(this);b.depthMask(!0)}b.disable(b.BLEND)}};
34257Scene.prototype.drawSecondary=function(a){this.fog&&this.fog.draw(this,a)};
34258Scene.prototype.complete=function(){if(!this.sky.complete()||!this.shadow.complete()||this.fog&&!this.fog.complete()||this.shadowFloor&&!this.shadowFloor.complete())return!1;for(var a=0;a<this.meshRenderables.length;++a)if(!this.meshRenderables[a].complete())return!1;return!0};function SceneAnimator(a,b,c){this.scene=a;this.animations=[];this.skinningRigs=[];this.meshIDs=[];this.lightIDs=[];this.materialIDs=[];this.views=[];this.viewYawOffsets=[];this.viewPitchOffsets=[];this.cameraObjectIndices=[];this.cameraChildrenIndices=[];this.subMeshObjectIndices=[];this.subMeshLiveIndices=[];this.scene=a;this.selectedCameraIndex=-1;this.selectedAnimationIndex=0;this.debugString="";this.scenePlaybackSpeed=this.playbackSpeed=1;this.animationProgress=this.totalSeconds=0;this.paused=
34259this.autoAdvanceNextAnimation=!1;this.animateVisibility=this.drawAnimated=this.linkSceneObjects=this.loadSkinningRigs=this.animateMaterials=this.animateTurntables=this.enableSkinning=this.animateMeshes=this.animateLights=this.playAnimations=this.loadAnimations=!0;this.showDebugInfo=!1;this.loopCount=0;this.loopTurntables=this.lockPlayback=!1;this.fogObjectIndex=-1;this.unitScaleSkinnedMeshes=!0;this.sceneScale=c.sceneScale;this.defaultCameraGlobalIndex=c.selectedCamera;this.selectedAnimationIndex=
34260c.selectedAnimation;this.autoPlayAnims=c.autoPlayAnims;this.showPlayControls=c.showPlayControls;c.scenePlaybackSpeed&&(this.scenePlaybackSpeed=c.scenePlaybackSpeed,0==this.scenePlaybackSpeed&&(this.scenePlaybackSpeed=1));this.autoPlayAnims||(this.paused=!0);if(this.loadAnimations){if(c.meshIDs)for(var d=c.meshIDs.length,e=0;e<d;++e){var f=c.meshIDs[e].partIndex;this.meshIDs.push(f)}if(c.lightIDs)for(d=c.lightIDs.length,e=0;e<d;++e)f=c.lightIDs[e],f=f.partIndex,this.lightIDs.push(f);if(c.materialIDs)for(d=
34261c.materialIDs.length,e=0;e<d;++e)f=c.materialIDs[e],f=f.partIndex,this.materialIDs.push(f);this.numMatricesInTable=c.numMatrices;e=b.get("MatTable.bin");f=new ByteStream(e.data);e||(this.numMatricesInTable=0,this.debugString+="<br>No mattable?");if(c.skinningRigs&&this.loadSkinningRigs)for(d=c.skinningRigs.length,e=0;e<d;++e){var g=new SkinningRig(b,c.skinningRigs[e],f);""==g.debugString?this.skinningRigs.push(g):(this.debugString+="<br>Error loading skinning rig "+e+" :"+g.debugString,this.debugString+=
34262"<br>Skipping the rest",e=d)}if(c.animations)for(f=c.animations.length,e=0;e<f;++e)d=new Animation(b,c.animations[e]),this.animations.push(d);this.startMS=Date.now();f=this.animations.length;if(this.linkSceneObjects&&0!=f){for(e=0;e<f;e++)for(b=this.animations[e],d=b.animatedObjects.length,c=0;c<d;c++)g=b.animatedObjects[c],"LightSO"==g.sceneObjectType&&(g.lightIndex=this.findLightIndexByPartIndex(c),-1!=g.lightIndex?b.lightObjects.push(g):this.debugString+="<br> got light not in scene "+g.name),
34263"FogSO"==g.sceneObjectType&&(this.fogObjectIndex=c),"SubMeshSO"==g.sceneObjectType&&0==e&&(this.subMeshObjectIndices.push(c),this.subMeshLiveIndices.push(-1)),"Material"==g.sceneObjectType&&(g.materialIndex=this.findMaterialIndexByPartIndex(c),-1==g.materialIndex?this.debugString+="<br> can't find material index for object "+c:b.materialObjects.push(g)),"TurnTableSO"==g.sceneObjectType&&b.turnTableObjects.push(g),"MeshSO"==g.sceneObjectType&&(g.meshIndex=this.findMeshIndexByPartIndex(this.scene.meshes,
34264c),-1==g.meshIndex?(this.debugString+="<br> can't find mesh index for object "+c,this.logObjectInfo(c,0)):(b.meshObjects.push(g),g.mesh=this.scene.meshes[g.meshIndex],-1!=g.skinningRigIndex&&g.mesh&&g.skinningRigIndex<this.skinningRigs.length&&(g.skinningRig=this.skinningRigs[g.skinningRigIndex],g.skinningRig.isRigidSkin||(g.mesh.dynamicVertexData?g.skinningRig.useOriginalMeshVertices(g.mesh):(this.debugString+="Skinning object - but mesh is not dynamic",this.debugString+="<br>Rig index "+g.skinningRigIndex,
34265this.debugString+=" not tagged as rigid"))))),"CameraSO"==g.sceneObjectType&&b.cameraObjects.push(g);b=this.animations[0];c=b.cameraObjects.length;for(e=0;e<c;e++)if(f=b.cameraObjects[e],d=this.scene.cameras[f.name]){if(d=d.view)d=new View(d),this.cameraObjectIndices.push(f.id),this.views.push(d),this.viewYawOffsets.push(0),this.viewPitchOffsets.push(0)}else this.debugString+="<br>no camDesc for "+f.name,this.views.push(a.view);a=this.scene.meshes.length;f=this.subMeshObjectIndices.length;for(e=d=
342660;e<a;e++)for(g=this.scene.meshes[e],c=0;c<f;c++){var h=this.subMeshObjectIndices[c],k=b.animatedObjects[h],l=b.animatedObjects[k.parentIndex];l.mesh||(this.debugString+="<br>submesh parent object has no mesh?",this.debugString+="<br>obj.name "+k.name,this.debugString+="<br>parent.name "+l.name,this.debugString+="<br>submesh index "+c,this.debugString+="<br>obj.index "+h);if(l.mesh==g){for(h=0;h<g.numSubMeshes;
34266h++)this.subMeshLiveIndices[c+(g.numSubMeshes-1-h)]=d,d++;c=f}}for(e=0;e<f;e++)-1==this.subMeshLiveIndices[e]&&
34267(this.debugString+="<br>Missing mesh? Unused submesh "+e+" of "+f);this.showDebugInfo=this.stopEverything=this.runDebugMode=!1;this.selectDefaultCamera();this.findCameraChildren();this.findFixedTransforms();this.runDebugMode&&(this.setAnimationProgress(0,!0),""!=this.debugString?this.stopEverything=!0:this.checkDebug())}}else this.debugString+="<br>Skip loading animation data"}
34268SceneAnimator.prototype.logTimes=function(){this.debugString+="<br>Times";var a=this.animations[0],b=a.animatedObjects.length;this.debugString+="<br>Animation totalSeconds "+a.totalSeconds;this.debugString+="<br>Animation totalFrames "+a.totalFrames;this.debugString+="<br>Animation totalObjects "+b;for(var c=0;c<b;c++){var d=a.animatedObjects[c];this.debugString+="<br>Object: "+c;this.debugString+=" End: "+d.endTime;this.debugString+=" Length: "+d.animationLength;this.debugString+=" Frames: "+d.totalFrames}};
34269SceneAnimator.prototype.flagAllForDebugging=function(){for(var a=this.animations.length,b=0;b<a;b++)for(var c=this.animations[b],d=c.animatedObjects.length,e=0;e<d;e++)c.animatedObjects[e].debugMe=!0};
34270SceneAnimator.prototype.checkDebug=function(){this.debugString="<br>--------------------------------------Debug Info:";this.debugString+="<br>this.selectedAnimationIndex :"+this.selectedAnimationIndex;var a=this.animations[this.selectedAnimationIndex],b=a.animatedObjects.length;this.debugString+="<br>numAnimatedObjects :"+b;""!=a.debugString&&(this.debugString+="<br>--------------------------------------------------Got animation bug info:",this.debugString+=a.debugString,this.showDebugInfo=this.stopEverything=
34271!0,a.debugString="");for(var c=0;c<b;c++){var d=a.animatedObjects[c];if(""!=d.debugString||""!=d.animatedLocalTransform.debugString)this.debugString+="<br>--------------------------------------------------Got object bug info:",this.debugString+=d.debugString,this.debugString+=d.animatedLocalTransform.debugString,this.showDebugInfo=this.stopEverything=!0,d.debugString="",d.animatedLocalTransform.debugString="";d.skinningRig&&""!=d.skinningRig.debugString&&(this.debugString+="<br>--------------------------------------------------Got skin rig info:",
34272this.debugString+=d.skinningRig.debugString,d.skinningRig.debugString="",this.showDebugInfo=this.stopEverything=!0)}this.debugString+="<br>--------------------------------------Done Debug Info:"};
34273SceneAnimator.prototype.logObjectInfo=function(a,b){var c=this.animations[this.selectedAnimationIndex],d=b*c.originalFPS;if(a>=c.animatedObjects.length)this.debugString+="object index "+a+" exceeds "+c.animatedObjects.length;else{var e=c.animatedObjects[a],f=c.animatedObjects[e.modelPartIndex],g=c.getObjectAnimationFramePercent(e,b),h=c.getObjectAnimationFramePercent(f,b);this.debugString="";this.debugString+="<br>Object Name: "+e.name;this.debugString+="<br>Object Type: "+e.sceneObjectType;this.debugString+=
34274"<br>Object Index: "+e.id;this.debugString+="<br>Part Index: "+e.modelPartIndex;this.debugString+="<br>Part Scale: "+e.modelPartScale;this.debugString+="<br>Mesh Index: "+e.meshIndex;this.debugString+="<br>Light Index: "+e.lightIndex;this.debugString+="<br>Deformer Index: "+e.skinningRigIndex;this.debugString+="<br>Parent Index: "+e.parentIndex;this.debugString+="<br>Scene time "+b;this.debugString+="<br>Scene framepercent "+d;this.debugString+="<br>Object looped framepercent "+g;this.debugString+=
34275"<br>Model  looped framepercent "+h;this.debugString+="<br>Object Anim length "+e.animationLength;this.debugString+="<br>Object Total frames "+e.totalFrames;this.debugString+="<br>Object FPS "+e.modelPartFPS;this.debugString+="<br>Model Part Anim length "+f.animationLength;this.debugString+="<br>Model total frames "+f.totalFrames;this.debugString+="<br>Model Part FPS "+f.modelPartFPS;d=Matrix.identity();c.getWorldTransform(e.id,b,d,this.sceneScale,!0);this.debugString+=e.debugString;var c=d[0],e=
34276d[1],f=d[2],g=d[4],h=d[5],k=d[6],l=d[8],n=d[9],d=d[10];Math.sqrt(c*c+e*e+f*f);Math.sqrt(g*g+h*h+k*k);Math.sqrt(l*l+n*n+d*d)}};
34276SceneAnimator.prototype.resetPlayback=function(){this.startMS=Date.now();this.animationProgress=this.totalSeconds=0;this.setAnimationProgress(0,!0)};SceneAnimator.prototype.pause=function(a){this.paused=a;this.startMS=Date.now()-1E3*this.totalSeconds/(this.playbackSpeed*this.scenePlaybackSpeed)};
34277SceneAnimator.prototype.setAnimationProgress=function(a,b){var c=this.animations[this.selectedAnimationIndex];this.animationProgress=a;this.totalSeconds=c.totalSeconds*this.animationProgress;this.totalSeconds-=1/c.originalFPS;0>this.totalSeconds&&(this.totalSeconds=0);this.startMS=Date.now()-1E3*this.totalSeconds/(this.playbackSpeed*this.scenePlaybackSpeed);b&&this.updateScene()};
34278SceneAnimator.prototype.setPlaybackSpeed=function(a){this.playbackSpeed=a;this.startMS=Date.now()-1E3*this.totalSeconds/(this.playbackSpeed*this.scenePlaybackSpeed)};
34279SceneAnimator.prototype.resetCustomView=function(){0<=this.selectedCameraIndex&&this.selectedCameraIndex<this.views.length&&(this.viewYawOffsets[this.selectedCameraIndex]=0,this.viewPitchOffsets[this.selectedCameraIndex]=0,this.scene.view.rotation[1]=this.views[this.selectedCameraIndex].rotation[1],this.scene.view.rotation[0]=this.views[this.selectedCameraIndex].rotation[0],this.setViewFromSelectedCamera())};
34280SceneAnimator.prototype.updateUserCamera=function(){this.clearCacheForCameraChildren();if(0<=this.selectedCameraIndex&&this.selectedCameraIndex<this.views.length&&this.selectedAnimationIndex<this.animations.length){var a=this.cameraObjectIndices[this.selectedCameraIndex],b=this.animations[this.selectedAnimationIndex];if(a<b.animatedObjects.length){var c=this.views[this.selectedCameraIndex],d=this.scene.view.rotation[1],e=this.scene.view.rotation[0],f=e-c.rotation[0];this.viewYawOffsets[this.selectedCameraIndex]=
34281d-c.rotation[1];this.viewPitchOffsets[this.selectedCameraIndex]=f;c=b.animatedObjects[a];b.getObjectAnimationFramePercent(c,this.totalSeconds);var f=Matrix.identity(),g=Matrix.identity();c.useFixedWorldTransform=!1;c.useFixedLocalTransform=!1;c.animatedLocalTransform.lockTransform=!1;c.animatedLocalTransform.clearCachedTransforms();c.cachedFrame0=-1;c.cachedFrame1=-1;c.cachedFrame2=-1;c.cachedFrame3=-1;c.cachedFrameUse0=0;c.cachedFrameUse1=0;c.cachedFrameUse2=0;c.cachedFrameUse3=0;b.getWorldTransform(a,
34282this.totalSeconds,g,this.sceneScale,!1);var a=g[0],h=g[1],k=g[2],l=g[4],n=g[5],m=g[6],p=g[8],r=g[9],s=g[10],a=Math.sqrt(a*a+h*h+k*k),l=Math.sqrt(l*l+n*n+m*m),n=Math.sqrt(p*p+r*r+s*s),m=-(this.scene.view.pivot[0]-g[12]),h=-(this.scene.view.pivot[1]-g[13]),g=-(this.scene.view.pivot[2]-g[14]);0>=m*p+h*r+g*s&&(d+=180);d=Matrix.rotation(Matrix.empty(),d,1);e=Matrix.rotation(Matrix.empty(),e,0);Matrix.mul(f,d,e);e=Math.sqrt(m*m+h*h+g*g);d=this.scene.view.pivot[1]+f[9]*e;g=this.scene.view.pivot[2]+f[10]*
34283e;f[12]=this.scene.view.pivot[0]+f[8]*e;f[13]=d;f[14]=g;e=Matrix.identity();b.getWorldTransform(c.parentIndex,this.totalSeconds,e,this.sceneScale,!1);b=Matrix.identity();Matrix.invert(b,e);e=Matrix.identity();Matrix.mul(e,b,f);e[12]/=this.sceneScale;e[13]/=this.sceneScale;e[14]/=this.sceneScale;f[0]*=a;f[1]*=a;f[2]*=a;f[4]*=l;f[5]*=l;f[6]*=l;f[8]*=n;f[9]*=n;f[10]*=n;c.setFixedWorldTransform(f);c.setFixedLocalTransform(e)}}};
34284SceneAnimator.prototype.setViewFromSelectedCamera=function(){if(0<=this.selectedCameraIndex&&this.selectedCameraIndex<this.views.length){var a=this.views[this.selectedCameraIndex],b=this.scene.view,c=this.viewYawOffsets[this.selectedCameraIndex],d=this.viewPitchOffsets[this.selectedCameraIndex];b.pivot[0]=a.pivot[0];b.pivot[1]=a.pivot[1];b.pivot[2]=a.pivot[2];b.rotation[0]=a.rotation[0]+d;b.rotation[1]=a.rotation[1]+c;b.radius=a.radius;b.nearPlane=a.nearPlane;b.fov=a.fov;b.saveResetView();b.updateProjection();
34285b.updateView()}};SceneAnimator.prototype.selectDefaultCamera=function(){if(-1!=this.defaultCameraGlobalIndex&&0<this.animations.length)for(var a=this.animations[0],b=a.cameraObjects.length,c=0;c<b;c++)if(a.cameraObjects[c].id==this.defaultCameraGlobalIndex){this.selectedCameraIndex=c;return}this.selectedCameraIndex=0};
34286SceneAnimator.prototype.updateAnimationPlayback=function(){if(!this.stopEverything||!this.runDebugMode){var a=this.animations[this.selectedAnimationIndex];this.updateUserCamera();if(this.paused||!this.playAnimations)this.startMS=0<this.playbackSpeed?Date.now()-1E3*this.totalSeconds/(this.playbackSpeed*this.scenePlaybackSpeed):Date.now()-1E3*this.totalSeconds,this.refreshTransformsOnly(),this.runDebugMode&&this.checkDebug(),a=this.scene.view,a.saveResetView(),a.updateProjection(),a.updateView();else{this.lockPlayback&&
342870<this.playbackSpeed&&(this.startMS=Date.now()-1E3*this.totalSeconds/(this.playbackSpeed*this.scenePlaybackSpeed));var b=(Date.now()-this.startMS)/1E3*this.playbackSpeed*this.scenePlaybackSpeed;this.totalSeconds=(Date.now()-this.startMS)/1E3*this.playbackSpeed*this.scenePlaybackSpeed;var c=b/a.totalSeconds,b=Math.floor(c),c=c-b;b!=this.loopCount&&(this.loopCount++,this.loopTurntables&&this.rolloverTurntables(),this.autoAdvanceNextAnimation&&(this.nextAnimation(),this.resetPlayback()));this.totalSeconds=
34288a.totalSeconds*c;this.animationProgress=this.totalSeconds/a.totalSeconds-Math.floor(this.totalSeconds/a.totalSeconds);this.updateScene();this.runDebugMode&&this.checkDebug()}}};
34289SceneAnimator.prototype.updateScene=function(){this.lastSceneFramePercent=this.totalSeconds*this.animations[this.selectedAnimationIndex].originalFPS;0!=this.fogObjectIndex&&this.updateFog();this.animateTurntables&&this.updateTurntables();this.animateMeshes&&this.poseMeshes();this.animateLights&&this.updateLights();this.animateMaterials&&this.updateMaterials();this.animateVisibility&&this.updateVisibility()};
34290SceneAnimator.prototype.findCameraChildren=function(){for(var a=this.animations[0],b=a.animatedObjects.length,c=0;c<b;c++)a.hasParentTypeInHierarchy(a.animatedObjects[c],"CameraSO")&&this.cameraChildrenIndices.push(c)};
34291SceneAnimator.prototype.findFixedTransforms=function(){for(var a=this.animations.length,b=0;b<a;b++)for(var c=this.animations[b],d=c.animatedObjects.length,e=0;e<d;e++){var f=c.animatedObjects[e];if(!f.useFixedWorldTransform&&!c.hasAnimationInHierarchy(f))if("Material"==f.sceneObjectType)f.setFixedWorldTransform(Matrix.identity()),f.setFixedLocalTransform(Matrix.identity());else{var g=Matrix.identity(),h=Matrix.identity();c.hasParentTypeInHierarchy(f,"SceneRootSO")?(c.getWorldTransform(f.id,0,g,this.sceneScale,
34292!1),f.evaluateLocalTransformAtFramePercent(0,h,!0,!1)):(c.evaluateModelPartTransformAtFrame(f.id,0,g,!1),f.evaluateLocalTransformAtFramePercent(0,h,!1,!1));f.setFixedWorldTransform(g);f.setFixedLocalTransform(h)}}};
34293SceneAnimator.prototype.clearCacheForCameraChildren=function(){for(var a=this.animations[this.selectedAnimationIndex],b=this.cameraChildrenIndices.length,c=0;c<b;c++){var d=a.animatedObjects[this.cameraChildrenIndices[c]];d.useFixedWorldTransform=!1;d.useFixedLocalTransform=!1;d.cachedFrame0=-10;d.cachedFrame1=-10;d.cachedFrame2=-10;d.cachedFrame3=-10;d.cachedFrameUse0=0;d.cachedFrameUse1=0;d.cachedFrameUse2=0;d.cachedFrameUse3=0;d.animatedLocalTransform.clearCachedTransforms();d.animatedLocalTransform.lockTransform=
34294!1}};
34295SceneAnimator.prototype.refreshTransformsOnly=function(){for(var a=this.animations[this.selectedAnimationIndex],b=a.meshObjects.length,c=0;c<b;c++){var d=a.meshObjects[c];a.getWorldTransform(d.id,this.totalSeconds,d.mesh.displayMatrix,this.sceneScale,!0);if(this.enableSkinning&&d.skinningRig&&this.unitScaleSkinnedMeshes&&!d.skinningRig.isRigidSkin){var d=d.mesh.displayMatrix,e=d[0],f=d[1],g=d[2],h=d[4],k=d[5],l=d[6],n=d[8],m=d[9],p=d[10],e=Math.sqrt(e*e+f*f+g*g),h=Math.sqrt(h*h+k*k+l*l),n=Math.sqrt(n*n+
34296m*m+p*p),n=(e+h+n)/2;d[0]/=n;d[1]/=n;d[2]/=n;d[4]/=n;d[5]/=n;d[6]/=n;d[8]/=n;d[9]/=n;d[10]/=n}}if(this.animateLights)for(b=a.lightObjects.length,c=0;c<b;c++)d=a.lightObjects[c],d.useFixedWorldTransform||(n=this.scene.lights.getLightPos(d.lightIndex),m=this.scene.lights.getLightDir(d.lightIndex),p=Matrix.identity(),a.getWorldTransform(d.id,this.totalSeconds,p,this.sceneScale,!0),m[0]=p[8],m[1]=p[9],m[2]=p[10],0!=n[3]&&(n[0]=p[12],n[1]=p[13],n[2]=p[14],this.scene.lights.setLightPos(d.lightIndex,n)),
34297this.scene.lights.setLightDir(d.lightIndex,m))};SceneAnimator.prototype.findMeshIndexByPartIndex=function(a,b){for(var c=0;c<this.meshIDs.length;++c)if(b==this.meshIDs[c])return c;return-1};SceneAnimator.prototype.findLightIndexByPartIndex=function(a){for(var b=0;b<this.lightIDs.length;b++)if(a==this.lightIDs[b])return b;return-1};SceneAnimator.prototype.findMaterialIndexByPartIndex=function(a){for(var b=0;b<this.materialIDs.length;b++)if(a==this.materialIDs[b])return b;return-1};
34298SceneAnimator.prototype.nextAnimation=function(){this.selectedAnimationIndex++;this.selectedAnimationIndex>=this.animations.length&&(this.selectedAnimationIndex=0)};SceneAnimator.prototype.selectAnimation=function(a){0<=a&&a<this.animations.length&&(this.selectedAnimationIndex=a);this.paused&&this.setAnimationProgress(this.animationProgress,!0)};
34298SceneAnimator.prototype.selectCamera=function(a){-1!=a&&this.selectedCameraIndex!=a&&(this.selectedCameraIndex=a,this.setViewFromSelectedCamera())};
34299SceneAnimator.prototype.getAnimatedCamera=function(){if(0<=this.selectedCameraIndex&&this.selectedAnimationIndex<this.animations.length){var a=this.animations[this.selectedAnimationIndex];if(this.selectedCameraIndex<a.cameraObjects.length)return a.cameraObjects[this.selectedCameraIndex]}};
34300SceneAnimator.prototype.poseMeshes=function(){for(var a=this.animations[this.selectedAnimationIndex],b=a.meshObjects.length,c=0;c<b;c++){var d=a.meshObjects[c];if(this.enableSkinning&&d.skinningRig)if(d.skinningRig.isRigidSkin)a.getWorldTransform(d.id,this.totalSeconds,d.mesh.displayMatrix,this.sceneScale,!0);else{d.setupSkinningRig(a,d.modelPartIndex,this.totalSeconds,d.skinningRig);a.getWorldTransform(d.id,this.totalSeconds,d.mesh.displayMatrix,this.sceneScale,!0);var e=d.modelPartScale*this.sceneScale;
34301if(this.unitScaleSkinnedMeshes){var f=d.mesh.displayMatrix,g=f[0],h=f[1],k=f[2],l=f[4],n=f[5],m=f[6],p=f[8],r=f[9],s=f[10],g=Math.sqrt(g*g+h*h+k*k),l=Math.sqrt(l*l+n*n+m*m),p=Math.sqrt(p*p+r*r+s*s),p=(g+l+p)/2;f[0]/=p;f[1]/=p;f[2]/=p;f[4]/=p;f[5]/=p;f[6]/=p;f[8]/=p;f[9]/=p;f[10]/=p;e*=p}d.skinningRig.deformMesh(d.mesh,e)}else a.getWorldTransform(d.id,this.totalSeconds,d.mesh.displayMatrix,this.sceneScale,!0)}};
34302SceneAnimator.prototype.updateLights=function(){for(var a=this.animations[this.selectedAnimationIndex],b=this.totalSeconds*a.originalFPS,c=a.lightObjects.length,d=0;d<c;d++){var e=a.lightObjects[d],f=this.scene.lights.getLightPos(e.lightIndex),g=this.scene.lights.getLightDir(e.lightIndex),h=this.scene.lights.getLightColor(e.lightIndex),k=Matrix.identity(),l=1;e.useFixedWorldTransform||a.getWorldTransform(e.id,this.totalSeconds,k,this.sceneScale,!0);e.redProperty&&(e.redProperty.evaluate(b,h[0],e),
34303h[0]=e.redProperty.lastValue);e.greenProperty&&(e.greenProperty.evaluate(b,h[1],e),h[1]=e.greenProperty.lastValue);e.blueProperty&&(e.blueProperty.evaluate(b,h[2],e),h[2]=e.blueProperty.lastValue);e.brightnessProperty&&(e.brightnessProperty.evaluate(b,l,e),l=e.brightnessProperty.lastValue);h[0]*=l;h[1]*=l;h[2]*=l;0!=f[3]&&(e.useFixedWorldTransform||(f[0]=k[12],f[1]=k[13],f[2]=k[14],this.scene.lights.setLightPos(e.lightIndex,f)),e.spotAngleProperty&&0<this.scene.lights.spot[3*e.lightIndex]&&(f=0,e.spotAngleProperty.evaluate(b,
34304f,e),f=e.spotAngleProperty.lastValue,this.scene.lights.setLightSpotAngle(e.lightIndex,f)),e.spotSharpnessProperty&&(f=0,e.spotSharpnessProperty.evaluate(b,f,e),f=e.spotSharpnessProperty.lastValue,this.scene.lights.setLightSpotSharpness(e.lightIndex,f)),e.distanceProperty&&(f=1,e.distanceProperty.evaluate(b,f,e),f=e.distanceProperty.lastValue*this.sceneScale,this.scene.lights.setLightDistance(e.lightIndex,f)));e.useFixedWorldTransform||(g[0]=k[8],g[1]=k[9],g[2]=k[10],this.scene.lights.setLightDir(e.lightIndex,
34305g));this.scene.lights.setLightColor(e.lightIndex,h)}};SceneAnimator.prototype.updateTurntables=function(){for(var a=this.animations[this.selectedAnimationIndex],b=this.totalSeconds*a.originalFPS,c=a.turnTableObjects.length,d=0;d<c;d++){var e=a.turnTableObjects[d];e.spinProperty.evaluate(b,0,e);e.turnTableSpin=e.turnTableSpinOffset+e.spinProperty.lastValue*this.totalSeconds}};
34306SceneAnimator.prototype.rolloverTurntables=function(){for(var a=this.animations[this.selectedAnimationIndex],b=a.turnTableObjects.length,c=0;c<b;c++){var d=a.turnTableObjects[c];d.turnTableSpinOffset=d.turnTableSpin}};
34307SceneAnimator.prototype.updateMaterials=function(){for(var a=this.animations[this.selectedAnimationIndex],b=this.totalSeconds*a.originalFPS,c=a.materialObjects.length,d=0;d<c;d++){var e=a.materialObjects[d];e.offsetUProperty&&(e.offsetUProperty.evaluate(b,0,e),this.scene.materialsList[e.materialIndex].uOffset=e.offsetUProperty.lastValue);e.offsetVProperty&&(e.offsetVProperty.evaluate(b,0,e),this.scene.materialsList[e.materialIndex].vOffset=e.offsetVProperty.lastValue);e.emissiveProperty&&1<e.emissiveProperty.numKeyframes&&
34308(e.emissiveProperty.evaluate(b,0,e),this.scene.materialsList[e.materialIndex].emissiveIntensity=e.emissiveProperty.lastV
34308alue)}};
34309SceneAnimator.prototype.updateFog=function(){var a=this.animations[this.selectedAnimationIndex],b=this.totalSeconds*a.originalFPS;0<=this.fogObjectIndex&&this.fogObjectIndex<a.animatedObjects.length&&this.scene.fog&&(a=a.animatedObjects[this.fogObjectIndex],a.redProperty&&(this.scene.fog.desc.color[0]=a.redProperty.evaluate(b,this.scene.fog.desc.color[0],a)),a.greenProperty&&(this.scene.fog.desc.color[1]=a.greenProperty.evaluate(b,this.scene.fog.desc.color[1],a)),a.blueProperty&&(this.scene.fog.desc.color[2]=
34310a.blueProperty.evaluate(b,this.scene.fog.desc.color[2],a)),a.distanceProperty&&(this.scene.fog.desc.distance=a.distanceProperty.evaluate(b,this.scene.fog.desc.distance,a)),a.opacityProperty&&(this.scene.fog.desc.opacity=a.opacityProperty.evaluate(b,this.scene.fog.desc.opacity,a)),a.skyIllumProperty&&(this.scene.fog.desc.skyIllum=a.skyIllumProperty.evaluate(b,this.scene.fog.desc.skyIllum,a)),a.lightIllumProperty&&(this.scene.fog.desc.lightIllum=a.lightIllumProperty.evaluate(b,this.scene.fog.desc.lightIllum,
34311a)),a.dispersionProperty&&(this.scene.fog.desc.dispersion=a.dispersionProperty.evaluate(b,this.scene.fog.desc.dispersion,a)))};
34312SceneAnimator.prototype.updateVisibility=function(){for(var a=this.animations[this.selectedAnimationIndex],b=this.subMeshObjectIndices.length,c=0;c<b;c++){var d=this.subMeshLiveIndices[c];if(-1!=d){var e=this.subMeshObjectIndices[c],d=this.scene.meshRenderables[d],f=a.getObjectAnimationFramePercent(a.animatedObjects[e],this.totalSeconds);d.visible=a.isVisibleAtFramePercent(e,f)}}};function Shader(a){this.gl=a;this.program=null;this.params={};this.samplers={};this.attribs={}}
34313Shader.prototype.build=function(a,b){var c=this.gl;this.program=c.createProgram();this.params={};this.samplers={};this.attribs={};var d=function(a){for(var b="",c=a.indexOf("\n"),d=0;-1!=c;)d++,b+=d+": ",b+=a.substring(0,c+1),a=a.substring(c+1,a.length),c=a.indexOf("\n");console.log(b)},e=c.createShader(c.VERTEX_SHADER);c.shaderSource(e,a);c.compileShader(e);c.getShaderParameter(e,c.COMPILE_STATUS)||(console.log(c.getShaderInfoLog(e)),marmoset.verboseErrors&&d(a));c.attachShader(this.program,e);e=
34314c.createShader(c.FRAGMENT_SHADER);c.shaderSource(e,b);c.compileShader(e);c.getShaderParameter(e,c.COMPILE_STATUS)||(console.log(c.getShaderInfoLog(e)),marmoset.verboseErrors&&d(b));c.attachShader(this.program,e);c.linkProgram(this.program);c.getProgramParameter(this.program,c.LINK_STATUS)||console.log(c.getProgramInfoLog(this.program));for(var e=c.getProgramParameter(this.program,c.ACTIVE_UNIFORMS),f=0,d=0;d<e;++d){var g=c.getActiveUniform(this.program,d),h=g.name,k=h.indexOf("[");0<=k&&(h=h.substring(0,
34315k));k=c.getUniformLocation(this.program,g.name);g.type==c.SAMPLER_2D||g.type==c.SAMPLER_CUBE?this.samplers[h]={location:k,unit:f++}:this.params[h]=k}e=c.getProgramParameter(this.program,c.ACTIVE_ATTRIBUTES);for(d=0;d<e;++d)f=c.getActiveAttrib(this.program,d),this.attribs[f.name]=c.getAttribLocation(this.program,f.name)};Shader.prototype.bind=function(){return this.program?(this.gl.useProgram(this.program),!0):!1};Shader.prototype.complete=function(){return!!this.program};function ShaderCache(a){this.gl=a;this.cache=[]}ShaderCache.prototype.fromURLs=function(a,b,c){var d="";if(c)for(var e=0;e<c.length;++e)d=c[e]+"\n"+d;c=d+":"+a+"|"+b;e=this.cache[c];if(void 0!==e)return e;var f=new Shader(this.gl),g=null,h=null,k=function(){null!=g&&null!=h&&f.build(g,h)};this.fetch(a,function(a){g=d+a;k()});this.fetch(b,function(a){h=d+a;k()});return this.cache[c]=f};
34316ShaderCache.prototype.fetch=function(a,b){"undefined"!=typeof ShaderTable?void 0!==ShaderTable[a]?this.resolveIncludes(new String(ShaderTable[a]),b):b(""):Network.fetchText("src/shader/"+a,function(a){this.resolveIncludes(a,b)}.bind(this),function(){b("")})};
34317ShaderCache.prototype.resolveIncludes=function(a,b){for(var c=[],d=!0,e=function(a,b,e,f,n){d=!0;c.push({offset:n,path:b.slice(1,b.length-1)});return""};d;)d=!1,a=a.replace(/#include\s((<[^>]+>)|("[^"]+"))/,e);if(0<c.length)for(var f=c.length,e=0;e<c.length;++e)this.fetch(c[e].path,function(d){this.src=d;if(0>=--f){for(d=c.length-1;0<=d;--d)a=a.substring(0,c[d].offset)+c[d].src+a.substring(c[d].offset);b(a)}}.bind(c[e]));else b(a)};function ShadowCollector(a,b){this.gl=a;this.shadowCount=b;this.nativeDepth=!!a.ext.textureDepth;this.desc=c;c=this.nativeDepth?["#define SHADOW_NATIVE_DEPTH"]:[];this.shaderSolid=a.shaderCache.fromURLs("shadowvert.glsl","shadowfrag.glsl",c);c.push("#define ALPHA_TEST 1");this.shaderAlphaTest=a.shaderCache.fromURLs("shadowvert.glsl","shadowfrag.glsl",c);this.depthTextures=[];this.depthTargets=[];if(0<this.shadowCount){var c={width:2048,height:2048,clamp:!0,mipmap:!1,nofilter:!0};a.hints.mobile&&(c.width=
34318c.height=1536);var d={width:c.width,height:c.height},e,f;this.nativeDepth?(e=a.DEPTH_COMPONENT,f=a.UNSIGNED_SHORT):(d.depthBuffer=Framebuffer.createDepthBuffer(a,c.width,c.height),e=a.RGB,f=a.UNSIGNED_BYTE);for(var g=0;g<this.shadowCount;++g)this.depthTextures[g]=new Texture(a,c),this.depthTextures[g].loadArray(null,e,f),this.nativeDepth?d.depth=this.depthTextures[g]:d.color0=this.depthTextures[g],this.depthTargets[g]=new Framebuffer(a,d)}}
34319ShadowCollector.prototype.bindDepthTexture=function(a,b){this.shadowCount>b&&this.depthTextures[b].bind(a)};
34320ShadowCollector.prototype.collect=function(a,b){for(var c=this.gl,d=a.lights,e=d.shadowCount,f=d.modelViewBuffer,g=d.projectionBuffer,h=d.matrix,k=0!=a.sceneAnimator,l=Matrix.empty(),n=!1,m=0;m<e;++m)if(d.shadowsNeedUpdate[m]){d.shadowsNeedUpdate[m]=0;n=!0;Matrix.mul(l,f.subarray(16*m,16*(m+1)),h);Matrix.mul(l,g.subarray(16*m,16*(m+1)),l);this.depthTargets[m].bind();c.clearColor(1,1,1,1);c.clear(c.COLOR_BUFFER_BIT|c.DEPTH_BUFFER_BIT);var p=this.shaderSolid;p.bind();c.uniformMatrix4fv(p.params.uViewProjection,
34321!1,l);c.uniformMatrix4fv(p.params.uMeshTransform,!1,Matrix.identity());for(var r=0;r<a.meshRenderables.length;++r){var s=a.meshRenderables[r],u=s.material;!s.mesh.desc.castShadows||!u.castShadows||0<u.shadowAlphaTest||(k&&c.uniformMatrix4fv(p.params.uMeshTransform,!1,s.mesh.displayMatrix),s.drawShadow(p.attribs.vPosition))}p=this.shaderAlphaTest;p.bind();c.uniformMatrix4fv(p.params.uViewProjection,!1,l);c.uniformMatrix4fv(p.params.uMeshTransform,!1,Matrix.identity());for(r=0;r<a.meshRenderables.length;++r)s=
34322a.meshRenderables[r],u=s.material,s.mesh.desc.castShadows&&u.castShadows&&0<u.shadowAlphaTest&&(u.textures.albedo.bind(p.samplers.tAlbedo),k&&
34322(c.uniform2f(p.params.uUVOffset,u.uOffset,u.vOffset),c.uniformMatrix4fv(p.params.uMeshTransform,!1,s.mesh.displayMatrix)),s.drawAlphaShadow(p.attribs.vPosition,p.attribs.vTexCoord))}n&&(b.bind(),c.enable(c.CULL_FACE),c.cullFace(c.BACK))};ShadowCollector.prototype.complete=function(){return this.shaderSolid.complete()&&this.shaderAlphaTest.complete()};function ShadowFloor(a,b,c,d){this.gl=a;this.desc=b;this.lightCount=d.count;this.shadowCount=c.shadowCount;b=this.nativeDepth?["#define SHADOW_NATIVE_DEPTH"]:[];b.push("#define LIGHT_COUNT "+this.lightCount);b.push("#define SHADOW_COUNT "+this.shadowCount);a.hints.mobile&&b.push("#define MOBILE");this.shader=a.shaderCache.fromURLs("shadowfloorvert.glsl","shadowfloorfrag.glsl",b);b=new Float32Array([-1,0,-1,-1,0,1,1,0,1,-1,0,-1,1,0,1,1,0,-1]);this.quadGeom=a.createBuffer();a.bindBuffer(a.ARRAY_BUFFER,
34323this.quadGeom);a.bufferData(a.ARRAY_BUFFER,b,a.STATIC_DRAW);a.bindBuffer(a.ARRAY_BUFFER,null)}
34324ShadowFloor.prototype.draw=function(a){var b=a.view,c=a.lights,d=a.shadow,e=this.gl,f=this.shader.params,g=this.shader.samplers;this.shader.bind();b=Matrix.mul(Matrix.empty(),b.projectionMatrix,b.viewMatrix);Matrix.mul(b,b,this.desc.transform);e.uniformMatrix4fv(f.uModelViewProjectionMatrix,!1,b);b=Matrix.mul(Matrix.empty(),c.matrix,this.desc.transform);e.uniformMatrix4fv(f.uModelSkyMatrix,!1,b);0<c.count&&(e.uniform4fv(f.uLightPositions,c.positionBuffer),e.uniform3fv(f.uLightDirections,c.directionBuffer),
34325e.uniform3fv(f.uLightColors,c.colors),e.uniform3fv(f.uLightParams,c.parameters),e.uniform3fv(f.uLightSpot,c.spot),a=0.392699*a.postRender.currentSample(),e.uniform2f(f.uShadowKernelRotation,0.5*Math.cos(a),0.5*Math.sin(a)),0<c.shadowCount&&(a=d.depthTextures[0].desc.width,e.uniform2f(f.uShadowMapSize,a,1/a),e.uniformMatrix4fv(f.uShadowMatrices,!1,c.finalTransformBuffer),e.uniformMatrix4fv(f.uInvShadowMatrices,!1,c.inverseTransformBuffer),e.uniform4fv(f.uShadowTexelPadProjections,c.shadowTexelPadProjections),
34326d.bindDepthTexture(g.tDepth0,0),d.bindDepthTexture(g.tDepth1,1),d.bindDepthTexture(g.tDepth2,2)));e.uniform3f(f.uShadowCatcherParams,this.desc.simple?1:0,this.desc.alpha,this.desc.edgeFade);e.depthMask(!1);e.enable(e.BLEND);e.blendFunc(e.ZERO,e.SRC_COLOR);c=this.shader.attribs.vPosition;e.bindBuffer(e.ARRAY_BUFFER,this.quadGeom);e.enableVertexAttribArray(c);e.vertexAttribPointer(c,3,e.FLOAT,!1,0,0);e.drawArrays(e.TRIANGLES,0,6);e.disableVertexAttribArray(c);e.bindBuffer(e.ARRAY_BUFFER,null);e.disable(e.BLEND);
34327e.depthMask(!0)};ShadowFloor.prototype.complete=function(){return this.shader.complete()};function Skin(a){this.numVertices=a.vertexCount;this.vertices=new Float32Array(a.vertexCount);a.vertexBuffer||(this.numVertices=0)};function SkinningCluster(){this.associateObjectIndex=this.linkObjectIndex=this.linkMode=0;this.vertexIndices=[];this.vertexWeights=[];this.matrix=Matrix.identity();this.defaultAssociateWorldTransform=this.defaultClusterWorldTransform=this.defaultClusterBaseTransform=0;this.defaultClusterWorldTransformInvert=Matrix.identity();this.defaultAssociateWorldTransformInvert=Matrix.identity();this.debugString=""}
34328SkinningCluster.prototype.solveAdditiveClusterTransform=function(a,b,c){b=Matrix.identity();var d=Matrix.identity(),e=Matrix.identity();Matrix.mul(b,a,this.defaultClusterBaseTransform);Matrix.mul(d,this.defaultAssociateWorldTransformInvert,b);Matrix.mul(e,this.defaultAssociateWorldTransformInvert,d);Matrix.mul(c,this.defaultClusterWorldTransformInvert,e)};
34329SkinningCluster.prototype.solveSimpleClusterTransform=function(a,b,c){var d=Matrix.identity(),e=Matrix.identity();Matrix.invert(e,b);Matrix.mul(d,e,a);Matrix.mul(c,d,this.defaultClusterBaseTransform)};
34330SkinningCluster.prototype.solveClusterTransformAtFrame=function(a,b,c,d){if(1==this.linkMode){var e=Matrix.identity();b=Matrix.identity();a.evaluateModelPartTransformAtFrame(this.linkObjectIndex,c,e,!1);a.evaluateModelPartTransformAtFrame(this.associateObjectIndex,c,b,!1);this.solveAdditiveClusterTransform(e,b,d)}else{var e=Matrix.identity(),f=Matrix.identity();a.evaluateModelPartTransformAtFrame(this.linkObjectIndex,c,e,!1);a.evaluateModelPartTransformAtFrame(b,c,f,!1);this.solveSimpleClusterTransform(e,
34331f,d)}};function SkinningRig(a,b,c){this.debugString="";
34331this.skinningClusters=[];this.srcVFile=b.file;if(a=a.get(this.srcVFile))if(a.data){this.rigByteStream=new ByteStream(a.data);a=new Uint32Array(this.rigByteStream.bytes.buffer,0,this.rigByteStream.bytes.length/4);this.expectedNumClusters=a[0];this.expectedNumVertices=a[1];this.numClusterLinks=a[2];this.originalObjectIndex=a[3];this.isRigidSkin=a[4];this.tangentMethod=a[5];b=6+7*this.expectedNumClusters;for(var d=0;d<this.expectedNumClusters;d++){var e=
34332new SkinningCluster;this.skinningClusters.push(e);var f=6+7*d;e.linkMode=a[f+1];e.linkObjectIndex=a[f+2];e.associateObjectIndex=a[f+3];var g=a[f+5];e.defaultClusterWorldTransform=c.getMatrix(a[f+4]);e.defaultClusterBaseTransform=c.getMatrix(g);Matrix.invert(e.defaultClusterWorldTransformInvert,e.defaultClusterWorldTransform);1==e.linkMode&&(e.defaultAssociateWorldTransform=c.getMatrix(a[f+6]),Matrix.invert(e.defaultAssociateWorldTransformInvert,e.defaultAssociateWorldTransform))}c=4*b;b=c+this.expectedNumVertices;
34333a=b+2*this.numClusterLinks;b=new Uint8Array(this.rigByteStream.bytes.subarray(b));a=new Uint8Array(this.rigByteStream.bytes.subarray(a));this.linkMapCount=new Uint8Array(this.rigByteStream.bytes.buffer,c,this.expectedNumVertices);this.linkMapClusterIndices=new Uint16Array(b.buffer);this.linkMapWeights=new Float32Array(a.buffer)}else this.debugString+="<br>No data in "+this.srcVFile;else this.debugString+="<br>Error loading buffer for skinning rig "+this.srcVFile}
34334SkinningRig.prototype.unpackUnitVectors=function(a,b,c,d){for(var e=0;e<c;e++){var f=b[d*e],g=b[d*e+1],h=32768<=g;h&&(g-=32768);var f=f/32767.4*2-1,g=g/32767.4*2-1,k=1-(f*f+g*g),k=Math.sqrt(k),k=isNaN(k)?0:k;h&&(k=-k);a[3*e]=f;a[3*e+1]=g;a[3*e+2]=k}};
34335SkinningRig.prototype.copyOriginalVertices=function(a){if(!this.unTransformedVertices)if(this.unTransformedVertices=new Float32Array(3*a.vertexCount),this.unTransformedNormals=new Float32Array(3*a.vertexCount),this.unTransformedTangents=new Float32Array(3*a.vertexCount),this.unTransformedBiTangents=new Float32Array(3*a.vertexCount),this.skinVertexWeights=new Float32Array(a.vertexCount),this.skinVertexTransform4x3=new Float32Array(12),a.dynamicVertexData){var b=new Float32Array(a.dynamicVertexData.buffer);
34336new Uint8Array(a.dynamicVertexData.buffer);var c=0,d=c,c=c+12+8;a.secondaryTexCoord&&(c+=8);var e=c,f=c+=4,c=c+4,g=a.stride/2,c=new Uint8Array(a.dynamicVertexData.subarray(c)),c=new Uint16Array(c.buffer),e=new Uint8Array(a.dynamicVertexData.subarray(e)),e=new Uint16Array(e.buffer),f=new Uint8Array(a.dynamicVertexData.subarray(f)),f=new Uint16Array(f.buffer);this.unpackUnitVectors(this.unTransformedNormals,c,a.vertexCount,g);this.unpackUnitVectors(this.unTransformedTangents,e,a.vertexCount,g);this.unpackUnitVectors(this.unTransformedBiTangents,
34337f,a.vertexCount,g);for(g=0;g<a.vertexCount;g++)f=(a.stride*g+d)/4,this.unTransformedVertices[3*g]=b[f],this.unTransformedVertices[3*g+1]=b[f+1],this.unTransformedVertices[3*g+2]=b[f+2]}else this.debugString+="<br>Can't init skinning rig - mesh buffer is not dynamic - rigid is "+this.isRigidSkin};SkinningRig.prototype.useOriginalMeshVertices=function(a){this.isRigidSkin?this.debugString+="<br>useOriginalMeshVertices for rigid skin?":this.copyOriginalVertices(a)};
34338SkinningRig.prototype.deformMeshVertices=function(a,b){if(0!=this.skinningClusters.length&&this.unTransformedVertices){var c=a.stride/4,d=new Float32Array(a.dynamicVertexData.buffer),e=new Uint16Array(a.dynamicVertexData.buffer);new Uint8Array(a.dynamicVertexData.buffer);var f;f=20;a.secondaryTexCoord&&(f+=8);var g=f,h=f+=4;f+=4;for(var k=this.unTransformedVertices.length/3,l=0,n=0;n<k;++n){var m=n,p=(m*a.stride+g)/2,r=(m*a.stride+h)/2,s=(m*a.stride+f)/2,u=this.linkMapCount[m],q=this.skinVertexTransform4x3;
34339this.skinVertexWeights[m]=0;q[0]=0;q[1]=0;q[2]=0;q[3]=0;q[4]=0;q[5]=0;q[6]=0;q[7]=0;q[8]=0;q[9]=0;q[10]=0;q[11]=0;var x=this.linkMapWeights[l];if(1==u&&1==x){var w=this.linkMapClusterIndices[l],w=this.skinningClusters[w],v=w.matrix;q[0]=v[0];q[1]=v[1];q[2]=v[2];q[3]=v[4];q[4]=v[5];q[5]=v[6];q[6]=v[8];q[7]=v[9];q[8]=v[10];q[9]=v[12];q[10]=v[13];q[11]=v[14];this.skinVertexWeights[m]=1}else for(var t=this.skinVertexWeights[m]=0;t<u;t++)x=this.linkMapWeights[l+t],w=this.linkMapClusterIndices[l+t],w<this.skinningCluster
34339s.length&&
34340(w=this.skinningClusters[w],v=w.matrix,q[0]+=x*v[0],q[1]+=x*v[1],q[2]+=x*v[2],q[3]+=x*v[4],q[4]+=x*v[5],q[5]+=x*v[6],q[6]+=x*v[8],q[7]+=x*v[9],q[8]+=x*v[10],q[9]+=x*v[12],q[10]+=x*v[13],q[11]+=x*v[14],this.skinVertexWeights[m]+=x,1==w.linkMode&&(this.skinVertexWeights[m]=1));l+=this.linkMapCount[n];if(0<this.skinVertexWeights[m]){var y=this.unTransformedVertices[3*n+0],E=this.unTransformedVertices[3*n+1],F=this.unTransformedVertices[3*n+2],A=this.unTransformedNormals[3*n+0],B=this.unTransformedNormals[3*
34341n+1],z=this.unTransformedNormals[3*n+2],w=this.unTransformedTangents[3*n+0],v=this.unTransformedTangents[3*n+1],C=this.unTransformedTangents[3*n+2],u=this.unTransformedBiTangents[3*n+0],q=this.unTransformedBiTangents[3*n+1],x=this.unTransformedBiTangents[3*n+2],t=this.skinVertexTransform4x3,G=1;0<this.skinVertexWeights[m]&&(G=1/this.skinVertexWeights[m]);d[c*n]=G*(y*t[0]+E*t[3]+F*t[6]+t[9])*b;d[c*n+1]=G*(y*t[1]+E*t[4]+F*t[7]+t[10])*b;d[c*n+2]=G*(y*t[2]+E*t[5]+F*t[8]+t[11])*b;y=A*t[0]+B*t[3]+z*t[6];
34342m=A*t[1]+B*t[4]+z*t[7];A=A*t[2]+B*t[5]+z*t[8];B=w*t[0]+v*t[3]+C*t[6];z=w*t[1]+v*t[4]+C*t[7];w=w*t[2]+v*t[5]+C*t[8];v=u*t[0]+q*t[3]+x*t[6];C=u*t[1]+q*t[4]+x*t[7];u=u*t[2]+q*t[5]+x*t[8];q=Math.sqrt(y*y+m*m+A*A);y/=q;m/=q;A/=q;q=32767.4*(y/2+0.5);x=32767.4*(m/2+0.5);0>A&&(x+=32768);e[s]=Math.floor(q);e[s+1]=Math.floor(x);q=Math.sqrt(B*B+z*z+w*w);B/=q;z/=q;w/=q;q=32767.4*(B/2+0.5);x=32767.4*(z/2+0.5);0>w&&(x+=32768);e[p]=Math.floor(q);e[p+1]=Math.floor(x);q=Math.sqrt(v*v+C*C+u*u);v/=q;C/=q;u/=q;q=32767.4*
34343(v/2+0.5);x=32767.4*(C/2+0.5);0>u&&(x+=32768);e[r]=Math.floor(q);e[r+1]=Math.floor(x)}else y=this.unTransformedVertices[3*n+0],E=this.unTransformedVertices[3*n+1],F=this.unTransformedVertices[3*n+2],d[c*n]=y*b,d[c*n+1]=E*b,d[c*n+2]=F*b}}};
34344SkinningRig.prototype.deformMesh=function(a,b){if(0!=this.skinningClusters.length&&!this.isRigidSkin){this.deformMeshVertices(a,b);var c=a.gl;c.bindBuffer(c.ARRAY_BUFFER,a.vertexBuffer);c.bufferData(c.ARRAY_BUFFER,a.dynamicVertexData,c.DYNAMIC_DRAW);c.bindBuffer(c.ARRAY_BUFFER,null)}};function Sky(a,b,c){this.gl=a;var d=b.extract("sky.dat")||b.extract("sky.png");if(void 0!==d){this.specularTexture=new Texture(a,{width:256,height:2048,clamp:!0});b=d.data;for(var d=d.data.length,e=d/4,f=new Uint8Array(d),g=0,h=0;g<d;++h)f[g++]=b[h+2*e],f[g++]=b[h+e],f[g++]=b[h],f[g++]=b[h+3*e];this.specularTexture.loadArray(f)}this.diffuseCoefficients=new Float32Array(c.diffuseCoefficients);this.backgroundMode=c.backgroundMode||0;this.backgroundBrightness=c.backgroundBrightness||1;this.backgroundColor=
34345new Float32Array(c.backgroundColor);if(1<=this.backgroundMode)if(this.backgroundShader=a.shaderCache.fromURLs("skyvert.glsl",3==this.backgroundMode?"skySH.glsl":"sky.glsl",["#define SKYMODE "+this.backgroundMode]),this.vertexBuffer=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffer),c=1/256,b=0.5/256,d=2.8*b,e=0.5*b,c=new Float32Array([0,1,0,0.49609375+c,0.49609375+c,1,0,0,0.9921875+c,0.49609375+c,0,0,1,0.49609375+c,0.9921875+c,-1,0,0,0+c,0.49609375+c,0,0,-1,0.49609375+c,0+c,0,-1,0,0.9921875+
34346c,0+c,0,-1,0,0.9921875+c,0.9921875+c,0,-1,0,0+c,0.9921875+c,0,-1,0,0+c,0+c,d,1-d,-d,0.5+b,0.5-b,d,1-d,d,0.5+b,0.5+b,-d,1-d,d,0.5-b,0.5+b,-d,1-d,-d,0.5-b,0.5-b,-d,0,-1+d,0.5-b,0+c+b,d,0,-1+d,0.5+b,0+c+b,1-d,0,-d,0.9921875+c-b,0.5-b,1-d,0,d,0.9921875+c-b,0.5+b,d,0,1-d,0.5+b,0.9921875+c-b,-d,0,1-d,0.5-b,0.9921875+c-b,-1+d,0,d,0+c+b,0.5+b,-1+d,0,-d,0+c+b,0.5-b,1,0,0,0.9921875+c-e,0.49609375+c,0,0,1,0.49609375+c,0.9921875+c-e,-1,0,0,0+c+e,0.49609375+c,0,0,-1,0.49609375+c,0+c+e,0,1,0,0.49609375+c-e,0.49609375+
34347c,0,1,0,0.49609375+c,0.49609375+c-e,0,1,0,0.49609375+c+e,0.49609375+c,0,1,0,0.49609375+c,0.49609375+c+e]),a.bufferData(a.ARRAY_BUFFER,c,a.STATIC_DRAW),a.bindBuffer(a.ARRAY_BUFFER,null),this.indexBuffer=a.createBuffer(),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer),c=new Uint16Array([2,1,6,3,2,7,8,4,3,4,5,1,9,14,15,17,10,16,18,19,11,20,13,12,28,12,13,13,24,28,28,24,9,9,24,14,25,9,15,25,15,21,10,25,21,10,21,16,22,26,10,22,10,17,18,11,26,22,18,26,19,23,27,19,27,11,23,20,27,27,20,12]),this.skyIndexCount=
34348c.length,a.bufferData(a.ELEMENT_ARRAY_BUFFER,c,a.STATIC_DRAW),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,null),3==this.backgroundMode)for(this.backgroundCoefficients=new Float32Array(this.diffuseCoefficients),g=0;g<this.backgroundCoefficients.length;++g)this.backgroundCoefficients[g]*=this.backgroundBrightness;else{this.backgroundTexture=new Texture(a,{width:256,height:256,clamp:!0});c=!1;var k;a.ext.textureHalf&&a.ext.textureHalfLinear&&(this.backgroundTexture.loadArray(null,a.RGB,a.ext.textureHalf.HALF_FLOAT_OES),
34349k=new Framebuffer(a,{color0:this.backgroundTexture}),c=k.valid);!c&&a.ext.textureFloat&&a.ext.textureFloatLinear&&!a.hints.mobile&&(this.backgroundTexture.loadArray(null,a.RGB,a.FLOAT),k=new Framebuffer(a,{color0:this.backgroundTexture}),c=k.valid);c||(this.backgroundTexture.loadArray(),k=new Framebuffer(a,{color0:this.backgroundTexture}));k.bind();k=new Shader(a);k.build("precision highp float; varying vec2 tc; attribute vec4 p; void main(){ gl_Position=p; tc=vec2(0.5,0.5/8.0)*p.xy+vec2(0.5,6.5/8.0); }",
34350"precision highp float; varying vec2 tc; uniform sampler2D tex; uniform float b; void main(){vec4 s=texture2D(tex,tc); gl_FragColor.xyz=s.xyz*(b*s.w);}");k.bind();a.uniform1f(k.params.b,7*Math.sqrt(this.backgroundBrightness));this.specularTexture.bind(k.samplers.tex);c=a.createBuffer();a.bindBuffer(a.ARRAY_BUFFER,c);c=new Float32Array([-1,-1,0.5,1,3,-1,0.5,1,-1,3,0.5,1]);a.bufferData(a.ARRAY_BUFFER,c,a.STATIC_DRAW);a.enableVertexAttribArray(k.attribs.p);a.vertexAttribPointer(k.attribs.p,4,a.FLOAT,
34351!1,0,0);a.drawArrays(a.TRIANGLES,0,3);a.disableVertexAttribArray(k.attribs.p)}}Sky.prototype.setClearColor=function(){if(marmoset.transparentBackgroun
34351d)this.gl.clearColor(0,0,0,0);else if(1>this.backgroundMode){var a=this.backgroundColor;this.gl.clearColor(a[0],a[1],a[2],1)}else this.gl.clearColor(0.0582,0.06772,0.07805,1)};
34352Sky.prototype.draw=function(a){if(1>this.backgroundMode||marmoset.transparentBackground)return!1;if(this.complete()){var b=this.gl,c=this.backgroundShader,d=a.view,e=a.lights.invMatrix;c.bind();b.uniformMatrix4fv(c.params.uInverseSkyMatrix,!1,e);b.uniformMatrix4fv(c.params.uViewProjection,!1,d.viewProjectionMatrix);3==this.backgroundMode?b.uniform4fv(c.params.uSkyCoefficients,this.backgroundCoefficients):this.backgroundTexture.bind(c.samplers.tSkyTexture);a=0.07+0.94*(1-a.stripData.activeFade());
34353b.uniform1f(c.params.uAlpha,a);b.bindBuffer(b.ARRAY_BUFFER,this.vertexBuffer);b.enableVertexAttribArray(c.attribs.vPosition);b.vertexAttribPointer(c.attribs.vPosition,3,b.FLOAT,!1,20,0);b.enableVertexAttribArray(c.attribs.vTexCoord);b.vertexAttribPointer(c.attribs.vTexCoord,2,b.FLOAT,!1,20,12);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,this.indexBuffer);1>a&&(b.enable(b.BLEND),b.blendFunc(b.SRC_ALPHA,b.ONE_MINUS_SRC_ALPHA));b.depthMask(!1);b.disable(b.DEPTH_TEST);b.drawElements(b.TRIANGLES,this.skyIndexCount,
34354b.UNSIGNED_SHORT,0);b.enable(b.DEPTH_TEST);b.depthMask(!0);1>a&&b.disable(b.BLEND);b.disableVertexAttribArray(c.attribs.vPosition);b.disableVertexAttribArray(c.attribs.vTexCoord)}};Sky.prototype.complete=function(){return this.backgroundShader&&!this.backgroundShader.complete()?!1:this.specularTexture.complete()};function StripData(){this.STRIP_NONE=-2;this.STRIP_MENU=-1;this.stripCount=5;this.strips=[0,0,0,0,0];this.labels=["Normals","Albedo","Reflectivity","Gloss","Topology"];this.stripSlant=0.25;this.selectedStrip=this.STRIP_NONE;this.animationActive=!1;this.timestamp=Date.now();this.update(!0)}StripData.expDecay=function(a,b){return Math.exp(-0.69314718/a*b)};
34355StripData.prototype.update=function(a){var b=0.001*(Date.now()-this.timestamp);this.timestamp=Date.now();for(var c=!1,d=0;d<this.stripCount;++d){var e=0,e=this.selectedStrip==this.STRIP_MENU?-0.9+0.3*(d+1):0>this.selectedStrip||d<this.selectedStrip?-2:2;if(a)this.strips[d]=e;else{var f=e-this.strips[d],f=f*StripData.expDecay(0.05,b);this.animationActive&&(this.strips[d]=e-f);c=c||0.001<Math.abs(f)}}this.animationActive=c};StripData.prototype.active=function(){return this.selectedStrip>=this.STRIP_MENU};
34356StripData.prototype.activeFade=function(){var a=(this.strips[this.stripCount-1]- -2)/(-0.9+0.3*this.stripCount- -2),a=1<a?1:a;return 0>a?0:a};StripData.prototype.activeWireframe=function(){return this.active()&&0.01<Math.abs(this.strips[4]-this.strips[3])};StripData.prototype.toggleMenu=function(){this.selectedStrip=this.selectedStrip==this.STRIP_MENU?this.STRIP_NONE:this.STRIP_MENU};
34357StripData.prototype.selectStrip=function(a,b){if(this.selectedStrip==this.STRIP_MENU){var c=a+b*this.stripSlant;this.selectedStrip=this.STRIP_NONE;for(var d=0;d<this.stripCount;++d)if(c<this.strips[d]){this.selectedStrip=d;break}}else this.selectedStrip=this.STRIP_MENU};function Texture(a,b){this.gl=a;this.id=null;this.type=a.TEXTURE_2D;this.format=a.RGBA;this.componentType=a.UNSIGNED_BYTE;b=b||{};this.desc={width:b.width||1,height:b.height||1,mipmap:b.mipmap,clamp:b.clamp,mirror:b.mirror,aniso:b.aniso,nofilter:b.nofilter}}
34358Texture.prototype.loadImage=function(a,b){var c=this.gl;a&&a.width&&a.height&&(this.desc.width=a.width,this.desc.height=a.height);this.id=c.createTexture();c.bindTexture(this.type,this.id);this.format=b||c.RGBA;this.componentType=c.UNSIGNED_BYTE;c.pixelStorei(c.UNPACK_FLIP_Y_WEBGL,!0);c.texImage2D(this.type,0,this.format,this.format,this.componentType,a);this.setParams();c.bindTexture(this.type,null)};
34359Texture.prototype.loadArray=function(a,b,c){var d=this.gl;this.id=d.createTexture();d.bindTexture(this.type,this.id);this.format=b||d.RGBA;this.componentType=c||d.UNSIGNED_BYTE;d.pixelStorei(d.UNPACK_FLIP_Y_WEBGL,!0);d.texImage2D(this.type,0,this.format,this.desc.width,this.desc.height,0,this.format,this.componentType,a||null);this.setParams();d.bindTexture(this.type,null)};
34360Texture.prototype.setParams=function(){var a=this.gl,b=function(a){return 0<a&&0==(a&a-1)};b(this.desc.width)&&b(this.desc.height)||(this.desc.clamp=!0,this.desc.mipmap=!1);b=!this.desc.nofilter;this.desc.mipmap?(a.generateMipmap(this.type),a.texParameteri(this.type,a.TEXTURE_MIN_FILTER,b?a.LINEAR_MIPMAP_LINEAR:a.NEAREST_MIPMAP_NEAREST)):a.texParameteri(this.type,a.TEXTURE_MIN_FILTER,b?a.LINEAR:a.NEAREST);a.texParameteri(this.type,a.TEXTURE_MAG_FILTER,b?a.LINEAR:a.NEAREST);if(this.desc.clamp||this.desc.mirror)b=
34361this.desc.clamp?a.CLAMP_TO_EDGE:a.MIRRORED_REPEAT,a.texParameteri(this.type,a.TEXTURE_WRAP_S,b),a.texParameteri(this.type,a.TEXTURE_WRAP_T,b);this.desc.aniso&&a.ext.textureAniso&&a.texParameteri(this.type,a.ext.textureAniso.TEXTURE_MAX_ANISOTROPY_EXT,this.desc.aniso)};
34361Texture.prototype.rebuildMips=function(){this.desc.mipmap&&(this.gl.bindTexture(this.type,this.id),this.gl.generateMipmap(this.type))};
34362Texture.prototype.copyColorBuffer=function(){this.gl.bindTexture(this.type,this.id);this.gl.copyTexSubImage2D(this.type,0,0,0,0,0,this.desc.width,this.desc.height)};Texture.prototype.bind=function(a){if(a){var b=this.gl;b.uniform1i(a.location,a.unit);b.activeTexture(b.TEXTURE0+a.unit);b.bindTexture(this.type,this.id)}};Texture.prototype.destroy=function(){this.gl.deleteTexture(this.id);this.id=null};Texture.prototype.complete=function(){return!!this.id};function TextureCache(a){this.gl=a;this.cache=[]}TextureCache.prototype.fromURL=function(a,b){var c=this.cache[a];if(void 0!==c)return c;var d=new Texture(this.gl,b);Network.fetchImage(a,function(a){d.loadImage(a)});return this.cache[a]=d};TextureCache.prototype.fromFile=function(a,b){if(!a)return null;var c=this.cache[a.name];if(void 0!==c)return c;var d=new Texture(this.gl,b);this.cache[a.name]=d;TextureCache.parseFile(a,function(a){d.loadImage(a);TextureCache.closeImage(a)});return d};
34363TextureCache.prototype.fromFilesMergeAlpha=function(a,b,c){if(!b)return this.fromFile(a,c);var d=a.name+"|"+b.name,e=this.cache[d];if(void 0!==e)return e;var f=this.gl;this.blitShader||(this.blitShader=new Shader(this.gl),this.blitShader.build("precision highp float; varying vec2 c; attribute vec2 pos; void main(){ gl_Position.xy = 2.0*pos-vec2(1.0); gl_Position.zw = vec2(0.5,1.0); c=pos; }","precision highp float; varying vec2 c; uniform sampler2D tTex; void main(){ gl_FragColor=texture2D(tTex,c).rgbr; }"),
34364this.mergeVerts=f.createBuffer(),f.bindBuffer(f.ARRAY_BUFFER,this.mergeVerts),e=new Float32Array([0,0,2,0,0,2]),f.bufferData(f.ARRAY_BUFFER,e,f.STATIC_DRAW),f.bindBuffer(f.ARRAY_BUFFER,null));var g=function(a){this.blitShader.bind();a.bind(this.blitShader.samplers.tTex);f.bindBuffer(f.ARRAY_BUFFER,this.mergeVerts);f.enableVertexAttribArray(this.blitShader.attribs.pos);f.vertexAttribPointer(this.blitShader.attribs.pos,2,f.FLOAT,!1,0,0);f.drawArrays(f.TRIANGLES,0,3);f.disableVertexAttribArray(this.blitShader.attribs.pos);
34365f.bindBuffer(f.ARRAY_BUFFER,null)}.bind(this),h=new Texture(this.gl,c);this.cache[d]=h;var k=0,l=0,n=function(){if(k&&l){var a,b;l.width*l.height>k.width*k.height?(a=l.width,b=l.height):(a=k.width,b=k.height);h.desc.width=a;h.desc.height=b;if(a<=f.limits.viewportSizes[0]&&b<=f.limits.viewportSizes[1]){var c={clamp:!0};k.width==a&&k.height==b?(h.loadImage(k,f.RGBA),a=new Framebuffer(f,{color0:h,ignoreStatus:!0}),TextureCache.closeImage(k)):(b=new Texture(f,c),b.loadImage(k,f.RGB),TextureCache.closeImage(k),
34366h.loadArray(null),a=new Framebuffer(f,{color0:h,ignoreStatus:!0}),a.bind(),g(b),b.destroy());b=new Texture(f,c);b.loadImage(l,f.RGB);TextureCache.closeImage(l);a.bind();f.colorMask(!1,!1,!1,!0);g(b);f.colorMask(!0,!0,!0,!0);b.destroy();Framebuffer.bindNone(f);h.rebuildMips()}else{c=document.createElement("canvas");c.width=a;c.height=b;var d=c.getContext("2d");d.drawImage(k,0,0);TextureCache.closeImage(k);c=d.getImageData(0,0,a,b);c=new Uint8Array(c.data.buffer,c.data.byteOffset,c.data.length);d.drawImage(l,
343670,0);TextureCache.closeImage(l);d=d.getImageData(0,0,a,b).data;a=a*b*4;for(b=0;b<a;b+=4)c[b+3]=d[b];h.loadArray(c)}TextureCache.closeImage(l)}}.bind(this);TextureCache.parseFile(a,function(a){k=a;n()});TextureCache.parseFile(b,function(a){l=a;n()});return h};
34368TextureCache.parseFile=function(a,b,c){var d=c||new Image;if("undefined"!=typeof URL&&"undefined"!=typeof URL.createObjectURL){a=new Blob([a.data],{type:a.type});var e=URL.createObjectURL(a);d.onload=function(){URL.revokeObjectURL(e);b&&b(d)};d.src=e}else{a=new Blob([a.data],{type:a.type});var f=new FileReader;f.onload=function(a){d.src=f.result};d.onload=function(){b&&b(d)};f.readAsDataURL(a)}};TextureCache.closeImage=function(a){a&&256<a.width*a.height&&(a.onload=null,a.onerror=null,a.src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D")};function TimelineSlider(a,b){this.name="none";this.debugString="";this.knobControlRect=new ControlRect(a);this.controlRect=new ControlRect(a);var c=document.createElement("div");c.id="sliderUI";c.style.position="absolute";c.style.overflow="hidden";c.style["-moz-user-select"]="none";c.style["-khtml-user-select"]="none";c.style["-webkit-user-select"]="none";c.style["-ms-user-select"]="none";
34368this.controlRect.linkedControl=c;this.backgroundControl=0;this.controlRect.registerChildControlRect(this.knobControlRect);
34369this.knobControlRect.setOpacity(0.65);this.sliderPercent=this.pixelsY=this.pixelsX=0;this.draggingSlider=!1;this.guiScreen=a;b.addImageElement(this.knobControlRect,"animationknob"+a.imageSetNumber+"x.png")}
34370TimelineSlider.prototype.setBackground3x1=function(a,b,c,d){var e=8/this.controlRect.getScreenHeight();this.backgroundControl=a.addTextButton("",0,(1-e)/2,1,e,1);this.backgroundControl.defaultAlpha=1;this.backgroundControl.setBackground3x1(a,0,0,b,c,d,4);this.backgroundControl.controlRect.xPercent=this.controlRect.xPercent;this.backgroundControl.controlRect.widthPercent=this.controlRect.widthPercent;this.controlRect.linkedControl.style.zIndex="3";this.setupCallbacks()};
34371TimelineSlider.prototype.setSize=function(a,b){this.pixelsX=a;this.pixelsY=b;var c=24/b;this.knobWidthPercent=24/a;this.knobControlRect.xPercent=0.5-this.knobWidthPercent/2;this.knobControlRect.yPercent=(1-c)/2+-1/b;this.knobControlRect.widthPercent=this.knobWidthPercent;this.knobControlRect.heightPercent=c;this.controlRect.updateElement();this.backgroundControl.controlRect.xPercent=this.controlRect.xPercent;this.backgroundControl.controlRect.widthPercent=this.controlRect.widthPercent;this.backgroundControl.controlRect.updateElement()};
34372TimelineSlider.prototype.setSliderPercent=function(a){0>a&&(a=0);1<a&&(a=1);this.sliderPercent=a;this.knobControlRect.xPercent=a-this.knobWidthPercent/2;this.knobControlRect.updateElement()};
34373TimelineSlider.prototype.setupCallbacks=function(){var a=function(a){if(this.draggingSlider){var b=this.backgroundControl.controlRect.linkedControl.getBoundingClientRect();this.setSliderPercent((a.clientX-b.left)/b.width);this.guiScreen.ui.viewer.scene.sceneAnimator.setAnimationProgress(this.sliderPercent,!0);this.guiScreen.ui.viewer.scene.sceneAnimator.paused&&(this.guiScreen.ui.viewer.scene.postRender.discardAAHistory(),this.guiScreen.ui.viewer.reDrawScene())}}.bind(this),b=function(a){this.draggingSlider=
34374!0;var b=this.backgroundControl.controlRect.linkedControl.getBoundingClientRect();this.setSliderPercent((a.clientX-b.left)/b.width);this.guiScreen.ui.viewer.scene.sceneAnimator.setAnimationProgress(this.sliderPercent,!0);this.guiScreen.ui.viewer.scene.sceneAnimator.lockPlayback=!0;this.guiScreen.ui.viewer.scene.sceneAnimator.paused&&(this.guiScreen.ui.viewer.scene.postRender.discardAAHistory(),this.guiScreen.ui.viewer.reDrawScene())}.bind(this),c=function(a){this.draggingSlider=!1;this.guiScreen.ui.viewer.scene.sceneAnimator.lockPlayback=
34375!1}.bind(this);this.guiScreen.ui.viewer.input.element.addEventListener("mousemove",a);this.guiScreen.ui.viewer.input.element.addEventListener("mouseup",c);this.backgroundControl.controlRect.linkedControl.addEventListener("mousemove",a);this.backgroundControl.controlRect.linkedControl.addEventListener("mousedown",b);this.backgroundControl.controlRect.linkedControl.addEventListener("mouseup",c);this.controlRect.linkedControl.addEventListener("mouseup",c)};function UI(a){this.viewer=a;this.stripData=a.stripData;a=this.container=document.createElement("div");a.id="marmosetUI";a.style.position="absolute";a.style.overflow="hidden";a.style["-moz-user-select"]="none";a.style["-khtml-user-select"]="none";a.style["-webkit-user-select"]="none";a.style["-ms-user-select"]="none";this.viewer.domRoot.appendChild(a);this.guiScreen=new GUIScreen(this)}
34376UI.prototype.setSize=function(a,b){this.container.width=a|0;this.container.height=b|0;this.container.style.width=a+"px";this.container.style.height=b+"px";this.guiScreen.setSize(this.container.width,this.container.height)};UI.prototype.clearView=function(){for(;this.container.hasChildNodes();)this.container.removeChild(this.container.childNodes[0]);delete this.progressBar;delete this.thumbnail;delete this.fadeThumbnail;delete this.playButton;delete this.helpOverlay};
34377UI.prototype.bindInput=function(a){a.onSingleTap.push(function(b,c){this.stripData.selectedStrip!=this.stripData.STRIP_NONE&&(b=2/a.element.clientWidth*b-1,c=1-2/a.element.clientHeight*c,this.stripData.selectStr
34377ip(b,c),this.stripData.selectedStrip==this.stripData.STRIP_MENU&&this.helpOverlay.active&&this.helpOverlay.toggle(),this.refreshUI(),this.viewer.wake())}.bind(this))};UI.sanitize=function(a){return a?a.replace(/<|>|\(|\)|$|%|=/g,""):a};
34378UI.sanitizeURL=function(a){return a?0==a.indexOf("http://")||0==a.indexOf("https://")||0==a.indexOf("ftp://")?encodeURI(a):"http://"+encodeURI(a):a};UI.prototype.showFailure=function(a){this.container.innerHTML='<br><br><br><p style="text-align:center;color:#aaaaaa"><b>Marmoset Viewer could not initialize.</b><br><i>'+(a||"")+"</i>"};
34379UI.prototype.showPreview=function(a){this.clearView();this.thumbnail=document.createElement("canvas");var b=this.container.width/this.container.height;this.thumbnail.height=this.viewer.mobile?200:300;this.thumbnail.width=this.thumbnail.height*b|0;this.thumbnail.style.width=this.thumbnail.style.height="100%";var b=this.thumbnail.getContext("2d"),c=b.fillStyle=b.createRadialGradient(this.thumbnail.width/2,this.thumbnail.height/2,(this.thumbnail.width+this.thumbnail.height)/2,this.thumbnail.width/2,
343800,0);c.addColorStop(0,"rgb(0,0,0)");c.addColorStop(1,"rgb(150,150,150)");b.fillStyle=c;b.fillRect(0,0,this.thumbnail.width,this.thumbnail.height);this.container.appendChild(this.thumbnail);this.playButton=document.createElement("input");this.playButton.type="image";this.playButton.src=marmoset.dataLocale+"play.png";this.playButton.style.position="absolute";this.playButton.style.left="50%";this.playButton.style.top="50%";this.playButton.style["-webkit-transform"]=this.playButton.style.transform="translate(-50%,-50%) scale(0.5,0.5)";
34381this.playButton.style.opacity=0.5;this.playButton.style.outline="0px";this.playButton.onclick=function(){this.viewer.loadScene(this.viewer.sceneURL);this.container.removeChild(this.playButton);delete this.playButton}.bind(this);this.container.appendChild(this.playButton);a||fetchThumbnail(this.viewer.sceneURL,function(a){this.loadingImageURL||this.setThumbnail(a)}.bind(this))};UI.prototype.setThumbnailURL=function(a){(this.loadingImageURL=a)&&Network.fetchImage(this.loadingImageURL,this.setThumbnail.bind(this))};
34382UI.prototype.setThumbnail=function(a){if(this.thumbnail)if(a.height>=this.container.height){var b=this.container.height/a.height;a.style.position="absolute";a.style.outline="0px";a.style.left="50%";a.style.top="50%";a.style["-webkit-transform"]=a.style.transform="translate(-50%,-50%) scale("+b+","+b+")";this.container.replaceChild(a,this.thumbnail);this.thumbnail=a}else{var c=this.thumbnail.getContext("2d"),d=this.thumbnail.width,e=this.thumbnail.height,b=e/a.height;c.drawImage(a,(d-a.width*b)/2,
343830,a.width*b,e);var f;try{f=c.getImageData(0,0,d,e)}catch(g){return}a=c.createImageData(d,e);for(var h=0;2>h;++h){for(var b=f.data,k=a.data,l=0,n=0;n<e;++n)for(var m=0;m<d;++m){for(var p=0,r=0,s=0,u=-2;2>=u;++u)for(var q=n+u,q=0>q?0:q>=e?e-1:q,x=-2;2>=x;++x)var w=m+x,w=0>w?0:w>=d?d-1:w,w=4*(q*d+w),p=p+b[w],r=r+b[w+1],s=s+b[w+2];k[l++]=p/25;k[l++]=r/25;k[l++]=s/25;k[l++]=255}b=f;f=a;a=b}c.putImageData(f,0,0)}};
34384UI.prototype.showActiveView=function(){var a=this.thumbnail;this.clearView();a&&(this.fadeThumbnail=a,this.fadeThumbnail.style.opacity=1,this.container.appendChild(this.fadeThumbnail));if(!marmoset.noUserInterface){void 0===marmoset.largeUI&&(marmoset.largeUI=this.viewer.mobile);450>this.container.width&&(marmoset.largeUI=!1);var b=FullScreen.support(),b=!0,a=1;window.devicePixelRatio&&(2<window.devicePixelRatio?a=4:1<window.devicePixelRatio&&(a=2));marmoset.largeUI&&4>a&&(a*=2);var c=marmoset.largeUI?
343850.3:0.5;this.stripText=[];for(var d=0;d<this.stripData.labels.length;++d){this.stripText[d]=document.createElement("div");this.stripText[d].style.position="absolute";this.stripText[d].style.cursor="pointer";this.stripText[d].style.pointerEvents="none";this.container.appendChild(this.stripText[d]);var e=document.createElement("div");e.style.color="white";e.style.opacity=0.5;e.style.fontFamily="Arial";e.style.textShadow="2px 2px 3px #000000";e.innerHTML=this.stripData.labels[d];this.stripText[d].appendChild(e);
34386this.stripText[d].txt=e;
34386e=document.createElement("div");e.style.width="10000px";e.style.height="2px";e.style.backgroundColor="#AAAAAA";e.style.opacity=1;e.style.position="absolute";e.style.left=e.style.top="-1px";this.stripText[d].appendChild(e);this.stripText[d].line=e}this.sigCluster=document.createElement("div");this.sigCluster.style.position="absolute";this.sigCluster.style.right=marmoset.largeUI?"12px":"9px";this.sigCluster.style.left="0px";this.sigCluster.style.top="6px";this.sigCluster.style.height=
34387marmoset.largeUI?"64px":"32px";this.logo=document.createElement("div");this.logo.style.position="absolute";this.logo.style.right=marmoset.largeUI?"-4px":"1px";this.logo.style.top=marmoset.largeUI?"0px":"4px";this.logo.title="Made with Marmoset Toolbag";var f=document.createElement("input");f.type="image";f.src=marmoset.dataLocale+"logo"+a+"x.png";f.style.border="none";f.style.width=f.style.height=marmoset.largeUI?"72px":"36px";f.style.border="0px";f.style.outline="0px";f.style.opacity=c;f.onmouseover=
34388function(){this.style.opacity=1}.bind(f);f.onmouseout=function(){this.style.opacity=c}.bind(f);f.onclick=function(a){window.open("http://www.marmoset.co/viewer?utm_source=inapp&utm_medium=menu&utm_campaign=viewer","_blank");this.style.opacity=c}.bind(f,this);d=new XMLHttpRequest;d.open("HEAD",f.src,!0);d.onload=function(a){this.logo.appendChild(a)}.bind(this,f);d.send();this.sigCluster.appendChild(this.logo);d=this.viewer.scene.metaData;d.title=UI.sanitize(d.title);d.subtitle=UI.sanitize(d.subtitle);
34389d.author=UI.sanitize(d.author);d.link=UI.sanitizeURL(d.link);var g=d.title&&0<d.title.length,e=d.subtitle&&0<d.subtitle.length,f=d.author&&0<d.author.length,h=d.link&&0<d.link.length;if(g||e||f){g||(d.title="Art");var k=!g&&!e,l=document.createElement("div");l.style.position="absolute";l.style.right=marmoset.largeUI?"74px":"46px";l.style.top="5px";l.style.width="1px";l.style.height=marmoset.largeUI?k?"21px":"35px":k?"18px":"31px";l.style.opacity=0.25;l.style.backgroundColor="white";this.sigCluster.appendChild(l);
34390this.sigCluster.line=l;k=document.createElement("a");h&&(k.href=d.link);k.style.position="absolute";k.style.right=marmoset.largeUI?"86px":"58px";k.style.top="6px";k.style.textAlign="right";k.style.color="white";k.style.fontFamily="Arial";k.style.fontSize=marmoset.largeUI?"14px":"12px";k.style.textDecoration="none";k.target="_blank";l=document.createElement("font");l.style.color="#FFFFFF";l.style.opacity=0.5;l.style.textDecoration="none";l.style.textShadow="1px 1px 2px rgba(0,0,0,0.7)";l.innerHTML=
34391d.title;f&&(l.innerHTML=g&&!e?l.innerHTML+"<br>by ":l.innerHTML+" by ");k.appendChild(l);g=document.createElement("font");g.style.color="#FF0044";g.style.opacity=1;g.style.textShadow="1px 1px 2px rgba(0,0,0,0.35)";g.innerHTML=d.author;k.appendChild(g);f=document.createElement("font");f.style.color="#FFFFFF";f.style.opacity=0.5;f.style.textShadow="1px 1px 2px rgba(0,0,0,0.7)";e&&(f.innerHTML="<br>",f.innerHTML+=d.subtitle);k.appendChild(f);h&&(k.onmouseover=function(a,b,c){a.style.opacity=c.style.opacity=
343921;b.style.textDecoration="underline"}.bind(k,l,g,f),k.onmouseout=function(a,b,c){a.style.opacity=c.style.opacity=0.5;b.style.textDecoration="none"}.bind(k,l,g,f));this.sigCluster.appendChild(k);this.sigCluster.sceneTitle=k}this.container.appendChild(this.sigCluster);this.sigCluster.active=!0;this.sigCluster.toggle=function(){this.sceneTitle&&this.line&&(this.active?(this.removeChild(this.sceneTitle),this.removeChild(this.line)):(this.appendChild(this.sceneTitle),this.appendChild(this.line)));this.active=
34393!this.active}.bind(this.sigCluster);this.helpOverlay=document.createElement("div");this.helpOverlay.style.pointerEvents="none";this.container.appendChild(this.helpOverlay);this.hideSigOnHelp=d=450>this.container.width;this.hideSigOnStrips=!0;g=[8,8];d?(e=198+2*g[0],f=258+2*g[1]):(e=354+2*g[0],f=218+2*g[1]);h=document.createElement("div");h.style.position="absolute";h.style.width=h.style.height="100%";this.helpOverlay.contents=h;h=document.createElement("div");h.style.position="absolute";h.style.right=
34394marmoset.largeUI?"92px":"54px";h.style.top=d?"16px":"48px";h.style.width=e+"px";h.style.height=f+"px";this.helpOverlay.contents.appendChild(h);f=document.createElement("div");f.style.position="absolute";f.style.width="100%";f.style.height="100%";f.style.backgroundColor="black";f.style.opacity="0.65";f.style.borderRadius="16px";h.appendChild(f);f=document.createElement("input");f.type="button";f.value="x";f.style.position="absolute";f.style.color="#FFFFFF";f.style.fontWeight="bolder";f.style.backgroundColor=
34395"rgba(0,0,0,0.0)";f.style.border="0px";f.style.outline="0px";f.style.fontSize=marmoset.largeUI?"16pt":"10pt";f.style.right=marmoset.largeUI?"2px":"8px";f.style.top=marmoset.largeUI?"0px":"4px";f.style.width=f.style.height=marmoset.largeUI?"32px":"16px";f.style.pointerEvents="auto";f.style.cursor="pointer";f.onclick=function(a){this.helpOverlay.toggle();this.refreshUI()}.bind(this,f);h.appendChild(f);f=document.createElement("center");f.style.position="absolute";f.style.left=g[0]-4+"px";f.style.right=
34396g[0]+4+"px";f.style.top=f.style.bottom=g[1]+"px";f.style.paddingTop="8px";d||(f.style.paddingRight="8px");h.appendChild(f);h=f;g=(this.viewer.mobile?"M":"PC")+(2<a?4:2)+"x.png";f=document.createElement("img");f.src=marmoset.dataLocale+"helprotate"+g;f.style.width="66px";f.style.height="90px";h.appendChild(f);f=document.createElement("img");f.src=marmoset.dataLocale+"helpzoom"+g;f.style.width="66px";f.style.height="90px";h.appendChild(f);f=document.createElement("img");f.src=marmoset.dataLocale+"helpmove"+
34397g;f.style.width="66px";f.style.height="90px";h.appendChild(f);f=document.createElement("img");f.src=marmoset.dataLocale+"helpreset"+g;f.style.width="66px";f.style.height="90px";h.appendChild(f);f=document.createElement("img");f.src=marmoset.dataLocale+"helplights"+g;f.style.position="relative";d||(f.style.left="8px");f.style.width="66px";f.style.height="90px";h.appendChild(f);g=document.createElement("a");g.href="http://www.marmoset.co/viewer?utm_source=inapp&utm_medium=menu&utm_campaign=viewer";
34398g.target="_blank";g.style.pointerEvents="auto";h.appendChild(g);k=document.createElement("img");k.src=marmoset.dataLocale+"helpshadow.png";k.style.position="absolute";k.style.left=0.5*e-(d?65:116)+"px";k.style.bottom=d?"6px":"8px";k.style.width=d?"116px":"232px";k.style.opacity=0;g.appendChild(k);k.targetOpacity=0;g.onmouseover=function(){this.targetOpacity=0.65}.bind(k);g.onmouseout=function(){this.targetOpacity=0}.bind(k);window.setInterval(function(){this.style.opacity=0.1*this.targetOpacity+0.9*
34399this.style.opacity}.bind(k),20);f=document.createElement("img");f.src=marmoset.dataLocale+"helptitle.png";f.style.position="absolute";f.style.left=0.5*e-(d?65:116)+"px";f.style.bottom=d?"8px":"12px";f.style.width=d?"116px":"232px";g.appendChild(f);e=document.createElement("div");e.style.position="absolute";e.style.left=0;e.style.right=d?"30px":"108px";e.style.bottom=d?"-4px":"4px";e.style.textAlign="right";e.style.fontFamilly="Arial";h.appendChild(e);d=document.createElement("font");d.style.fontSize=
34400"9pt";d.style.fontFamily="Arial";e.appendChild(d);g=document.createElement("a");g.style.color="#FF0044";g.style.textDecoration="none";g.style.pointerEvents="auto";g.innerHTML="www.marmoset.co/viewer";g.href="http://www.marmoset.co/viewer?utm_source=inapp&utm_medium=menu&utm_campaign=viewer";g.target="_blank";g.onmouseover=function(a){this.style.textDecoration="underline";a.targetOpacity=0.65}.bind(g,k);g.onmouseout=function(a){this.style.textDecoration="none";a.targetOpacity=0}.bind(g,k);d.appendChild(g);
34401this.helpOverlay.active=!1;this.helpOverlay.toggle=function(a){this.active?this.removeChild(this.contents):this.appendChild(this.contents);this.active=!this.active}.bind(this.helpOverlay,this.viewer);this.menuCluster=document.createElement("div");this.menuCluster.style.position="absolute";this.menuCluster.style.right=marmoset.largeUI?"4px":"8px";this.menuCluster.style.top=marmoset.largeUI?"70px":"40px";marmoset.largeUI?(this.menuCluster.style.width="72px",this.menuCluster.style.height="64px"):(this.menuCluster.style.width=
34402"36px",this.menuCluster.style.height="36px");h=document.createElement("div");h.style.left=h.style.top="0px";h.style.width=h.style.height="100%";this.menuCluster.contents=h;this.menuCluster.appendChild(h);d=0;e=function(a,b,c,d,e){var f=document.createElement("input");f.type="image";f.src=marmoset.dataLocale+c;f.style.position="absolute";f.style.left="0px";f.style.bottom=-100*d+"%";f.style.border="none";f.style.outline="0px";f.title=b;f.style.opacity=e;marmoset.largeUI?(f.style.width="64px",f.style.height=
34403"48px"):(f.style.width="32px",f.style.height="24px");f.onmouseover=function(a){this.style.opacity=a}.bind(f,1);f.onmouseout=function(a){this.style.opacity=a}.bind(f,e);f.onmouseup=function(a){this.style.opacity=a}.bind(f,e);b=new XMLHttpRequest;b.open("HEAD",f.src,!0);b.onload=function(a){a.appendChild(this)}.bind(f,a);b.send();return f};b&&(b=e(this.menuCluster.contents,"Full Screen","fullscreen"+a+"x.png",d++,c),b.onclick=function(a){FullScreen.active()?FullScreen.end():FullScreen.begin(this.viewer.domRoot,
34404this.viewer.fullscreenChange.bind(this.viewer));a.style.opacity=c;this.refreshUI()}.bind(this,b));b=e(this.menuCluster.contents,"Layer Views","strips"+a+"x.png",d++,c);
34404b.onclick=function(a){this.stripData.toggleMenu();this.helpOverlay.active&&this.helpOverlay.toggle();this.viewer.wake();this.refreshUI()}.bind(this,b);b=e(this.menuCluster.contents,"Help","help"+a+"x.png",d++,c);b.onclick=function(a){this.stripData.selectedStrip==this.stripData.STRIP_MENU&&this.stripData.toggleMenu();this.helpOverlay.toggle();
34405this.refreshUI()}.bind(this,b);this.guiScreen&&this.guiScreen.setupActiveView(this);this.container.appendChild(this.menuCluster);this.menuCluster.active=!0;this.menuCluster.toggle=function(){this.active?this.removeChild(this.contents):this.appendChild(this.contents);this.active=!this.active}.bind(this.menuCluster);void 0!==marmoset.hostImage&&(marmoset.hostURL&&(g=document.createElement("a"),g.href=marmoset.hostURL,g.target="_blank"),f=document.createElement("img"),f.src=marmoset.hostImage,f.style.position=
34406"absolute",f.style.top="4px",f.style.left="4px",f.style.opacity=0.65,f.style["-webkit-transform"]=f.style.transform="translate(-50%,-50%) scale(0.5,0.5) translate(50%,50%)",marmoset.hostURL?(f.onmouseover=function(){this.style.opacity=1}.bind(f),f.onmouseout=function(){this.style.opacity=0.5}.bind(f),g.appendChild(f),this.hostLogo=g):this.hostLogo=f,d=new XMLHttpRequest,d.open("HEAD",f.src,!0),d.onload=function(){this.container.appendChild(this.hostLogo)}.bind(this),d.send());this.sceneStats=document.createElement("text");
34407this.sceneStats.style.position="absolute";this.sceneStats.style.left="9px";this.sceneStats.style.bottom="8px";this.sceneStats.style.color="gray";this.sceneStats.style.fontFamily="Arial";this.sceneStats.style.fontSize="75%";for(d=b=a=0;d<this.viewer.scene.meshes.length;++d)e=this.viewer.scene.meshes[d],a+=e.indexCount/3,b+=e.vertexCount;this.sceneStats.innerHTML="Triangles: "+(a|0).toLocaleString()+"<br>Vertices: "+(b|0).toLocaleString();marmoset.showFrameTime&&(this.frameTimer=document.createElement("text"),
34408this.frameTimer.style.position="absolute",this.frameTimer.style.left=this.frameTimer.style.top="5px",this.frameTimer.style.color="gray",this.frameTimer.style.fontSize="75%",this.container.appendChild(this.frameTimer),this.frameTimer.innerHTML="--",this.frameCount=1E20);this.animateStrips()}};
34409UI.prototype.refreshUI=function(){if(this.sigCluster){var a=!1,b=this.stripData.selectedStrip==this.stripData.STRIP_MENU;this.hideSigOnStrips&&(a=a||b);this.hideSigOnHelp&&(a=a||this.helpOverlay.active);
34409this.sigCluster.active==a&&this.sigCluster.toggle()}};
34410UI.prototype.signalLoadProgress=function(a,b){if(this.thumbnail){if(!this.progressBar){var c=document.createElement("div");c.style.backgroundColor="rgb(30,30,30)";c.style.opacity=0.5;c.style.position="absolute";c.style.left="20%";c.style.width="60%";c.style.bottom="30%";c.style.height="2px";this.progressBar=document.createElement("div");this.progressBar.style.backgroundColor="white";this.progressBar.style.position="absolute";this.progressBar.style.left=this.progressBar.style.bottom="0px";this.progressBar.style.height=
34411"100%";this.progressBar.style.width="0px";c.appendChild(this.progressBar);this.container.appendChild(c);this.playButton&&(this.container.removeChild(this.playButton),delete this.playButton)}this.progressBar.style.width=0>=b?(100*a/(2097152+a)|0)+"%":(100*a/b|0)+"%"}};UI.prototype.animating=function(){return!!this.fadeThumbnail||!!this.frameTimer};
34412UI.prototype.animate=function(){this.fadeThumbnail&&(this.fadeThumbnailTimer=this.fadeThumbnailTimer||Date.now(),this.fadeThumbnail.style.opacity=1-0.0015*(Date.now()-this.fadeThumbnailTimer),0.01>this.fadeThumbnail.style.opacity&&(this.container.removeChild(this.fadeThumbnail),delete this.fadeThumbnail,delete this.fadeThumbnailTimer));if(this.frameTimer&&(this.frameCount++,60<=this.frameCount)){var a=(new Date).getTime();if(void 0!==this.frameTime){var b=(a-this.frameTime)/this.frameCount,b=Math.floor(100*
34413b)/100;this.frameTimer.innerHTML=b+" ms";this.frameTimer.style.color=32>b?"green":"red"}this.frameCount=0;this.frameTime=a}this.guiScreen&&this.guiScreen.playbackControls&&(a=this.guiScreen.playbackControls.timelineSlider,a.draggingSlider?this.viewer.scene.sceneAnimator.setAnimationProgress(a.sliderPercent,!0):a.setSliderPercent(this.viewer.scene.sceneAnimator.animationProgress));if(this.sceneStats){for(var c=b=a=0;c<this.viewer.scene.meshes.length;++c)var d=this.viewer.scene.meshes[c],a=a+d.indexCount/
344143,b=b+d.vertexCount;this.sceneStats.innerHTML="Triangles: "+(a|0).toLocaleString()+"<br>Vertices: "+(b|0).toLocaleString();this.viewer.scene.sceneAnimator&&this.viewer.scene.sceneAnimator.showPlayControls&&(this.sceneStats.innerHTML+="<br><br><br><br>");a=!!this.sceneStats.parentElement;b=this.stripData.active()||!1;a&&!b?(this.container.removeChild(this.sceneStats),this.hostLogo&&this.container.appendChild(this.hostLogo)):!a&&b&&(this.container.appendChild(this.sceneStats),this.hostLogo&&this.container.removeChild(this.hostLogo))}this.refreshUI();
34415if(this.stripData.animationActive||this.stripData.active())this.animateStrips(),this.stripData.animationActive&&this.viewer.wake()};
34416UI.prototype.animateStrips=function(){if(this.stripText)for(var a=Math.atan(this.viewer.canvas.height/this.viewer.canvas.width/this.stripData.stripSlant),b=0;b<this.stripData.labels.length;++b){var c=this.stripData.strips[b],c=c-this.stripData.stripSlant,c=0.5+0.5*c;b==this.stripData.selectedStrip?(this.stripText[b].style["-ms-transform"]=this.stripText[b].style["-webkit-transform"]=this.stripText[b].style.transform="none",this.stripText[b].style.top="4px",this.stripText[b].style.left="0px",this.stripText[b].style.width=
34417"150px",this.stripText[b].txt.style.textAlign="center",this.stripText[b].txt.style.background="rgba(0, 0, 0, 0.75)",this.stripText[b].txt.style.background="-webkit-linear-gradient(left, rgba(0,0,0,0.75), rgba(0,0,0,0))",this.stripText[b].txt.style.background="-o-linear-gradient(left,      rgba(0,0,0,0.75), rgba(0,0,0,0))",this.stripText[b].txt.style.background="-moz-linear-gradient(left,    rgba(0,0,0,0.75), rgba(0,0,0,0))",this.stripText[b].txt.style.background="linear-gradient(left,         rgba(0,0,0,0.75), rgba(0,0,0,0))",
34418this.stripText[b].txt.style.paddingLeft="32px",this.stripText[b].txt.style.paddingTop="6px",this.stripText[b].txt.style.paddingBottom="4px",this.stripText[b].txt.style.textShadow="1px 1px 2px rgba(0,0,0,0.7)",this.stripText[b].line.style.opacity=0.5,this.stripText[b].line.style.top="100%",this.stripText[b].line.style.width="100%",this.stripText[b].line.style.height="1px"):(this.stripText[b].style["-ms-transform"]=this.stripText[b].style["-webkit-transform"]=this.stripText[b].style.transform="translate(-50%, -50%) rotate("+
34419a+"rad) translate(50%, 50%)",this.stripText[b].style.left=100*c+"%",this.stripText[b].style.top="0px",this.stripText[b].style.width="85px",this.stripText[b].txt.style.textAlign="left",this.stripText[b].txt.style.background="none",this.stripText[b].txt.style.paddingLeft="8px",this.stripText[b].txt.style.paddingTop="6px",this.stripText[b].txt.style.paddingBottom="4px",this.stripText[b].txt.style.textShadow="2px 0px 3px rgba(0,0,0,0.7)",this.stripText[b].line.style.opacity=1,this.stripText[b].line.style.top=
34420"-1px",this.stripText[b].line.style.width="10000px",this.stripText[b].line.style.height="2px")}};var Vect={type:Float32Array,create:function(a,b,c,d){var e=new Vect.type(4);e[0]=a;e[1]=b;e[2]=c;e[3]=d;return e},empty:function(){return new Vect.type(4)},set:function(a,b,c,d,e){a[0]=b;a[1]=c;a[2]=d;a[3]=e},copy:function(a,b){a[0]=b[0];a[1]=b[1];a[2]=b[2];a[3]=b[3]},add:function(a,b,c){a[0]=b[0]+c[0];a[1]=b[1]+c[1];a[2]=b[2]+c[2];a[3]=b[3]+c[3];return a},sub:function(a,b,c){a[0]=b[0]-c[0];a[1]=b[1]-c[1];a[2]=b[2]-c[2];a[3]=b[3]-c[3];return a},scale:function(a,b,c){a[0]=c[0]*b;a[1]=c[1]*b;a[2]=c[2]*
34421b;a[3]=c[3]*b;return a},mul:function(a,b,c){a[0]=b[0]*c[0];a[1]=b[1]*c[1];a[2]=b[2]*c[2];a[3]=b[3]*c[3];return a},mad:function(a,b,c,d){a[0]=b[0]*c[0]+d[0];a[1]=b[1]*c[1]+d[1];a[2]=b[2]*c[2]+d[2];a[3]=b[3]*c[3]+d[3];return a},smad:function(a,b,c,d){a[0]=b*c[0]+d[0];a[1]=b*c[1]+d[1];a[2]=b*c[2]+d[2];a[3]=b*c[3]+d[3];return a},negate:function(a,b){a[0]=-b[0];a[1]=-b[1];a[2]=-b[2];return a},negate4:function(a,b){a[0]=-b[0];a[1]=-b[1];a[2]=-b[2];a[3]=-b[3];return a},length:function(a){var b=a[0],c=a[1];
34422a=a[2];return Math.sqrt(b*b+c*c+a*a)},distance:function(a,b){var c=a[0]-b[0],d=a[1]-b[1],e=a[2]-b[2];return Math.sqrt(c*c+d*d+e*e)},dot:function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]},dot4:function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3]},normalize:function(a,b){var c=b[0],d=b[1],e=b[2],f=Math.sqrt(c*c+d*d+e*e);if(0==f)return Vect.set(a,0,0,0,0);f=1/f;a[0]=c*f;a[1]=d*f;a[2]=e*f;return a},cross:function(a,b,c){a[0]=b[1]*c[2];a[0]+=-b[2]*c[1];a[1]=b[2]*c[0]-b[0]*c[2];a[2]=b[0]*c[1]-
34423b[1]*c[0];return a},lerp:function(a,b,c,d){var e=1-d;a[0]=b[0]*e+c[0]*d;a[1]=b[1]*e+c[1]*d;a[2]=b[2]*e+c[2]*d;return a},lerp4:function(a,b,c,d){var e=1-d;a[0]=b[0]*e+c[0]*d;a[1]=b[1]*e+c[1]*d;a[2]=b[2]*e+c[2]*d;a[3]=b[3]*e+c[3]*d;return a},min:function(a,b,c){a[0]=Math.min(b[0],c[0]);a[1]=Math.min(b[1],c[1]);a[2]=Math.min(b[2],c[2]);a[3]=Math.min(b[3],c[3]);return a},max:function(a,b,c){a[0]=Math.max(b[0],c[0]);a[1]=Math.max(b[1],c[1]);a[2]=Math.max(b[2],c[2]);a[3]=Math.max(b[3],c[3]);return a},projectOnPlane:function(a,
34424b,c,d){var e=Vect.empty();Vect.sub(e,b,c);c=Vect.dot(e,d);smad(a,-c,normal,b);return a}};function View(a){this.pivot=[0,0,0];this.rotation=[0,0];this.radius=1;this.nearPlane=0.3;this.fov=45;this.size=[1,1];this.transform=Matrix.empty();this.viewMatrix=Matrix.empty();this.projectionMatrix=Matrix.empty();this.viewProjectionMatrix=Matrix.empty();a?this.loadView(a,!0):(this.saveResetView(),this.updateView(),this.updateProjection())}
34425View.prototype.saveResetView=function(){this.resetDesc={angles:[this.rotation[0],this.rotation[1]],pivot:[this.pivot[0],this.pivot[1],this.pivot[2]],limits:this.limits,orbitRadius:this.radius,fov:this.fov}};View.prototype.loadView=function(a,b){a&&(this.rotation[0]=a.angles[0],this.rotation[1]=a.angles[1],this.pivot[0]=a.pivot[0],this.pivot[1]=a.pivot[1],this.pivot[2]=a.pivot[2],this.radius=a.orbitRadius,this.fov=a.fov,this.limits=a.limits,b&&this.saveResetView(),this.updateView(),this.updateProjection())};
34426View.prototype.reset=function(){this.loadView(this.resetDesc)};
34427View.prototype.updateView=function(){if(void 0!==this.limits){if
34427(this.limits.angles){var a=this.limits.angles.x,b=this.limits.angles.y;if(void 0!==a){var c=this.rotation[0]-a.offset,a=Math.min(Math.max(c,a.min),a.max);this.rotation[0]+=a-c}void 0!==b&&(c=this.rotation[1]-b.offset,a=Math.min(Math.max(c,b.min),b.max),this.rotation[1]+=a-c)}void 0!==this.limits.orbitRadius&&(b=this.limits.orbitRadius.min,c=this.limits.orbitRadius.max,void 0!==b&&(this.radius=Math.max(this.radius,b)),void 0!==c&&(this.radius=
34428Math.min(this.radius,c)));void 0!==this.limits.pan&&(b=this.limits.pan,c=this.resetDesc.pivot,b.x&&(this.pivot[0]=c[0]),b.y&&(this.pivot[1]=c[1]),b.z&&(this.pivot[2]=c[2]))}Matrix.translation(this.transform,0,0,this.radius);b=Matrix.rotation(Matrix.empty(),this.rotation[0],0);c=Matrix.rotation(Matrix.empty(),this.rotation[1],1);Matrix.mul(b,c,b);Matrix.mul(this.transform,b,this.transform);this.transform[12]+=this.pivot[0];this.transform[13]+=this.pivot[1];this.transform[14]+=this.pivot[2];Matrix.invert(this.viewMatrix,
34429this.transform);Matrix.mul(this.viewProjectionMatrix,this.viewMatrix,this.projectionMatrix)};View.prototype.updateProjection=function(a){Matrix.perspectiveInfinite(this.projectionMatrix,this.fov,this.size[0]/this.size[1],this.nearPlane,a);Matrix.mul(this.viewProjectionMatrix,this.projectionMatrix,this.viewMatrix)};function WebViewer(a,b,c,d){this.mobile=!!/Android|iPhone|iPod|iPad|Windows Phone|IEMobile|BlackBerry|webOS/.test(navigator.userAgent);this.mobileFast=!!/iPhone|iPad/.test(navigator.userAgent);var e;if(e=!this.mobile)a:{e=document.createElement("canvas");e.width=e.height=16;if(e=e.getContext("webgl",{})||e.getContext("experimental-webgl",{})){var f=e.getExtension("WEBGL_debug_renderer_info");if(f){e=e.getParameter(f.UNMASKED_RENDERER_WEBGL);e=!!/Intel|INTEL/.test(e);break a}}e=!1}this.desktopSlow=
34430e;this.domRoot=document.createElement("div");this.domRoot.style.width=a+"px";this.domRoot.style.height=b+"px";this.initCanvas(a,b);this.scene=this.input=null;this.sceneURL=c;this.sleepCounter=8;this.onLoad=null;this.stripData=new StripData;this.ui=new UI(this);this.ui.setSize(a,b);this.ui.showPreview(d)}
34431WebViewer.prototype.initCanvas=function(a,b){this.canvas&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);this.canvas=document.createElement("canvas");this.pixelRatio=window.devicePixelRatio||1;if(this.mobile){var c=this.mobileFast?1.5:1;this.pixelRatio=this.pixelRatio>c?c:this.pixelRatio}else this.desktopSlow&&(this.pixelRatio=1);this.canvas.width=a*this.pixelRatio;this.canvas.height=b*this.pixelRatio;this.canvas.style.width=a+"px";this.canvas.style.height=b+"px";this.canvas.style.position=
34432"absolute";this.domRoot.appendChild(this.canvas)};
34433WebViewer.prototype.initGL=function(){var a={alpha:!!marmoset.transparentBackground,depth:!1,stencil:!1,antialias:!1,premultipliedAlpha:!!marmoset.transparentBackground,preserveDrawingBuffer:!1},a=this.gl=this.canvas.getContext("webgl",a)||this.canvas.getContext("experimental-webgl",a);if(!this.gl)return this.ui.showFailure('Please <a href="http://get.webgl.org/" target=_blank>check<a/> to ensure your browser has support for WebGL.'),!1;this.canvas.addEventListener("webglcontextlost",function(a){a.preventDefault()}.bind(this),
34434!1);this.canvas.addEventListener("webglcontextrestored",function(a){this.loadScene(this.sceneURL)}.bind(this),!1);a.ext={textureAniso:a.getExtension("EXT_texture_filter_anisotropic")||a.getExtension("WEBKIT_EXT_texture_filter_anisotropic")||a.getExtension("MOZ_EXT_texture_filter_anisotropic"),textureFloat:a.getExtension("OES_texture_float"),textureFloatLinear:a.getExtension("OES_texture_float_linear"),textureHalf:a.getExtension("OES_texture_half_float"),textureHalfLinear:a.getExtension("OES_texture_half_float_linear"),
34435textureDepth:a.getExtension("WEBGL_depth_texture"),colorBufferFloat:a.getExtension("WEBGL_color_buffer_float"),colorBufferHalf:a.getExtension("EXT_color_buffer_half_float"),index32bit:a.getExtension("OES_element_index_uint"),loseContext:a.getExtension("WEBGL_lose_context"),derivatives:a.getExtension("OES_standard_derivatives"),renderInfo:a.getExtension("WEBGL_debug_renderer_info")};a.limits={textureSize:a.getParameter(a.MAX_TEXTURE_SIZE),textureCount:a.getParameter(a.MAX_TEXTURE_IMAGE_UNITS),varyings:a.getParameter(a.MAX_VARYING_VECTORS),
34436vertexAttribs:a.getParameter(a.MAX_VERTEX_ATTRIBS),vertexUniforms:a.getParameter(a.MAX_VERTEX_UNIFORM_VECTORS),fragmentUniforms:a.getParameter(a.MAX_FRAGMENT_UNIFORM_VECTORS),viewportSizes:a.getParameter(a.MAX_VIEWPORT_DIMS),vendor:a.getParameter(a.VENDOR),version:a.getParameter(a.VERSION)};a.hints={mobile:this.mobile,pixelRatio:this.pixelRatio};a.enable(a.DEPTH_TEST);a.shaderCache=new ShaderCache(a);a.textureCache=new TextureCache(a);this.allocBacking();return!0};
34437WebViewer.prototype.allocBacking=function(){var a=this.gl,b=!1,c={width:this.canvas.width,height:this.canvas.height};this.mainColor=new Texture(a,c);this.mainDepth=null;a.ext.textureDepth&&(this.mainDepth=new Texture(a,{width:this.canvas.width,height:this.canvas.height,nofilter:!0}),this.mainDepth.loadArray(null,a.DEPTH_COMPONENT,a.UNSIGNED_INT));a.ext.textureHalf&&a.ext.textureHalfLinear&&(this.mainColor.loadArray(null,a.RGBA,a.ext.textureHalf.HALF_FLOAT_OES),this.mainBuffer=new Framebuffer(a,{color0:this.mainColor,
34438depth:this.mainDepth,createDepth:!this.mainDepth}),b=this.mainBuffer.valid);!b&&a.ext.textureFloat&&a.ext.textureFloatLinear&&!a.hints.mobile&&(this.mainColor.loadArray(null,a.RGBA,a.FLOAT),this.mainBuffer=new Framebuffer(a,{color0:this.mainColor,depth:this.mainDepth,createDepth:!this.mainDepth}),b=this.mainBuffer.valid);for(;!b;)this.mainColor=new Texture(a,c),this.mainColor.loadArray(null,a.RGBA,a.UNSIGNED_BYTE),this.mainBuffer=new Framebuffer(a,{color0:this.mainColor,depth:this.mainDepth,createDepth:!this.mainDepth}),
34439b=this.mainBuffer.valid,c.width/=2,c.height/=2,this.mainDepth&&(this.mainDepth.destroy(),this.mainDepth=null);this.mainBufferNoDepth=new Framebuffer(a,{color0:this.mainColor})};
34440WebViewer.prototype.loadScene=function(a){this.sceneURL=a||this.sceneURL;this.scene=this.input=null;if(this.initGL()&&this.sceneURL){var b=this.ui.signalLoadProgress.bind(this.ui);a=function(a){b(1,1);this.scene=new Scene(this.gl);this.scene.stripData=this.stripData;if(this.scene.load(new Archive(a)))if(2070>=this.scene.metaData.tbVersion)this.ui.showFailure("This .mview file is from an out-of-date beta version of Toolbag. Please re-export it with the new version. Thanks!");else{if(this.bindInput(),
34441this.requestFrame(this.updateLoad.bind(this)),this.onLoad)this.onLoad()}else this.ui.showFailure("Package file could not be read or is invali
34441d.")}.bind(this);var c=function(){this.ui.showFailure("Package file ("+this.sceneURL+") could not be retrieved.")}.bind(this);Network.fetchBinary(this.sceneURL,a,c,b)}};
34442WebViewer.prototype.unload=function(){delete this.scene;delete this.input;delete this.ui;delete this.mainColor;delete this.mainBuffer;delete this.gl;var a=this.domRoot.clientWidth,b=this.domRoot.clientHeight;this.initCanvas(a,b);this.ui=new UI(this);this.ui.setSize(a,b);this.ui.showPreview();this.cancelFrame()};
34443WebViewer.prototype.bindInput=function(){this.input=new Input(this.ui.container);this.input.onDrag.push(function(a,b,c,d){a=1-2.2/(Math.sqrt(c*c+d*d)+2.2);b=this.scene.view;b.rotation[1]-=0.4*c*a;b.rotation[0]-=0.4*d*a;b.rotation[0]=90<b.rotation[0]?90:b.rotation[0];b.rotation[0]=-90>b.rotation[0]?-90:b.rotation[0];b.updateView();this.wake()}.bind(this));this.input.onPan.push(function(a,b){var c=this.scene.view,d=c.fov/45*0.8*(c.radius/this.domRoot.clientHeight),e=-a*d,d=b*d;c.pivot[0]+=e*c.transform[0]+
34444d*c.transform[4];c.pivot[1]+=e*c.transform[1]+d*c.transform[5];c.pivot[2]+=e*c.transform[2]+d*c.transform[6];c.updateView();this.wake()}.bind(this));this.input.onPan2.push(function(a,b){var c=1-2.2/(Math.sqrt(a*a+b*b)+2.2);this.scene.lights.rotation-=0.4*a*c;this.wake()}.bind(this));this.input.onZoom.push(function(a){var b=this.scene.view;b.radius*=1-0.002*a;b.radius=0.001>b.radius?0.001:b.radius;b.radius=1E3<b.radius?1E3:b.radius;b.updateView();this.wake()}.bind(this));this.input.onDoubleTap.push(function(a,
34445b){this.scene.view.reset();this.scene.sceneAnimator&&this.scene.sceneAnimator.resetCustomView();this.wake()}.bind(this));this.ui.bindInput(this.input)};WebViewer.prototype.wake=function(a){a=a||16;this.sleepCounter=this.sleepCounter<a?a:this.sleepCounter;this.scene.postRender.discardAAHistory();this.requestFrame(this.update.bind(this))};
34446WebViewer.prototype.requestFrame=function(a){var b=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame;if(!this.frameRequestPending){var c=function(){this.frameRequestPending=0;a()}.bind(this);this.frameRequestPending=b(c,this.canvas)}};WebViewer.prototype.cancelFrame=function(){this.frameRequestPending&&(window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame)(this.frameRequestPending)};
34447WebViewer.prototype.fullscreenChange=function(){FullScreen.active()?(this.oldRootWidth=this.domRoot.style.width,this.oldRootHeight=this.domRoot.style.height,this.domRoot.style.width="100%",this.domRoot.style.height="100%"):(this.domRoot.style.width=this.oldRootWidth,this.domRoot.style.height=this.oldRootHeight);this.wake()};
34448WebViewer.prototype.resize=function(a,b){a&&b?(this.domRoot.style.width=a+"px",this.domRoot.style.height=b+"px"):(a=this.domRoot.clientWidth,b=this.domRoot.clientHeight);this.canvas.width=a*this.pixelRatio;this.canvas.height=b*this.pixelRatio;this.canvas.style.width=a+"px";this.canvas.style.height=b+"px";this.ui.setSize(a,b);this.allocBacking();this.wake()};WebViewer.prototype.updateLoad=function(){this.scene.complete()?this.start():this.requestFrame(this.updateLoad.bind(this));this.ui.animate()};
34449WebViewer.prototype.start=function(){this.scene.view.updateView();this.ui.showActiveView();this.requestFrame(this.update.bind(this))};WebViewer.prototype.update=function(){var a=this.scene.sceneAnimator&&!this.scene.sceneAnimator.paused;if(0<this.sleepCounter||this.ui.animating()||a||this.stripData.animationActive)this.stripData.update(),this.ui.animate(),this.scene.update(),this.drawScene(),this.requestFrame(this.update.bind(this));a?this.scene.postRender.discardAAHistory():this.sleepCounter--};
34450WebViewer.prototype.reDrawScene=function(){this.stripData.update();this.ui.animate();this.scene.update();this.drawScene();this.requestFrame(this.update.bind(this));this.scene.postRender.discardAAHistory()};
34451WebViewer.prototype.drawScene=function(){this.gl.isContextLost()||(this.domRoot.clientWidth==this.canvas.clientWidth&&this.domRoot.clientHeight==this.canvas.clientHeight||this.resize(),this.scene.view.size=[this.mainBuffer.width,this.mainBuffer.height],this.scene.view.updateProjection(),this.scene.postRender.adjustProjectionForSupersampling(this.scene.view),this.scene.collectShadows(this.mainBuffer),this.mainBuffer.bind(),this.scene.draw(this.mainBuffer),this.mainDepth&&(this.mainBufferNoDepth.bind(),
34452this.scene.drawSecondary(this.mainDepth)),this.scene.postRender.present(this.mainColor,this.canvas.width,this.canvas.height,this.stripData.active()))};marmoset="undefined"==typeof marmoset?{}:marmoset;marmoset.WebViewer=WebViewer;marmoset.dataLocale=(0==window.location.protocol.indexOf("https")?"https:":"http:")+"//viewer.marmoset.co/main/data/";var ShaderTable={"alphaprepassfrag.glsl":"precision mediump float;\n#include <matdither.glsl>\nuniform sampler2D tAlbedo;varying mediump vec2 d;void main(){float e=texture2D(tAlbedo,d).a;if(e<=f(d.x)){discard;}gl_FragColor=vec4(0.0);}","alphaprepassvert.glsl":"precision highp float;uniform mat4 uModelViewProjectionMatrix;uniform vec2 uUVOffset;attribute vec3 vPosition;attribute vec2 vTexCoord;varying mediump vec2 d;vec4 h(mat4 i,vec3 p){return i[0]*p.x+(i[1]*p.y+(i[2]*p.z+i[3]));}void main(void){gl_Position=h(uModelViewProjectionMatrix,vPosition.xyz);d=vTexCoord+uUVOffset;}",
34453"bloom.glsl":"precision mediump float;uniform sampler2D tInput;uniform vec4 uKernel[BLOOM_SAMPLES];varying highp vec2 j;void main(void){vec3 c=vec3(0.0,0.0,0.0);for(int k=0;k<BLOOM_SAMPLES;++k){vec3 l=uKernel[k].xyz;vec3 m=texture2D(tInput,j+l.xy).xyz;m=max(m,vec3(0.0,0.0,0.0));c+=m*l.z;}gl_FragColor.xyz=c;gl_FragColor.w=0.0;}","bloomshrink.glsl":"precision highp float;uniform sampler2D tInput;varying highp vec2 j;void main(void){float o=0.25/256.0;gl_FragColor=0.25*(texture2D(tInput,j+vec2(o,o))+texture2D(tInput,j+vec2(o,-o))+texture2D(tInput,j+vec2(-o,o))+texture2D(tInput,j+vec2(-o,-o)));}",
34454"fogfrag.glsl":"precision highp float;uniform sampler2D tDepth;uniform vec3 uDepthToZ;uniform vec4 uUnproject;uniform mat4 uInvViewMatrix;uniform float uFogInvDistance;uniform float uFogOpacity;uniform float uFogDispersion;uniform vec3 uFogType;uniform vec3 uFogColor;uniform float uFogIllum;uniform mat4 uLightMatrix;\n#ifdef FOG_IBL\nuniform vec4 uFogLightSphere[9];\n#else\nuniform vec4 uSpotParams;uniform vec4 uLightPosition;uniform vec3 uLightColor;uniform vec4 uLightAttenuation;\n#ifdef FOG_SHADOWS\nuniform mat4 uShadowProj;uniform sampler2D uShadowMap;uniform float uDitherOffset;
34454uniform vec4 uCylinder;\n#endif\n#endif\nvec4 h(mat4 i,vec3 p){return i[0]*p.x+(i[1]*p.y+(i[2]*p.z+i[3]));}vec3 u(mat4 i,vec3 v){return i[0].xyz*v.x+i[1].xyz*v.y+i[2].xyz*v.z;}float A(float B){B*=uFogInvDistance;float C=uFogType.x*min(B,1.0)+(uFogType.y-uFogType.y/(1.0+16.0*B*B))+(uFogType.z-uFogType.z*exp(-3.0*B));return C*uFogOpacity;}\n#ifdef FOG_SHADOWS\nfloat D(vec3 E){vec4 p=h(uShadowProj,E);vec3 F=p.xyz/p.w;vec4 G=texture2D(uShadowMap,F.xy);float H=(G.x+G.y*(1.0/255.0))+G.z*(1.0/65025.0);return F.z<H || H>=1.0?1.0:0.0;}float f(vec2 I){return fract(sin(dot(I,vec2(12.9898,78.233)))*43758.5453+uDitherOffset);}void J(vec3 K,vec3 L,out float M,out float N){vec3 v=uSpotParams.xyz,p=uCylinder.xyz;vec3 O=L-dot(L,v)*v;vec3 P=(K-p)-dot(K-p,v)*v;float a=dot(O,O);float b=2.0*dot(O,P);float c=dot(P,P)-uCylinder.w;float Q=b*b-4.0*a*c;if(Q>=0.0){Q=sqrt(Q);M=(-b-Q)/(2.0*a);N=(-b+Q)/(2.0*a);}else {M=N=0.0;}}\n#endif\nvarying vec2 j;void main(void){vec3 R=uInvViewMatrix[3].xyz;float H=texture2D(tDepth,j).x;H=min(H,0.9999);vec3 S;S.z=uDepthToZ.y/(uDepthToZ.z*H+uDepthToZ.x);S.xy=S.z*(j*uUnproject.xy+uUnproject.zw);S=h(uInvViewMatrix,S).xyz;vec3 T;T.xy=(j*uUnproject.xy+uUnproject.zw);T.z=1.0;T=normalize(u(uInvViewMatrix,-T).xyz);vec3 U=uFogColor;\n#if defined(FOG_IBL)\nvec3 G=u(uLightMatrix,T);vec3 V=uFogLightSphere[0].xyz;V+=uFogLightSphere[1].xyz*G.y;V+=uFogLightSphere[2].xyz*G.z;V+=uFogLightSphere[3].xyz*G.x;vec3 swz=G.yyz*G.xzx;V+=uFogLightSphere[4].xyz*swz.x;V+=uFogLightSphere[5].xyz*swz.y;V+=uFogLightSphere[7].xyz*swz.z;vec3 sqr=G*G;V+=uFogLightSphere[6].xyz*(3.0*sqr.z-1.0);V+=uFogLightSphere[8].xyz*(sqr.x-sqr.y);U=mix(U,U*V,uFogIllum);float C=A(length(S-R));gl_FragColor.xyz=U*C;gl_FragColor.w=C;return;\n#else\n#if defined(FOG_SPOT) || defined(FOG_OMNI)\nfloat W=0.0,X=0.0;{float r=1.0/(uLightAttenuation.z);float a=1.0;float b=2.0*dot(T,R-uLightPosition.xyz);float c=dot(uLightPosition.xyz,uLightPosition.xyz)+dot(R,R)+-2.0*dot(uLightPosition.xyz,R)+-r*r;float Q=b*b-4.0*a*c;if(Q>=0.0){Q=sqrt(Q);W=(-b-Q)/(2.0*a);X=(-b+Q)/(2.0*a);}}\n#if defined(FOG_SPOT)\n{float Y=uSpotParams.w,Z=1.0-Y;vec3 v=T;vec3 dc=uSpotParams.xyz;vec3 dd=R-uLightPosition.xyz;vec3 de=v-dot(v,dc)*dc,df=dd-dot(dd,dc)*dc;float a=Y*dot(de,de)-Z*dot(v,dc)*dot(v,dc);float b=2.0*Y*dot(de,df)-2.0*Z*dot(v,dc)*dot(dd,dc);float c=Y*dot(df,df)-Z*dot(dd,dc)*dot(dd,dc);float Q=b*b-4.0*a*c;if(Q>=0.0){float dh=(-b-sqrt(Q))/(2.0*a);float di=(-b+sqrt(Q))/(2.0*a);if(di<dh){float de=dh;dh=di;di=de;}bool dj=dot(-uLightPosition.xyz+R+T*dh,uSpotParams.xyz)<=0.0;bool dk=dot(-uLightPosition.xyz+R+T*di,uSpotParams.xyz)<=0.0;if(!dj ||!dk){if(dj){dh=di;di=X;}else if(dk){di=dh;dh=W;}W=max(W,dh);X=min(X,di);}else {X=W=0.0;}}else {X=W=0.0;}}\n#endif\nfloat tx=dot(T,S-R);W=clamp(W,0.0,tx);X=clamp(X,0.0,tx);float dl=0.0;if(X>W){\n#ifdef FOG_SHADOWS\n#ifdef MOBILE\n#define SAMPLES 16\n#else\n#define SAMPLES 32\n#endif\nfloat dm=f(j)*(X-W)/float(SAMPLES-2);\n#else\n#define SAMPLES 8\nfloat dm=0.0;\n#endif\nfor(int k=0;k<SAMPLES;++k){float t=W+(X-W)*float(k)/float(SAMPLES-1);vec3 p=R+(t+dm)*T;float a=clamp(length(p-uLightPosition.xyz)*uLightAttenuation.z,0.0,1.0);a=1.0+uLightAttenuation.x*a+uLightAttenuation.y*a*a;\n#ifdef FOG_SHADOWS\na*=D(p);\n#endif\ndl+=a-a*A(t);}dl*=1.0/float(SAMPLES);dl*=(X-W)*uLightAttenuation.z;dl*=A(X-W);}U*=dl*uFogIllum;\n#elif defined(FOG_DIR)\nfloat C=A(dot(T,S-R));\n#ifdef FOG_SHADOWS\nfloat W,X;J(R,T,W,X);float tx=dot(T,S-R);W=clamp(W,0.0,tx);X=clamp(X,0.0,tx);if(X>W){\n#ifdef MOBILE\n#define SAMPLES 16\n#else\n#define SAMPLES 32\n#endif\nfloat dl=0.0;float dm=f(j)*(X-W)/float(SAMPLES-2);float dn=(X-W)*(1.0/float(SAMPLES));for(int k=0;k<SAMPLES;++k){float t=W+float(k)*dn+dm;vec3 p=R+t*T;float s=D(p);C-=(1.0-s)*(A(t+dn)-A(t));}}\n#endif\nfloat du=0.5+0.5*dot(T,-uSpotParams.xyz);du=1.0+uFogDispersion*(2.0*du*du-1.0);U*=(0.1*C)*(du*uFogIllum);\n#endif\ngl_FragColor.xyz=U*uLightColor;gl_FragColor.w=0.0;\n#endif\n}",
34455"fogvert.glsl":"precision highp float;attribute vec2 vCoord;varying vec2 j;void main(void){j=vCoord;gl_Position.xy=2.0*vCoord-vec2(1.0,1.0);gl_Position.zw=vec2(0.0,1.0);}","matdither.glsl":"float f(highp float I){highp float G=0.5*fract(gl_FragCoord.x*0.5)+0.5*fract(gl_FragCoord.y*0.5);return 0.4+0.6*fract(G+3.141592e6*I);}","matfrag.glsl":"\n#extension GL_OES_standard_derivatives : enable\nprecision mediump float;varying highp vec3 dv;varying mediump vec2 d;varying mediump vec3 dA;varying mediump vec3 dB;varying mediump vec3 dC;\n#ifdef VERTEX_COLOR\nvarying lowp vec4 dD;\n#endif\n#ifdef TEXCOORD_SECONDARY\nvarying mediump vec2 dE;\n#endif\nuniform sampler2D tAlbedo;uniform sampler2D tReflectivity;uniform sampler2D tNormal;uniform sampler2D tExtras;uniform sampler2D tSkySpecular;\n#ifdef REFRACTION\nuniform sampler2D tRefraction;\n#endif\nuniform vec4 uDiffuseCoefficients[9];uniform vec3 uCameraPosition;uniform float uAlphaTest;uniform vec3 uFresnel;uniform float uHorizonOcclude;uniform float uHorizonSmoothing;\n#ifdef EMISSIVE\nuniform float uEmissiveScale;uniform vec4 uTexRangeEmissive;\n#endif\n#ifdef AMBIENT_OCCLUSION\nuniform vec4 uTexRangeAO;\n#endif\n#ifdef REFRACTION\nuniform float uRefractionIOREntry;uniform float uRefractionRayDistance;uniform vec3 uRefractionTint;uniform float uRefractionAlbedoTint;uniform mat4 uRefractionViewProjection;uniform vec4 uTexRangeRefraction;\n#endif\n#ifdef LIGHT_COUNT\nuniform vec4 uLightPositions[LIGHT_COUNT];uniform vec3 uLightDirections[LIGHT_COUNT];uniform vec3 uLightColors[LIGHT_COUNT];uniform vec3 uLightParams[LIGHT_COUNT];uniform vec3 uLightSpot[LIGHT_COUNT];\n#endif\n#ifdef ANISO\nuniform float uAnisoStrength;uniform vec3 uAnisoTangent;uniform float uAnisoIntegral;uniform vec4 uTexRangeAniso;\n#endif\n#define saturate(x) clamp( x, 0.0, 1.0 )\n#include <matsampling.glsl>\n#include <matlighting.glsl>\n#include <matshadows.glsl>\n#include <matskin.glsl>\n#include <matmicrofiber.glsl>\n#include <matstrips.glsl>\n#ifdef TRANSPARENCY_DITHER\n#include <matdither.glsl>\n#endif\nvoid main(void){vec4 m=texture2D(tAlbedo,d);vec3 dF=dG(m.xyz);float e=m.w;\n#ifdef VERTEX_COLOR\n{vec3 dH=dD.xyz;\n#ifdef VERTEX_COLOR_SRGB\ndH=dH*(dH*(dH*0.305306011+vec3(0.682171111))+vec3(0.012522878));\n#endif\ndF*=dH;\n#ifdef VERTEX_COLOR_ALPHA\ne*=dD.w;\n#endif\n}\n#endif\n#ifdef ALPHA_TEST\nif(e<uAlphaTest){discard;}\n#endif\n#ifdef TRANSPARENCY_DITHER\ne=(e>f(d.x))?1.0:e;\n#endif\nvec3 dI=dJ(texture2D(tNormal,d).xyz);\n#ifdef ANISO\n#ifdef ANISO_NO_DIR_TEX\nvec3 dK=dL(uAnisoTangent);\n#else\nm=dM(d,uTexRangeAniso);vec3 dK=2.0*m.xyz-vec3(1.0);dK=dL(dK);\n#endif\ndK=dK-dI*dot(dK,dI);dK=normalize(dK);vec3 dN=dK*uAnisoStrength;\n#endif\nvec3 dO=normalize(uCameraPosition-dv);m=texture2D(tReflectivity,d);vec3 dP=dG(m.xyz);float dQ=m.w;float dR=dQ;\n#ifdef HORIZON_SMOOTHING\nfloat dS=dot(dO,dI);dS=uHorizonSmoothing-dS*uHorizonSmoothing;dQ=mix(dQ,1.0,dS*dS);\n#endif\n#ifdef STRIPVIEW\ndT dU;dV(dU,dQ,dP);\n#endif\nfloat dW=1.0;\n#ifdef AMBIENT_OCCLUSION\n#ifdef AMBIENT_OCCLUSION_SECONDARY_UV\ndW=dM(dE,uTexRangeAO).x;\n#else\ndW=dM(d,uTexRangeAO).x;\n#endif\ndW*=dW;\n#endif\n#if defined(SKIN)\ndX dY;dZ(dY);dY.ec*=dW;
34455\n#elif defined(MICROFIBER)\ned ee;ef(ee,dI);ee.eh*=dW;\n#else\nvec3 ei=ej(dI);ei*=dW;\n#endif\nvec3 ek=reflect(-dO,dI);\n#ifdef ANISO\nvec3 rt=ek-(0.5*dN*dot(ek,dK));vec3 el=em(rt,mix(dQ,0.5*dQ,uAnisoStrength));\n#else\nvec3 el=em(ek,dQ);\n#endif\nel*=en(ek,dC);\n#ifdef LIGHT_COUNT\nhighp float eo=10.0/log2(dQ*0.968+0.03);eo*=eo;float eu=eo*(1.0/(8.0*3.1415926))+(4.0/(8.0*3.1415926));eu=min(eu,1.0e3);\n#ifdef SHADOW_COUNT\nev eA;\n#ifdef SKIN\n#ifdef SKIN_VERSION_1\neB(eA,SHADOW_KERNEL+SHADOW_KERNEL*dY.eC);\n#else\neD eE;float eF=SHADOW_KERNEL+SHADOW_KERNEL*dY.eC;eG(eE,eF);eB(eA,eF);\n#endif\n#else\neB(eA,SHADOW_KERNEL);\n#endif\n#endif\n#ifdef ANISO\neu*=uAnisoIntegral;\n#endif\nfor(int k=0;k<LIGHT_COUNT;++k){vec3 eH=uLightPositions[k].xyz-dv*uLightPositions[k].w;float eI=inversesqrt(dot(eH,eH));eH*=eI;float a=saturate(uLightParams[k].z/eI);a=1.0+a*(uLightParams[k].x+uLightParams[k].y*a);float s=saturate(dot(eH,uLightDirections[k]));s=saturate(uLightSpot[k].y-uLightSpot[k].z*(1.0-s*s));vec3 eJ=(a*s)*uLightColors[k].xyz;\n#if defined(SKIN)\n#ifdef SHADOW_COUNT\n#ifdef SKIN_VERSION_1\neK(dY,eA.eL[k],1.0,eH,dI,eJ);\n#else\neK(dY,eA.eL[k],eE.eE[k],eH,dI,eJ);\n#endif\n#else\neK(dY,1.0,0.0,eH,dI,eJ);\n#endif\n#elif defined(MICROFIBER)\n#ifdef SHADOW_COUNT\neM(ee,eA.eL[k],eH,dI,eJ);\n#else\neM(ee,1.0,eH,dI,eJ);\n#endif\n#else\nfloat eN=saturate((1.0/3.1415926)*dot(eH,dI));\n#ifdef SHADOW_COUNT\neN*=eA.eL[k];\n#endif\nei+=eN*eJ;\n#endif\nvec3 eO=eH+dO;\n#ifdef ANISO\neO=eO-(dN*dot(eO,dK));\n#endif\neO=normalize(eO);float eP=eu*pow(saturate(dot(eO,dI)),eo);\n#ifdef SHADOW_COUNT\neP*=eA.eL[k];\n#endif\nel+=eP*eJ;}\n#endif\n#if defined(SKIN)\nvec3 ei,diff_extra;eQ(ei,diff_extra,dY,dO,dI,dQ);\n#elif defined(MICROFIBER)\nvec3 ei,diff_extra;eR(ei,diff_extra,ee,dO,dI,dQ);\n#endif\nvec3 eS=eT(dO,dI,dP,dQ*dQ);el*=eS;\n#ifdef REFRACTION\nvec4 eU;{vec3 G=refract(-dO,dI,uRefractionIOREntry);G=dv+G*uRefractionRayDistance;vec4 eV=uRefractionViewProjection[0]*G.x+(uRefractionViewProjection[1]*G.y+(uRefractionViewProjection[2]*G.z+uRefractionViewProjection[3]));vec2 c=eV.xy/eV.w;c=0.5*c+vec2(0.5,0.5);vec2 i=mod(floor(c),2.0);c=fract(c);c.x=i.x>0.0?1.0-c.x:c.x;c.y=i.y>0.0?1.0-c.y:c.y;eU.rgb=texture2D(tRefraction,c).xyz;eU.rgb=mix(eU.rgb,eU.rgb*dF,uRefractionAlbedoTint);eU.rgb=eU.rgb-eU.rgb*eS;eU.rgb*=uRefractionTint;\n#ifdef REFRACTION_NO_MASK_TEX\neU.a=1.0;\n#else\neU.a=dM(d,uTexRangeRefraction).x;\n#endif\n}\n#endif\n#ifdef DIFFUSE_UNLIT\ngl_FragColor.xyz=dF;\n#else\ngl_FragColor.xyz=ei*dF;\n#endif\n#ifdef REFRACTION\ngl_FragColor.xyz=mix(gl_FragColor.xyz,eU.rgb,eU.a);\n#endif\ngl_FragColor.xyz+=el;\n#if defined(SKIN) || defined(MICROFIBER)\ngl_FragColor.xyz+=diff_extra;\n#endif\n#ifdef EMISSIVE\n#ifdef EMISSIVE_SECONDARY_UV\nvec2 eW=dE;\n#else\nvec2 eW=d;\n#endif\ngl_FragColor.xyz+=uEmissiveScale*dG(dM(eW,uTexRangeEmissive).xyz);\n#endif\n#ifdef STRIPVIEW\ngl_FragColor.xyz=eX(dU,dI,dF,dP,dR,ei,el,gl_FragColor.xyz);\n#endif\n#ifdef NOBLEND\ngl_FragColor.w=1.0;\n#else\ngl_FragColor.w=e;\n#endif\n}",
34456"matlighting.glsl":"vec3 eY(vec3 eZ,float fc){return exp(-0.5*fc/(eZ*eZ))/(eZ*2.5066283);}vec3 fd(vec3 eZ){return vec3(1.0,1.0,1.0)/(eZ*2.5066283);}vec3 fe(vec3 ff){return vec3(-0.5,-0.5,-0.5)/(ff);}vec3 fh(vec3 fi,float fc){return exp(fi*fc);}\n#define SAMPLE_COUNT 21.0\n#define SAMPLE_HALF 10.0\n#define GAUSS_SPREAD 0.05\nvec3 fj(float fk,float fl,vec3 fm){vec3 fn=vec3(fl,fl,fl);fn=0.8*fn+vec3(0.2);vec3 fo=cos(fn*3.14159);vec3 fu=cos(fn*3.14159*0.5);fu*=fu;fu*=fu;fu*=fu;fn=fn+0.05*fo*fu*fm;fu*=fu;fu*=fu;fu*=fu;fn=fn+0.1*fo*fu*fm;fn=saturate(fn);fn*=fn*1.2;return fn;}vec3 fv(vec3 fm){return vec3(1.0,1.0,1.0)/3.1415926;}float fA(float fk,float fm){return saturate(-fk*fm+fk+fm);}vec3 fB(float fk,vec3 fm){return saturate(-fk*fm+vec3(fk)+fm);}float fC(float fm){return-0.31830988618379*fm+0.31830988618379;}vec3 fD(vec3 fm){return-0.31830988618379*fm+vec3(0.31830988618379);}vec3 eT(vec3 dO,vec3 dI,vec3 dP,float fE){float C=1.0-saturate(dot(dO,dI));float fF=C*C;C*=fF*fF;C*=fE;return(dP-C*dP)+C*uFresnel;}vec2 fG(vec2 fH,vec2 fm){fH=1.0-fH;vec2 fI=fH*fH;fI*=fI;fH=mix(fI,fH*0.4,fm);return fH;}vec3 ej(vec3 fJ){\n#define c(n) uDiffuseCoefficients[n].xyz\nvec3 G=(c(0)+fJ.y*((c(1)+c(4)*fJ.x)+c(5)*fJ.z))+fJ.x*(c(3)+c(7)*fJ.z)+c(2)*fJ.z;\n#undef c\nvec3 sqr=fJ*fJ;G+=uDiffuseCoefficients[6].xyz*(3.0*sqr.z-1.0);G+=uDiffuseCoefficients[8].xyz*(sqr.x-sqr.y);return G;}void fK(inout vec3 fL,inout vec3 fM,inout vec3 fN,vec3 fJ){fL=uDiffuseCoefficients[0].xyz;fM=uDiffuseCoefficients[1].xyz*fJ.y;fM+=uDiffuseCoefficients[2].xyz*fJ.z;fM+=uDiffuseCoefficients[3].xyz*fJ.x;vec3 swz=fJ.yyz*fJ.xzx;fN=uDiffuseCoefficients[4].xyz*swz.x;fN+=uDiffuseCoefficients[5].xyz*swz.y;fN+=uDiffuseCoefficients[7].xyz*swz.z;vec3 sqr=fJ*fJ;fN+=uDiffuseCoefficients[6].xyz*(3.0*sqr.z-1.0);fN+=uDiffuseCoefficients[8].xyz*(sqr.x-sqr.y);}vec3 fO(vec3 fL,vec3 fM,vec3 fN,vec3 fP,float fm){fP=mix(vec3(1.0),fP,fm);return(fL+fM*fP.x)+fN*fP.z;}vec3 fQ(vec3 fL,vec3 fM,vec3 fN,vec3 fP,vec3 fR){vec3 fS=mix(vec3(1.0),fP.yyy,fR);vec3 fT=mix(vec3(1.0),fP.zzz,fR);
34456return(fL+fM*fS)+fN*fT;}vec3 em(vec3 fJ,float dQ){fJ/=dot(vec3(1.0),abs(fJ));vec2 fU=abs(fJ.zx)-vec2(1.0,1.0);vec2 fV=vec2(fJ.x<0.0?fU.x:-fU.x,fJ.z<0.0?fU.y:-fU.y);vec2 fW=(fJ.y<0.0)?fV:fJ.xz;fW=vec2(0.5*(254.0/256.0),0.125*0.5*(254.0/256.0))*fW+vec2(0.5,0.125*0.5);float fX=fract(7.0*dQ);fW.y+=0.125*(7.0*dQ-fX);vec2 fY=fW+vec2(0.0,0.125);vec4 fZ=mix(texture2D(tSkySpecular,fW),texture2D(tSkySpecular,fY),fX);vec3 r=fZ.xyz*(7.0*fZ.w);return r*r;}float en(vec3 fJ,vec3 hc){float hd=dot(fJ,hc);hd=saturate(1.0+uHorizonOcclude*hd);return hd*hd;}",
34457"matmicrofiber.glsl":"\n#ifdef MICROFIBER\nuniform vec4 uTexRangeFuzz;uniform vec4 uFresnelColor;uniform float uFresnelIntegral;uniform float uFresnelOcc;uniform float uFresnelGlossMask;struct ed{vec3 eh;vec3 eN;vec3 he;vec3 hf;vec3 hh;};void ef(out ed s,vec3 dI){s.eh=s.eN=ej(dI);s.he=vec3(0.0);s.hf=uFresnelColor.rgb;s.hh=uFresnelColor.aaa*vec3(1.0,0.5,0.25);\n#ifndef MICROFIBER_NO_FUZZ_TEX\nvec4 m=dM(d,uTexRangeFuzz);s.hf*=dG(m.rgb);\n#endif\n}void eM(inout ed s,float hi,vec3 eH,vec3 dI,vec3 eJ){float fk=dot(eH,dI);float eN=saturate((1.0/3.1415926)*fk);float hj=fA(fk,s.hh.z);\n#ifdef SHADOW_COUNT\neN*=hi;float hk=mix(1.0,hi,uFresnelOcc);float he=hj*hk;\n#else \nfloat he=hj;\n#endif\ns.he=he*eJ+s.he;s.eN=eN*eJ+s.eN;}void eR(out vec3 ei,out vec3 diff_extra,inout ed s,vec3 dO,vec3 dI,float dQ){s.he*=uFresnelIntegral;float fH=dot(dO,dI);vec2 hl=fG(vec2(fH,fH),s.hh.xy);s.he=s.eh*hl.x+(s.he*hl.y);s.he*=s.hf;float hm=saturate(1.0+-uFresnelGlossMask*dQ);s.he*=hm*hm;ei=s.eN;diff_extra=s.he;}\n#endif\n",
34458"matsampling.glsl":"vec3 dG(vec3 c){return c*c;}vec3 dJ(vec3 n){vec3 hn=dA;vec3 ho=dB;vec3 hu=gl_FrontFacing?dC:-dC;\n#ifdef TSPACE_RENORMALIZE\nhu=normalize(hu);\n#endif\n#ifdef TSPACE_ORTHOGONALIZE\nhn-=dot(hn,hu)*hu;\n#endif\n#ifdef TSPACE_RENORMALIZE\nhn=normalize(hn);\n#endif\n#ifdef TSPACE_ORTHOGONALIZE\nho=(ho-dot(ho,hu)*hu)-dot(ho,hn)*hn;\n#endif\n#ifdef TSPACE_RENORMALIZE\nho=normalize(ho);\n#endif\n#ifdef TSPACE_COMPUTE_BITANGENT\nvec3 hv=cross(hu,hn);ho=dot(hv,ho)<0.0?-hv:hv;\n#endif\nn=2.0*n-vec3(1.0);return normalize(hn*n.x+ho*n.y+hu*n.z);}vec3 dL(vec3 t){vec3 hu=gl_FrontFacing?dC:-dC;return normalize(dA*t.x+dB*t.y+hu*t.z);}vec4 dM(vec2 hA,vec4 hB){\n#if GL_OES_standard_derivatives\nvec2 hC=fract(hA);vec2 hD=fwidth(hC);float hE=(hD.x+hD.y)>0.5?-6.0:0.0;return texture2D(tExtras,hC*hB.xy+hB.zw,hE);\n#else\nreturn texture2D(tExtras,fract(hA)*hB.xy+hB.zw);\n#endif\n}vec3 hF(sampler2D hG,vec2 hH,float hI){vec3 n=texture2D(hG,hH,hI*2.5).xyz;return dJ(n);}",
34459"matshadows.glsl":"\n#ifdef SHADOW_COUNT\n#ifdef MOBILE\n#define SHADOW_KERNEL (4.0/1536.0)\n#else\n#define SHADOW_KERNEL (4.0/2048.0)\n#endif\nhighp vec4 h(highp mat4 i,highp vec3 p){return i[0]*p.x+(i[1]*p.y+(i[2]*p.z+i[3]));}uniform sampler2D tDepth0;\n#if SHADOW_COUNT > 1\nuniform sampler2D tDepth1;\n#if SHADOW_COUNT > 2\nuniform sampler2D tDepth2;\n#endif\n#endif\nuniform highp vec2 uShadowKernelRotation;uniform highp vec2 uShadowMapSize;uniform highp mat4 uShadowMatrices[SHADOW_COUNT];uniform highp vec4 uShadowTexelPadProjections[SHADOW_COUNT];\n#ifndef MOBILE\nuniform highp mat4 uInvShadowMatrices[SHADOW_COUNT];\n#endif\nhighp float hJ(highp vec3 G){\n#ifdef SHADOW_NATIVE_DEPTH\nreturn G.x;\n#else\nreturn(G.x+G.y*(1.0/255.0))+G.z*(1.0/65025.0);\n#endif\n}\n#ifndef SHADOW_COMPARE\n#define SHADOW_COMPARE(a,b) ((a) < (b) ? 1.0 : 0.0)\n#endif\n#ifndef SHADOW_CLIP\n#define SHADOW_CLIP(c,v) v\n#endif\nfloat hK(sampler2D hL,highp vec2 hA,highp float H){\n#ifndef MOBILE\nhighp vec2 c=hA*uShadowMapSize.x;highp vec2 a=floor(c)*uShadowMapSize.y,b=ceil(c)*uShadowMapSize.y;highp vec4 eE;eE.x=hJ(texture2D(hL,a).xyz);eE.y=hJ(texture2D(hL,vec2(b.x,a.y)).xyz);eE.z=hJ(texture2D(hL,vec2(a.x,b.y)).xyz);eE.w=hJ(texture2D(hL,b).xyz);highp vec4 hM;hM.x=SHADOW_COMPARE(H,eE.x);hM.y=SHADOW_COMPARE(H,eE.y);hM.z=SHADOW_COMPARE(H,eE.z);hM.w=SHADOW_COMPARE(H,eE.w);highp vec2 w=c-a*uShadowMapSize.x;vec2 s=(w.y*hM.zw+hM.xy)-w.y*hM.xy;return(w.x*s.y+s.x)-w.x*s.x;\n#else\nhighp float G=hJ(texture2D(hL,hA.xy).xyz);return SHADOW_COMPARE(H,G);\n#endif\n}highp float hN(sampler2D hL,highp vec3 hA,float hO){highp vec2 l=uShadowKernelRotation*hO;float s;s=hK(hL,hA.xy+l,hA.z);s+=hK(hL,hA.xy-l,hA.z);s+=hK(hL,hA.xy+vec2(-l.y,l.x),hA.z);s+=hK(hL,hA.xy+vec2(l.y,-l.x),hA.z);s*=0.25;return s*s;}struct ev{float eL[LIGHT_COUNT];};void eB(out ev ss,float hO){highp vec3 hP[SHADOW_COUNT];vec3 hu=gl_FrontFacing?dC:-dC;for(int k=0;k<SHADOW_COUNT;++k){vec4 hQ=uShadowTexelPadProjections[k];
34459float hR=hQ.x*dv.x+(hQ.y*dv.y+(hQ.z*dv.z+hQ.w));\n#ifdef MOBILE\nhR*=.001+hO;\n#else\nhR*=.0005+0.5*hO;\n#endif\nhighp vec4 hS=h(uShadowMatrices[k],dv+hR*hu);hP[k]=hS.xyz/hS.w;}float m;\n#if SHADOW_COUNT > 0\nm=hN(tDepth0,hP[0],hO);ss.eL[0]=SHADOW_CLIP(hP[0].xy,m);\n#endif\n#if SHADOW_COUNT > 1\nm=hN(tDepth1,hP[1],hO);ss.eL[1]=SHADOW_CLIP(hP[1].xy,m);\n#endif\n#if SHADOW_COUNT > 2\nm=hN(tDepth2,hP[2],hO);ss.eL[2]=SHADOW_CLIP(hP[2].xy,m);\n#endif\nfor(int k=SHADOW_COUNT;k<LIGHT_COUNT;++k){ss.eL[k]=1.0;}}struct eD{highp float eE[LIGHT_COUNT];};\n#ifdef MOBILE\nvoid eG(out eD ss,float hO){for(int k=0;k<LIGHT_COUNT;++k){ss.eE[k]=1.0;}}\n#else\nhighp vec4 hT(sampler2D hL,highp vec2 hA,highp mat4 hU){highp vec4 E;E.xy=hA;\n#ifndef MOBILE\nhighp vec2 c=hA*uShadowMapSize.x;highp vec2 a=floor(c)*uShadowMapSize.y,b=ceil(c)*uShadowMapSize.y;highp vec4 hM;hM.x=hJ(texture2D(hL,a).xyz);hM.y=hJ(texture2D(hL,vec2(b.x,a.y)).xyz);hM.z=hJ(texture2D(hL,vec2(a.x,b.y)).xyz);hM.w=hJ(texture2D(hL,b).xyz);highp vec2 w=c-a*uShadowMapSize.x;vec2 s=(w.y*hM.zw+hM.xy)-w.y*hM.xy;E.z=(w.x*s.y+s.x)-w.x*s.x;\n#else \nE.z=hJ(texture2D(hL,hA.xy).xyz);\n#endif\nE=h(hU,E.xyz);E.xyz/=E.w;return E;}void eG(out eD ss,float hO){highp vec3 hV[SHADOW_COUNT];vec3 hu=gl_FrontFacing?dC:-dC;hu*=0.6;for(int k=0;k<SHADOW_COUNT;++k){vec4 hQ=uShadowTexelPadProjections[k];float hR=hQ.x*dv.x+(hQ.y*dv.y+(hQ.z*dv.z+hQ.w));\n#ifdef MOBILE\nhR*=.001+hO;\n#else\nhR*=.0005+0.5*hO;\n#endif\nhighp vec4 hS=h(uShadowMatrices[k],dv-hR*hu);hV[k]=hS.xyz/hS.w;}highp vec4 hW;\n#if SHADOW_COUNT > 0\nhW=hT(tDepth0,hV[0].xy,uInvShadowMatrices[0]);ss.eE[0]=length(dv.xyz-hW.xyz);\n#endif\n#if SHADOW_COUNT > 1\nhW=hT(tDepth1,hV[1].xy,uInvShadowMatrices[1]);ss.eE[1]=length(dv.xyz-hW.xyz);\n#endif\n#if SHADOW_COUNT > 2\nhW=hT(tDepth2,hV[2].xy,uInvShadowMatrices[2]);ss.eE[2]=length(dv.xyz-hW.xyz);\n#endif\nfor(int k=SHADOW_COUNT;k<LIGHT_COUNT;++k){ss.eE[k]=1.0;}}\n#endif\n#endif\n",
34460"matskin.glsl":"\n#ifdef SKIN\n#ifndef SKIN_NO_SUBDERMIS_TEX\nuniform vec4 uTexRangeSubdermis;\n#endif\n#ifndef SKIN_NO_TRANSLUCENCY_TEX\nuniform vec4 uTexRangeTranslucency;\n#endif\n#ifndef SKIN_NO_FUZZ_TEX\nuniform vec4 uTexRangeFuzz;\n#endif\nuniform vec4 uTransColor;uniform vec4 uFresnelColor;uniform vec3 uSubdermisColor;uniform float uTransScatter;uniform float uFresnelOcc;uniform float uFresnelGlossMask;uniform float uTransSky;uniform float uFresnelIntegral;uniform float uTransIntegral;uniform float uSkinTransDepth;uniform float uSkinShadowBlur;uniform float uNormalSmooth;struct dX{vec3 hX;vec3 hY,hZ,ic,he;vec3 ec,eh,id;vec3 ie;vec3 ih;vec3 ii;vec3 ij;float ik;float il;float im;float eC;};void dZ(out dX s){vec4 m;\n#ifdef SKIN_NO_SUBDERMIS_TEX\ns.hX=uSubdermisColor;s.im=1.0;\n#else \nm=dM(d,uTexRangeSubdermis);s.hX=dG(m.xyz);s.im=m.w*m.w;\n#endif\ns.ij=uTransColor.rgb;s.ik=uTransScatter;\n#ifdef SKIN_VERSION_1\ns.eC=uSkinShadowBlur*s.im;\n#else \ns.il=max(max(s.ij.r,s.ij.g),s.ij.b)*uTransColor.a;float io=max(s.hX.r,max(s.hX.g,s.hX.b));io=1.0-io;io*=io;io*=io;io*=io;io=1.0-(io*io);s.im*=io;s.eC=uSkinShadowBlur*s.im*dot(s.hX.rgb,vec3(0.333,0.334,0.333));\n#endif\n#ifndef SKIN_NO_TRANSLUCENCY_TEX\nm=dM(d,uTexRangeTranslucency);s.ij*=dG(m.xyz);\n#endif\ns.ie=hF(tNormal,d,uNormalSmooth*s.im);vec3 iu,iv,iA;fK(iu,iv,iA,s.ie);s.eh=s.hY=iu+iv+iA;\n#ifdef SKIN_VERSION_1 \ns.ec=fQ(iu,iv,iA,vec3(1.0,0.6667,0.25),s.hX);\n#else\ns.ec=fQ(iu,iv,iA,vec3(1.0,0.6667,0.25),s.hX*0.2+vec3(0.1));\n#endif\n#ifdef SKIN_VERSION_1\nvec3 iB,iC,iD;fK(iB,iC,iD,-s.ie);s.id=fO(iB,iC,iD,vec3(1.0,0.4444,0.0625),s.ik);s.id*=uTransSky;\n#else \ns.id=vec3(0.0);\n#endif\ns.hZ=s.ic=s.he=vec3(0.0);s.hX*=0.5;s.ik*=0.5;s.ih=uFresnelColor.rgb;s.ii=uFresnelColor.aaa*vec3(1.0,0.5,0.25);\n#ifndef SKIN_NO_FUZZ_TEX\nm=dM(d,uTexRangeFuzz);s.ih*=dG(m.rgb);\n#endif\n}void eK(inout dX s,float iE,float iF,vec3 eH,vec3 dI,vec3 eJ){float fk=dot(eH,dI);float fl=dot(eH,s.ie);float eN=saturate((1.0/3.1415926)*fk);float hi=iE*iE;hi*=hi;hi=saturate(6.0*hi);\n#ifdef SKIN_VERSION_1 \nvec3 iG=fB(fl,s.hX);\n#else \nvec3 iG=fj(fk,fl,s.hX);\n#endif\nfloat iH=fA(-fl,s.ik);vec3 ic=vec3(iH*iH);\n#ifdef SKIN_VERSION_1\n#ifdef SHADOW_COUNT\nvec3 iI=vec3(iE);float iJ=saturate(hi-2.0*(iE*iE));iI+=iJ*s.hX;float iK=iE;\n#endif\n#else\n#ifdef SHADOW_COUNT\nvec3 iI;highp vec3 iL=(0.995*s.hX)+vec3(0.005,0.005,0.005);highp vec3 iM=vec3(1.0)-iL;iL=mix(iL,iM,iE);float iN=sqrt(iE);vec3 iO=2.0*vec3(1.0-iN);iN=1.0-iN;iN=(1.0-iN*iN);iI=saturate(pow(iL*iN,iO));highp float iP=0.35/(uSkinTransDepth+0.001);highp float iQ=saturate(iF*iP);
34460iQ=saturate(1.0-iQ);iQ*=iQ;highp vec3 iR=vec3((-3.0*iQ)+3.15);highp vec3 iS=(0.9975*s.ij)+vec3(0.0025,0.0025,0.0025);highp float io=saturate(10.0*dot(iS,iS));vec3 iK=pow(iS*iQ,iR)*io;\n#else \nic=vec3(0.0);\n#endif\n#endif\nfloat hj=fA(fl,s.ii.z);\n#ifdef SHADOW_COUNT\nvec3 hk=mix(vec3(1.0),iI,uFresnelOcc);vec3 he=hj*hk;\n#else\nvec3 he=vec3(hj);\n#endif\n#ifdef SHADOW_COUNT\niG*=iI;eN*=hi;ic*=iK;\n#endif\ns.he=he*eJ+s.he;s.ic=ic*eJ+s.ic;s.hZ=iG*eJ+s.hZ;s.hY=eN*eJ+s.hY;}void eQ(out vec3 ei,out vec3 diff_extra,inout dX s,vec3 dO,vec3 dI,float dQ){s.he*=uFresnelIntegral;float fH=dot(dO,dI);vec2 hl=fG(vec2(fH,fH),s.ii.xy);s.he=s.eh*hl.x+(s.he*hl.y);s.he*=s.ih;float hm=saturate(1.0+-uFresnelGlossMask*dQ);s.he*=hm*hm;s.ic=s.ic*uTransIntegral;\n#ifdef SKIN_VERSION_1\ns.hZ=(s.hZ*fD(s.hX))+s.ec;\n#else\ns.hZ=(s.hZ*fv(s.hX))+s.ec;\n#endif\nei=mix(s.hY,s.hZ,s.im);\n#ifdef SKIN_VERSION_1\ns.ic=(s.ic+s.id)*s.ij;diff_extra=(s.he+s.ic)*s.im;\n#else\nei+=s.ic*s.il;diff_extra=s.he*s.im;\n#endif\n}\n#endif\n",
34461"matstrips.glsl":"\n#ifdef STRIPVIEW\nuniform float uStrips[5];uniform vec2 uStripRes;struct dT{float io[5];float bg;};void dV(out dT iT,inout float dQ,inout vec3 dP){highp vec2 hA=gl_FragCoord.xy*uStripRes-vec2(1.0,1.0);hA.x+=0.25*hA.y;iT.io[0]=step(hA.x,uStrips[0]);iT.io[1]=step(hA.x,uStrips[1]);iT.io[2]=step(hA.x,uStrips[2]);iT.io[3]=step(hA.x,uStrips[3]);iT.io[4]=step(hA.x,uStrips[4]);iT.bg=1.0-iT.io[4];iT.io[4]-=iT.io[3];iT.io[3]-=iT.io[2];iT.io[2]-=iT.io[1];iT.io[1]-=iT.io[0];bool iU=iT.io[4]>0.0;dQ=iU?0.5:dQ;dP=iU?vec3(0.1):dP;}vec3 eX(dT iT,vec3 dI,vec3 dF,vec3 dP,float dQ,vec3 ei,vec3 el,vec3 iV){return iT.io[0]*(dI*0.5+vec3(0.5))+iT.io[1]*dF+iT.io[2]*dP+vec3(iT.io[3]*dQ)+iT.io[4]*(vec3(0.12)+0.3*ei+el)+iT.bg*iV;}\n#endif\n",
34462"matvert.glsl":"precision highp float;uniform mat4 uModelViewProjectionMatrix;uniform mat4 uSkyMatrix;uniform vec2 uUVOffset;attribute vec3 vPosition;attribute vec2 vTexCoord;attribute vec2 vTangent;attribute vec2 vBitangent;attribute vec2 vNormal;\n#ifdef VERTEX_COLOR\nattribute vec4 vColor;\n#endif\n#ifdef TEXCOORD_SECONDARY\nattribute vec2 vTexCoord2;\n#endif\nvarying highp vec3 dv;varying mediump vec2 d;varying mediump vec3 dA;varying mediump vec3 dB;varying mediump vec3 dC;\n#ifdef VERTEX_COLOR\nvarying lowp vec4 dD;\n#endif\n#ifdef TEXCOORD_SECONDARY\nvarying mediump vec2 dE;\n#endif\nvec3 iW(vec2 v){bool iX=(v.y>(32767.1/65535.0));v.y=iX?(v.y-(32768.0/65535.0)):v.y;vec3 r;r.xy=(2.0*65535.0/32767.0)*v-vec2(1.0);r.z=sqrt(clamp(1.0-dot(r.xy,r.xy),0.0,1.0));r.z=iX?-r.z:r.z;return r;}vec4 h(mat4 i,vec3 p){return i[0]*p.x+(i[1]*p.y+(i[2]*p.z+i[3]));}vec3 u(mat4 i,vec3 v){return i[0].xyz*v.x+i[1].xyz*v.y+i[2].xyz*v.z;}void main(void){gl_Position=h(uModelViewProjectionMatrix,vPosition.xyz);d=vTexCoord+uUVOffset;dA=u(uSkyMatrix,iW(vTangent));dB=u(uSkyMatrix,iW(vBitangent));dC=u(uSkyMatrix,iW(vNormal));dv=h(uSkyMatrix,vPosition.xyz).xyz;\n#ifdef VERTEX_COLOR\ndD=vColor;\n#endif\n#ifdef TEXCOORD_SECONDARY\ndE=vTexCoord2;\n#endif\n}",
34463"postaa.glsl":"precision mediump float;uniform sampler2D tInput;varying vec2 j;void main(void){gl_FragColor=texture2D(tInput,j);}","postfrag.glsl":"precision mediump float;uniform sampler2D tInput;\n#ifdef BLOOM\nuniform sampler2D tBloom;\n#endif\n#ifdef GRAIN\nuniform sampler2D tGrain;\n#endif\n#ifdef COLOR_LUT\nuniform sampler2D tLUT;\n#endif\nuniform vec3 uScale;uniform vec3 uBias;uniform vec3 uSaturation;uniform vec4 uSharpenKernel;uniform vec3 uSharpness;uniform vec3 uBloomColor;uniform vec4 uVignetteAspect;uniform vec4 uVignette;uniform vec4 uGrainCoord;uniform vec2 uGrainScaleBias;varying vec2 j;vec3 iY(vec3 c){vec3 iZ=sqrt(c);return(iZ-iZ*c)+c*(0.4672*c+vec3(0.5328));}void main(void){vec4 jc=texture2D(tInput,j);vec3 c=jc.xyz;\n#ifdef SHARPEN\nvec3 hM=texture2D(tInput,j+uSharpenKernel.xy).xyz;hM+=texture2D(tInput,j-uSharpenKernel.xy).xyz;hM+=texture2D(tInput,j+uSharpenKernel.zw).xyz;hM+=texture2D(tInput,j-uSharpenKernel.zw).xyz;vec3 jd=uSharpness.x*c-uSharpness.y*hM;c+=clamp(jd,-uSharpness.z,uSharpness.z);\n#endif\n#ifdef BLOOM\nc+=uBloomColor*texture2D(tBloom,j).xyz;\n#endif\n#ifdef VIGNETTE\nvec2 je=j*uVignetteAspect.xy-uVignetteAspect.zw;vec3 v=clamp(vec3(1.0,1.0,1.0)-uVignette.xyz*dot(je,je),0.0,1.0);vec3 jf=v*v;jf*=v;c*=mix(v,jf,uVignette.w);\n#endif\n#ifdef SATURATION\nfloat gray=dot(c,vec3(0.3,0.59,0.11));c=mix(vec3(gray,gray,gray),c,uSaturation);\n#endif\n#ifdef CONTRAST\nc=c*uScale+uBias;\n#endif\n#ifdef GRAIN\nfloat jh=uGrainScaleBias.x*texture2D(tGrain,j*uGrainCoord.xy+uGrainCoord.zw).x+uGrainScaleBias.y;c+=c*jh;\n#endif\n#ifdef REINHARD\n{c*=1.8;float ji=dot(c,vec3(0.3333));c=clamp(c/(1.0+ji),0.0,1.0);}\n#elif defined(HEJL)\n{const highp float jj=0.22,jk=0.3,jl=.1,jm=0.2,jn=.01,jo=0.3;const highp float ju=1.25;highp vec3 eO=max(vec3(0.0),c-vec3(.004));c=(eO*((ju*jj)*eO+ju*vec3(jl*jk,jl*jk,jl*jk))+ju*vec3(jm*jn,jm*jn,jm*jn))/(eO*(jj*eO+vec3(jk,jk,jk))+vec3(jm*jo,jm*jo,jm*jo))-ju*vec3(jn/jo,jn/jo,jn/jo);}\n#endif\n#ifdef COLOR_LUT\nc=clamp(c,0.0,1.0);c=(255.0/256.0)*c+vec3(0.5/256.0);c.x=texture2D(tLUT,c.xx).x;c.y=texture2D(tLUT,c.yy).y;c.z=texture2D(tLUT,c.zz).z;c*=c;\n#endif\ngl_FragColor.xyz=iY(c);gl_FragColor.w=jc.w;}",
34464"postvert.glsl":"precision highp float;attribute vec2 vCoord;varying vec2 j;void main(void){j=vCoord;gl_Position.xy=2.0*vCoord-vec2(1.0,1.0);gl_Position.zw=vec2(0.0,1.0);}","shadowfloorfrag.glsl":"precision mediump float;varying highp vec3 dv;varying mediump vec2 jv;varying mediump vec3 dC;uniform vec3 uShadowCatcherParams;\n#ifdef LIGHT_COUNT\nuniform vec4 uLightPositions[LIGHT_COUNT];uniform vec3 uLightDirections[LIGHT_COUNT];uniform vec3 uLightColors[LIGHT_COUNT];uniform vec3 uLightParams[LIGHT_COUNT];uniform vec3 uLightSpot[LIGHT_COUNT];\n#endif\n#define saturate(x) clamp( x, 0.0, 1.0 )\n#define SHADOW_COMPARE(a,b) ((a) < (b) || (b) >= 1.0 ? 1.0 : 0.0)\n#define SHADOW_CLIP(c,v) ((c.x<0.0 || c.x>1.0 || c.y<0.0 || c.y>1.0) ? 1.0 : v)\n#include <matshadows.glsl>\nvoid main(void){ev eA;eB(eA,SHADOW_KERNEL);vec3 jA=vec3(0.0,0.0,0.0);vec3 jB=vec3(0.0,0.0,0.0);for(int k=0;k<SHADOW_COUNT;++k){vec3 eH=uLightPositions[k].xyz-dv*uLightPositions[k].w;float eI=inversesqrt(dot(eH,eH));eH*=eI;float a=saturate(uLightParams[k].z/eI);a=1.0+a*(uLightParams[k].x+uLightParams[k].y*a);float s=saturate(dot(eH,uLightDirections[k]));s=saturate(uLightSpot[k].y-uLightSpot[k].z*(1.0-s*s));vec3 jC=mix(uLightColors[k].xyz,vec3(1.0,1.0,1.0),uShadowCatcherParams.x);vec3 jD=(a*s)*jC;jD*=saturate(dot(eH,dC));jB+=jD;jA+=jD*eA.eL[k];}float jE=1.0e-4;vec3 r=(jA+jE)/(jB+jE);float jF=saturate(dot(jv,jv))*uShadowCatcherParams.z;r=mix(r,vec3(1.0,1.0,1.0),jF);r=mix(vec3(1.0,1.0,1.0),r,uShadowCatcherParams.y);gl_FragColor.xyz=r;gl_FragColor.w=1.0;}",
34465"shadowfloorvert.glsl":"precision highp float;uniform mat4 uModelViewProjectionMatrix;uniform mat4 uModelSkyMatrix;uniform float uScale;attribute vec3 vPosition;varying highp vec3 dv;varying mediump vec2 jv;varying mediump vec3 dC;vec4 h(mat4 i,vec3 p){return i[0]*p.x+(i[1]*p.y+(i[2]*p.z+i[3]));}void main(void){jv=vPosition.xz;dC=normalize(uModelSkyMatrix[1].xyz);dv=h(uModelSkyMatrix,vPosition).xyz;gl_Position=h(uModelViewProjectionMatrix,vPosition);}","shadowfrag.glsl":"precision highp float;varying vec2 jG;\n#ifdef ALPHA_TEST\nvarying mediump vec2 d;uniform sampler2D tAlbedo;
34465\n#endif\nvec3 jH(float v){vec4 jI=vec4(1.0,255.0,65025.0,16581375.0)*v;jI=fract(jI);jI.xyz-=jI.yzw*(1.0/255.0);return jI.xyz;}void main(void){\n#ifdef ALPHA_TEST\nfloat e=texture2D(tAlbedo,d).a;if(e<0.5){discard;}\n#endif\n#ifdef SHADOW_NATIVE_DEPTH\ngl_FragColor.xyz=vec3(0.0,0.0,0.0);\n#else\ngl_FragColor.xyz=jH((jG.x/jG.y)*0.5+0.5);\n#endif\ngl_FragColor.w=0.0;}",
34466"shadowvert.glsl":"precision highp float;attribute vec3 vPosition;attribute vec2 vTexCoord;uniform mat4 uMeshTransform;uniform mat4 uViewProjection;varying vec2 jG;\n#ifdef ALPHA_TEST\nvarying mediump vec2 d;uniform vec2 uUVOffset;\n#endif\nvec4 h(mat4 i,vec3 p){return i[0]*p.x+(i[1]*p.y+(i[2]*p.z+i[3]));}void main(void){vec3 p=h(uMeshTransform,vPosition).xyz;gl_Position=h(uViewProjection,p);jG=gl_Position.zw;\n#ifdef ALPHA_TEST\nd=vTexCoord+uUVOffset;\n#endif\n}","sky.glsl":"precision highp float;uniform sampler2D tSkyTexture;uniform float uAlpha;varying vec2 d;void main(void){vec3 r=texture2D(tSkyTexture,d).xyz;gl_FragColor.xyz=r*r;gl_FragColor.w=uAlpha;}",
34467"skySH.glsl":"precision mediump float;uniform vec4 uSkyCoefficients[9];uniform float uAlpha;varying vec3 jJ;void main(void){vec3 G=normalize(jJ);vec3 r=uSkyCoefficients[0].xyz;r+=uSkyCoefficients[1].xyz*G.y;r+=uSkyCoefficients[2].xyz*G.z;r+=uSkyCoefficients[3].xyz*G.x;vec3 swz=G.yyz*G.xzx;r+=uSkyCoefficients[4].xyz*swz.x;r+=uSkyCoefficients[5].xyz*swz.y;r+=uSkyCoefficients[7].xyz*swz.z;vec3 sqr=G*G;r+=uSkyCoefficients[6].xyz*(3.0*sqr.z-1.0);r+=uSkyCoefficients[8].xyz*(sqr.x-sqr.y);gl_FragColor.xyz=r;gl_FragColor.w=uAlpha;}",
34468"skyvert.glsl":"precision highp float;uniform mat4 uInverseSkyMatrix;uniform mat4 uViewProjection;attribute vec3 vPosition;attribute vec2 vTexCoord;\n#if SKYMODE == 3\nvarying vec3 jJ;\n#else\nvarying vec2 d;\n#endif\nvec4 h(mat4 i,vec3 p){return i[0]*p.x+(i[1]*p.y+(i[2]*p.z+i[3]));}vec4 u(mat4 i,vec3 v){return i[0]*v.x+i[1]*v.y+i[2]*v.z;}void main(void){vec3 p=h(uInverseSkyMatrix,vPosition).xyz;gl_Position=u(uViewProjection,p);gl_Position.z-=(1.0/65535.0)*gl_Position.w;\n#if SKYMODE == 3\njJ=vPosition;jJ.xy+=1e-20*vTexCoord;\n#else\nd=vTexCoord;\n#endif\n}",
34469"wirefrag.glsl":"precision highp float;uniform vec4 uStripParams;void main(void){vec2 c=gl_FragCoord.xy*uStripParams.xy-vec2(1.0,1.0);c.x+=0.25*c.y;float a=c.x<uStripParams.z?0.0:0.9;a=c.x<uStripParams.w?a:0.0;gl_FragColor=vec4(0.0,0.0,0.0,a);}","wirevert.glsl":"precision highp float;uniform mat4 uModelViewProjectionMatrix;attribute vec3 vPosition;vec4 h(mat4 i,vec3 p){return i[0]*p.x+(i[1]*p.y+(i[2]*p.z+i[3]));}void main(void){gl_Position=h(uModelViewProjectionMatrix,vPosition);gl_Position.z+=-0.00005*gl_Position.w;}",
34470nil:""};})(marmoset);
34471(function() {
34472  if (App.Widgets == null) {
34473    App.Widgets = {};
34474  }
34475
34476}).call(this);
34477(function() {
34478  App.Widgets.AnchorsManager = (function() {
34479    function AnchorsManager() {}
34480
34481    AnchorsManager.prototype.initialize = function() {
34482      if (location.hash) {
34483        return setTimeout(function() {
34484          var allowToJumpTo;
34485          allowToJumpTo = ['#tab-comments'];
34486          if (_.include(location.hash, '#tab-') && !_.include(allowToJumpTo, location.hash)) {
34487            return window.scrollTo(0, 0);
34488          }
34489        }, 1);
34490      }
34491    };
34492
34493    return AnchorsManager;
34494
34495  })();
34496
34497}).call(this);
34498(function() {
34499  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
34500
34501  App.Widgets.AuthControl = (function() {
34502    function AuthControl() {
34503      this.onModalClick = bind(this.onModalClick, this);
34504      this.requestAuthControl = bind(this.requestAuthControl, this);
34505    }
34506
34507    AuthControl.prototype.initialize = function() {
34508      var $notificationBar;
34509      $(document).on('click', '.js-modal', this.onModalClick);
34510      if (App.config.isSupportSession) {
34511        $notificationBar = $(JST["support/notification_bar"]({
34512          username: App.config.username
34513        }));
34514        return $notificationBar.insertAfter('.notification-bar');
34515      } else {
34516        return $(document).on('click', '.js-auth-control', this.requestAuthControl);
34517      }
34518    };
34519
34520    AuthControl.prototype.requestAuthControl = function(event) {
34521      if (App.config.userSignedIn) {
34522        return;
34523      }
34524      event.preventDefault();
34525      event.stopPropagation();
34526      return App.events.publish('app:login:open');
34527    };
34528
34529    AuthControl.prototype.onModalClick = function(event) {
34530      var modalId;
34531      event.preventDefault();
34532      if ($(this).hasClass('js-auth-control') && !App.config.userSignedIn) {
34533        return;
34534      }
34535      modalId = $(event.currentTarget).attr('href') || $(event.currentTarget).data('href');
34536      return $(modalId).makeMeModal();
34537    };
34538
34539    return AuthControl;
34540
34541  })();
34542
34543}).call(this);
34544(function() {
34545  App.Widgets.AutoDisableLink = (function() {
34546    function AutoDisableLink() {}
34547
34548    AutoDisableLink.prototype.initialize = function() {
34549      return $(document).on('click', '.js-auto-disable-link', function(event) {
34550        var linkOrButton;
34551        linkOrButton = $(this);
34552        if (linkOrButton.attr('disabled')) {
34553          event.stopPropagation();
34554          return event.preventDefault();
34555        } else {
34556          return setTimeout(function() {
34557            return linkOrButton.attr('disabled', true);
34558          }, 100);
34559        }
34560      });
34561    };
34562
34563    return AutoDisableLink;
34564
34565  })();
34566
34567}).call(this);
34568(function() {
34569  App.Widgets.BackendEvents = (function() {
34570    function BackendEvents() {}
34571
34572    BackendEvents.prototype.initialize = function() {
34573      return this.pushGTMEventsToDataLayer();
34574    };
34575
34576    BackendEvents.prototype.pushGTMEventsToDataLayer = function() {
34577      var e, i, len;
34578      for (i = 0, len = customDataLayer.length; i < len; i++) {
34579        e = customDataLayer[i];
34580        dataLayer.push(e);
34581      }
34582      return true;
34583    };
34584
34585    return BackendEvents;
34586
34587  })();
34588
34589}).call(this);
34590(function() {
34591  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
34592
34593  App.Widgets.BuyButton = (function() {
34594    function BuyButton() {
34595      this.onJsOrderClicked = bind(this.onJsOrderClicked, this);
34596    }
34597
34598    BuyButton.prototype.initialize = function() {
34599      return this.bindEvents();
34600    };
34601
34602    BuyButton.prototype.bindEvents = function() {
34603      return $(document).on('click', '.js-order', this.onJsOrderClicked);
34604    };
34605
34606    BuyButton.prototype.onJsOrderClicked = function(event) {
34607      mute(event);
34608      return this.addToCart($(event.currentTarget), "/api/internal/cg/items", {
34609        kind: 'cg'
34610      }, function(response) {
34611        return true;
34612      });
34613    };
34614
34615    BuyButton.prototype.addToCart = function(button, path, data, callback) {
34616      var itemId;
34617      if (data == null) {
34618        data = {};
34619      }
34620      button.attr('disabled', true);
34621      itemId = button.data('id');
34622      data.offer_id = button.data('offer-id');
34623      data.meta_source = button.data('meta-source');
34624      data.meta_referrer = document.referrer != null ? document.referrer : location.href;
34625      return $.ajax(path + "/" + itemId + "/cart_items?locale=" + I18n.locale, {
34626        data: data,
34627        method: 'POST',
34628        error: function(jqXHR) {
34629          App.events.publish("app:notification:flash:error", jqXHR.responseJSON.errors.detail);
34630          return button.attr('disabled', false);
34631        },
34632        success: function(data) {
34633          var message;
34634          message = data.included[0].attributes.title + " " + (I18n.t('navigation.item_add_message'));
34635          App.events.publish('app:cart:added', data);
34636          App.events.publish('app:cart:add', data.data.attributes.kind);
34637          App.events.publish("app:notification:flash:success", message);
34638          if (typeof callback === "function") {
34639            callback(data);
34640          }
34641          return button.attr('disabled', false);
34642        }
34643      });
34644    };
34645
34646    return BuyButton;
34647
34648  })();
34649
34650}).call(this);
34651(function() {
34652  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
34653
34654  App.Widgets.Comments = (function() {
34655    Comments.prototype.$el = null;
34656
34657    function Comments(options) {
34658      this.remove = bind(this.remove, this);
34659      this.replyTo = bind(this.replyTo, this);
34660      this.edit = bind(this.edit, this);
34661      if (options == null) {
34662        return;
34663      }
34664      this.options = options;
34665      this.$el = options.el;
34666    }
34667
34668    Comments.prototype.initialize = function() {
34669      var data;
34670      if (!((this.$el != null) && this.$el.exists())) {
34671        this.$el = $(".comments");
34672      }
34673      data = this.$el.data();
34674      if (!data) {
34675        return;
34676      }
34677      this.commentableType = data.commentable_type;
34678      this.commentableId = data.commentable_id;
34679      this.initPlugins();
34680      this.initNewCommentForm();
34681      this.bindEvents();
34682      return this.showButtons();
34683    };
34684
34685    Comments.prototype.bindEvents = function() {
34686      this.$el.find('.comment .js-reply').on('click', this.replyTo);
34687      this.$el.find('.comment .js-edit').on('click', this.edit);
34688      return this.$el.find('.comment .js-remove').on('click', this.remove);
34689    };
34690
34691    Comments.prototype.edit = function(e) {
34692      var $el, comment;
34693      $el = $(e.currentTarget).closest(".comment").addClass('is-edited');
34694      comment = new App.Backbone.Models.Comment({
34695        id: $el.data('id')
34696      });
34697      return comment.fetch({
34698        success: (function(_this) {
34699          return function() {
34700            var editForm;
34701            editForm = new App.Backbone.Views.Comments.Form({
34702              model: comment
34703            }).render();
34704            $el.html(editForm.el);
34705            return App.events.once('app:comment:posted', function() {
34706              return $el.removeClass('is-edited');
34707            });
34708          };
34709        })(this)
34710      });
34711    };
34712
34713    Comments.prototype.replyTo = function(e) {
34714      var $el, newComment, newCommentContainer, newCommentDepth, newForm;
34715      $el = $(e.currentTarget).parents('.comment');
34716      newComment = new App.Backbone.Models.Comment({
34717        parent_id: $el.data('id'),
34718        commentable_type: this.commentableType,
34719        commentable_id: this.commentableId
34720      });
34721      newForm = new App.Backbone.Views.Comments.Form({
34722        model: newComment
34723      }).render();
34724      newCommentContainer = $('<article class="comment"></article>');
34725      newCommentDepth = 1;
34726      newCommentContainer.addClass("comment--depth-" + newCommentDepth);
34727      newCommentContainer.html(newForm.el);
34728      return $(newCommentContainer).insertAfter($el);
34729    };
34730
34731    Comments.prototype.remove = function(e) {
34732      var $el, comment;
34733      $el = $(e.currentTarget).closest('.comment');
34734      comment = new App.Backbone.Models.Comment({
34735        id: $el.data('id')
34736      });
34737      comment.destroy();
34738      $el.nextUntil('.comment--depth-0').hide('slow');
34739      return $el.hide('slow');
34740    };
34741
34742    Comments.prototype.initNewCommentForm = function() {
34743      var newComment, newForm;
34744      newComment = new App.Backbone.Models.Comment({
34745        commentable_type: this.commentableType,
34746        commentable_id: this.commentableId
34747      });
34748      newForm = new App.Backbone.Views.Comments.Form({
34749        model: newComment
34750      }).render();
34751      if ((this.options != null) && this.options.placement === 'append') {
34752        return this.$el.append(newForm.el);
34753      } else {
34754        return this.$el.prepend(newForm.el);
34755      }
34756    };
34757
34758    Comments.prototype.initPlugins = function() {
34759      return this.$el.find(".timeago").timeago();
34760    };
34761
34762    Comments.prototype.showButtons = function() {
34763      return this.$el.find('.comment').each(function(i, el) {
34764        var $comment;
34765        $comment = $(el);
34766        if (App.config.userId === $comment.data('author-id')) {
34767          $comment.find(".comment__actions .js-edit").removeClass('is-hidden');
34768        } else {
34769          $comment.find(".comment__actions .js-reply").removeClass('is-hidden');
34770        }
34771        if (App.config.isAdmin) {
34772          return $comment.find(".comment__actions .js-remove").removeClass('is-hidden');
34773        }
34774      });
34775    };
34776
34777    return Comments;
34778
34779  })();
34780
34781}).call(this);
34782(function() {
34783  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
34784
34785  App.Widgets.CookiesPolicy = (function() {
34786    function CookiesPolicy() {
34787      this.closeCookiesModal = bind(this.closeCookiesModal, this);
34788      this.closeCookiesPolicy = bind(this.closeCookiesPolicy, this);
34789      this.acceptCookiesPolicy = bind(this.acceptCookiesPolicy, this);
34790    }
34791
34792    CookiesPolicy.prototype.initialize = function() {
34793      var cookieEnabled, noticeAlreadyExists;
34794      if (gon.cookies_map == null) {
34795        return;
34796      }
34797      if (App.config.environment === 'test') {
34798        return;
34799      }
34800      cookieEnabled = navigator.cookieEnabled;
34801      noticeAlreadyExists = $('.js-inline-cookies');
34802      if (!cookieEnabled) {
34803        document.cookie = 'testcookie';
34804        cookieEnabled = document.cookie.indexOf('testcookie') !== -1;
34805      }
34806      if (!(cookieEnabled || App.config.userIsABot)) {
34807        this.showDisabledCookiesModal();
34808      }
34809      if (!(Cookies.get(gon.cookies_map.cookies_policy_closed) || noticeAlreadyExists)) {
34810        return this.setupUserInteractionListener();
34811      }
34812    };
34813
34814    CookiesPolicy.prototype.showNotificationBar = function() {
34815      this.$notificationBar = $(JST["cookies_policy/notification_bar"]());
34816      $('body').prepend(this.$notificationBar);
34817      this.$notificationBar.find('.js-cookies-policy-accept').on('click', this.acceptCookiesPolicy);
34818      return this.$notificationBar.find('.js-cookies-policy-close').on('click', this.closeCookiesPolicy);
34819    };
34820
34821    CookiesPolicy.prototype.acceptCookiesPolicy = function() {
34822      this.$notificationBar.hide();
34823      return Cookies.set(gon.cookies_map.cookies_policy_closed, true, {
34824        expires: 365,
34825        path: '/'
34826      });
34827    };
34828
34829    CookiesPolicy.prototype.closeCookiesPolicy = function() {
34830      this.$notificationBar.hide();
34831      return Cookies.set(gon.cookies_map.cookies_policy_closed, true, {
34832        path: '/'
34833      });
34834    };
34835
34836    CookiesPolicy.prototype.showDisabledCookiesModal = function() {
34837      this.$disabledCookiesModal = $(JST["cookies_policy/disabled_cookies_modal"]());
34838      $('body').prepend(this.$disabledCookiesModal);
34839      return this.$disabledCookiesModal.find('.js-close-modal').on('click', this.closeCookiesModal);
34840    };
34841
34842    CookiesPolicy.prototype.closeCookiesModal = function() {
34843      return this.$disabledCookiesModal.hide();
34844    };
34845
34846    CookiesPolicy.prototype.setupUserInteractionListener = function() {
34847      if (window.setupUserInteractionListener) {
34848        return window.setupUserInteractionListener((function(_this) {
34849          return function() {
34850            return _this.showNotificationBar();
34851          };
34852        })(this));
34853      } else {
34854        this.userHasInteracted = false;
34855        this.handleUserInteraction = (function(_this) {
34856          return function() {
34857            if (_this.userHasInteracted) {
34858              return;
34859            }
34860            _this.userHasInteracted = true;
34861            _this.removeInteractionListeners();
34862            return _this.showNotificationBar();
34863          };
34864        })(this);
34865        $(document).on('click.cookiesPolicy', this.handleUserInteraction);
34866        $(document).on('scroll.cookiesPolicy', this.handleUserInteraction);
34867        $(document).on('keydown.cookiesPolicy', this.handleUserInteraction);
34868        $(document).on('touchstart.cookiesPolicy', this.handleUserInteraction);
34869        return $(document).on('mousemove.cookiesPolicy', this.handleUserInteraction);
34870      }
34871    };
34872
34873    CookiesPolicy.prototype.removeInteractionListeners = function() {
34874      $(document).off('click.cookiesPolicy');
34875      $(document).off('scroll.cookiesPolicy');
34876      $(document).off('keydown.cookiesPolicy');
34877      $(document).off('touchstart.cookiesPolicy');
34878      return $(document).off('mousemove.cookiesPolicy');
34879    };
34880
34881    return CookiesPolicy;
34882
34883  })();
34884
34885}).call(this);
34886/**
34887 * @license countdown.js v2.3.3 http://countdownjs.org
34888 * Copyright (c)2006-2012 Stephen M. McKamey.
34889 * Licensed under The MIT License.
34890 */
34891/*jshint bitwise:false */
34892
34893/**
34894 * @public
34895 * @type {Object|null}
34896 */
34897
34898var module;
34899
34900/**
34901 * API entry
34902 * @public
34903 * @param {function(Object)|Date|number} start the starting date
34904 * @param {function(Object)|Date|number} end the ending date
34905 * @param {number} units the units to populate
34906 * @return {Object|number}
34907 */
34908var countdown = (
34909
34910    /**
34911     * @param {Object} module CommonJS Module
34912     */
34913        function(module) {
34914        /*jshint smarttabs:true */
34915
34916        'use strict';
34917
34918        /**
34919         * @private
34920         * @const
34921         * @type {number}
34922         */
34923        var MILLISECONDS	= 0x001;
34924
34925        /**
34926         * @private
34927         * @const
34928         * @type {number}
34929         */
34930        var SECONDS			= 0x002;
34931
34932        /**
34933         * @private
34934         * @const
34935         * @type {number}
34936         */
34937        var MINUTES			= 0x004;
34938
34939        /**
34940         * @private
34941         * @const
34942         * @type {number}
34943         */
34944        var HOURS			= 0x008;
34945
34946        /**
34947         * @private
34948         * @const
34949         * @type {number}
34950         */
34951        var DAYS			= 0x010;
34952
34953        /**
34954         * @private
34955         * @const
34956         * @type {number}
34957         */
34958        var WEEKS			= 0x020;
34959
34960        /**
34961         * @private
34962         * @const
34963         * @type {number}
34964         */
34965        var MONTHS			= 0x040;
34966
34967        /**
34968         * @private
34969         * @const
34970         * @type {number}
34971         */
34972        var YEARS			= 0x080;
34973
34974        /**
34975         * @private
34976         * @const
34977         * @type {number}
34978         */
34979        var DECADES			= 0x100;
34980
34981        /**
34982         * @private
34983         * @const
34984         * @type {number}
34985         */
34986        var CENTURIES		= 0x200;
34987
34988        /**
34989         * @private
34990         * @const
34991         * @type {number}
34992         */
34993        var MILLENNIA		= 0x400;
34994
34995        /**
34996         * @private
34997         * @const
34998         * @type {number}
34999         */
35000        var DEFAULTS		= YEARS|MONTHS|DAYS|HOURS|MINUTES|SECONDS;
35001
35002        /**
35003         * @private
35004         * @const
35005         * @type {number}
35006         */
35007        var MILLISECONDS_PER_SECOND = 1000;
35008
35009        /**
35010         * @private
35011         * @const
35012         * @type {number}
35013         */
35014        var SECONDS_PER_MINUTE = 60;
35015
35016        /**
35017         * @private
35018         * @const
35019         * @type {number}
35020         */
35021        var MINUTES_PER_HOUR = 60;
35022
35023        /**
35024         * @private
35025         * @const
35026         * @type {number}
35027         */
35028        var HOURS_PER_DAY = 24;
35029
35030        /**
35031         * @private
35032         * @const
35033         * @type {number}
35034         */
35035        var MILLISECONDS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
35036
35037        /**
35038         * @private
35039         * @const
35040         * @type {number}
35041         */
35042        var DAYS_PER_WEEK = 7;
35043
35044        /**
35045         * @private
35046         * @const
35047         * @type {number}
35048         */
35049        var MONTHS_PER_YEAR = 12;
35050
35051        /**
35052         * @private
35053         * @const
35054         * @type {number}
35055         */
35056        var YEARS_PER_DECADE = 10;
35057
35058        /**
35059         * @private
35060         * @const
35061         * @type {number}
35062         */
35063        var DECADES_PER_CENTURY = 10;
35064
35065        /**
35066         * @private
35067         * @const
35068         * @type {number}
35069         */
35070        var CENTURIES_PER_MILLENNIUM = 10;
35071
35072        /**
35073         * @private
35074         * @param {number} x number
35075         * @return {number}
35076         */
35077        var ceil = Math.ceil;
35078
35079        /**
35080         * @private
35081         * @param {number} x number
35082         * @return {number}
35083         */
35084        var floor = Math.floor;
35085
35086        /**
35087         * @private
35088         * @param {Date} ref reference date
35089         * @param {number} shift number of months to shift
35090         * @return {number} number of days shifted
35091         */
35092        function borrowMonths(ref, shift) {
35093            var prevTime = ref.getTime();
35094
35095            // increment month by shift
35096            ref.setUTCMonth( ref.getUTCMonth() + shift );
35097
35098            // this is the trickiest since months vary in length
35099            return Math.round( (ref.getTime() - prevTime) / MILLISECONDS_PER_DAY );
35100        }
35101
35102        /**
35103         * @private
35104         * @param {Date} ref reference date
35105         * @return {number} number of days
35106         */
35107        function daysPerMonth(ref) {
35108            var a = ref.getTime();
35109
35110            // increment month by 1
35111            var b = new Date(a);
35112            b.setUTCMonth( ref.getUTCMonth() + 1 );
35113
35114            // this is the trickiest since months vary in length
35115            return Math.round( (b.getTime() - a) / MILLISECONDS_PER_DAY );
35116        }
35117
35118        /**
35119         * @private
35120         * @param {Date} ref reference date
35121         * @return {number} number of days
35122         */
35123        function daysPerYear(ref) {
35124            var a = ref.getTime();
35125
35126            // increment year by 1
35127            var b = new Date(a);
35128            b.setUTCFullYear( ref.getUTCFullYear() + 1 );
35129
35130            // this is the trickiest since years (periodically) vary in length
35131            return Math.round( (b.getTime() - a) / MILLISECONDS_PER_DAY );
35132        }
35133
35134        /**
35135         * @private
35136         * @const
35137         * @type {number}
35138         */
35139        var LABEL_MILLISECONDS	= 0;
35140
35141        /**
35142         * @private
35143         * @const
35144         * @type {number}
35145         */
35146        var LABEL_SECONDS		= 1;
35147
35148        /**
35149         * @private
35150         * @const
35151         * @type {number}
35152         */
35153        var LABEL_MINUTES		= 2;
35154
35155        /**
35156         * @private
35157         * @const
35158         * @type {number}
35159         */
35160        var LABEL_HOURS			= 3;
35161
35162        /**
35163         * @private
35164         * @const
35165         * @type {number}
35166         */
35167        var LABEL_DAYS			= 4;
35168
35169        /**
35170         * @private
35171         * @const
35172         * @type {number}
35173         */
35174        var LABEL_WEEKS			= 5;
35175
35176        /**
35177         * @private
35178         * @const
35179         * @type {number}
35180         */
35181        var LABEL_MONTHS		= 6;
35182
35183        /**
35184         * @private
35185         * @const
35186         * @type {number}
35187         */
35188        var LABEL_YEARS			= 7;
35189
35190        /**
35191         * @private
35192         * @const
35193         * @type {number}
35194         */
35195        var LABEL_DECADES		= 8;
35196
35197        /**
35198         * @private
35199         * @const
35200         * @type {number}
35201         */
35202        var LABEL_CENTURIES		= 9;
35203
35204        /**
35205         * @private
35206         * @const
35207         * @type {number}
35208         */
35209        var LABEL_MILLENNIA		= 10;
35210
35211        /**
35212         * @private
35213         * @const
35214         * @type {Array}
35215         */
35216        var LABELS_SINGLUAR;
35217
35218        /**
35219         * @private
35220         * @const
35221         * @type {Array}
35222         */
35223        var LABELS_PLURAL;
35224
35225        /**
35226         * @private
35227         * @param {number} value
35228         * @param {number} unit unit index into label list
35229         * @return {string}
35230         */
35231        function plurality(value, unit) {
35232            return value+' '+((value === 1) ? LABELS_SINGLUAR[unit] : LABELS_PLURAL[unit]);
35233        }
35234
35235        /**
35236         * Formats the entries as English labels
35237         *
35238         * @private
35239         * @param {Timespan} ts
35240         * @return {Array}
35241         */
35242        var formatList;
35243
35244        /**
35245         * Timespan representation of a duration of time
35246         *
35247         * @private
35248         * @this {Timespan}
35249         * @constructor
35250         */
35251        function Timespan() {}
35252
35253        /**
35254         * Formats the Timespan as a sentance
35255         *
35256         * @private
35257         * @return {string}
35258         */
35259        Timespan.prototype.toString = function() {
35260            var label = formatList(this);
35261
35262            var count = label.length;
35263            if (!count) {
35264                return '';
35265            }
35266            if (count > 1) {
35267                label[count-1] = 'and '+label[count-1];
35268            }
35269            return label.join(', ');
35270        };
35271
35272        /**
35273         * Formats the Timespan as HTML
35274         *
35275         * @private
35276         * @param {string} tag HTML tag name to wrap each value
35277         * @return {string}
35278         */
35279        Timespan.prototype.toHTML = function(tag) {
35280            tag = tag || 'span';
35281            var label = formatList(this);
35282
35283            var count = label.length;
35284            if (!count) {
35285                return '';
35286            }
35287            for (var i=0; i<count; i++) {
35288                // wrap each unit in tag
35289                label[i] = '<'+tag+'>'+label[i]+'</'+tag+'>';
35290            }
35291            if (--count) {
35292                label[count] = 'and '+label[count];
35293            }
35294            return label.join(', ');
35295        };
35296
35297        /**
35298         * Formats the entries as English labels
35299         *
35300         * @private
35301         * @param {Timespan} ts
35302         * @return {Array}
35303         */
35304        formatList = function(ts) {
35305            var list = [];
35306
35307            var value = ts.millennia;
35308            if (value) {
35309                list.push(plurality(value, LABEL_MILLENNIA));
35310            }
35311
35312            value = ts.centuries;
35313            if (value) {
35314                list.push(plurality(value, LABEL_CENTURIES));
35315            }
35316
35317            value = ts.decades;
35318            if (value) {
35319                list.push(plurality(value, LABEL_DECADES));
35320            }
35321
35322            value = ts.years;
35323            if (value) {
35324                list.push(plurality(value, LABEL_YEARS));
35325            }
35326
35327            value = ts.months;
35328            if (value) {
35329                list.push(plurality(value, LABEL_MONTHS));
35330            }
35331
35332            value = ts.weeks;
35333            if (value) {
35334                list.push(plurality(value, LABEL_WEEKS));
35335            }
35336
35337            value = ts.days;
35338            if (value) {
35339                list.push(plurality(value, LABEL_DAYS));
35340            }
35341
35342            value = ts.hours;
35343            if (value) {
35344                list.push(plurality(value, LABEL_HOURS));
35345            }
35346
35347            value = ts.minutes;
35348            if (value) {
35349                list.push(plurality(value, LABEL_MINUTES));
35350            }
35351
35352            value = ts.seconds;
35353            if (value) {
35354                list.push(plurality(value, LABEL_SECONDS));
35355            }
35356
35357            value = ts.milliseconds;
35358            if (value) {
35359                list.push(plurality(value, LABEL_MILLISECONDS));
35360            }
35361
35362            return list;
35363        };
35364
35365        /**
35366         * Borrow any underflow units, carry any overflow units
35367         *
35368         * @private
35369         * @param {Timespan} ts
35370         * @param {string} toUnit
35371         */
35372        function rippleRounded(ts, toUnit) {
35373            switch (toUnit) {
35374                case 'seconds':
35375                    if (ts.seconds !== SECONDS_PER_MINUTE || isNaN(ts.minutes)) {
35376                        return;
35377                    }
35378                    // ripple seconds up to minutes
35379                    ts.minutes++;
35380                    ts.seconds = 0;
35381
35382                /* falls through */
35383                case 'minutes':
35384                    if (ts.minutes !== MINUTES_PER_HOUR || isNaN(ts.hours)) {
35385                        return;
35386                    }
35387                    // ripple minutes up to hours
35388                    ts.hours++;
35389                    ts.minutes = 0;
35390
35391                /* falls through */
35392                case 'hours':
35393                    if (ts.hours !== HOURS_PER_DAY || isNaN(ts.days)) {
35394                        return;
35395                    }
35396                    // ripple hours up to days
35397                    ts.days++;
35398                    ts.hours = 0;
35399
35400                /* falls through */
35401                case 'days':
35402                    if (ts.days !== DAYS_PER_WEEK || isNaN(ts.weeks)) {
35403                        return;
35404                    }
35405                    // ripple days up to weeks
35406                    ts.weeks++;
35407                    ts.days = 0;
35408
35409                /* falls through */
35410                case 'weeks':
35411                    if (ts.weeks !== daysPerMonth(ts.refMonth)/DAYS_PER_WEEK || isNaN(ts.months)) {
35412                        return;
35413                    }
35414                    // ripple weeks up to months
35415                    ts.months++;
35416                    ts.weeks = 0;
35417
35418                /* falls through */
35419                case 'months':
35420                    if (ts.months !== MONTHS_PER_YEAR || isNaN(ts.years)) {
35421                        return;
35422                    }
35423                    // ripple months up to years
35424                    ts.years++;
35425                    ts.months = 0;
35426
35427                /* falls through */
35428                case 'years':
35429                    if (ts.years !== YEARS_PER_DECADE || isNaN(ts.decades)) {
35430                        return;
35431                    }
35432                    // ripple years up to decades
35433                    ts.decades++;
35434                    ts.years = 0;
35435
35436                /* falls through */
35437                case 'decades':
35438                    if (ts.decades !== DECADES_PER_CENTURY || isNaN(ts.centuries)) {
35439                        return;
35440                    }
35441                    // ripple decades up to centuries
35442                    ts.centuries++;
35443                    ts.decades = 0;
35444
35445                /* falls through */
35446                case 'centuries':
35447                    if (ts.centuries !== CENTURIES_PER_MILLENNIUM || isNaN(ts.millennia)) {
35448                        return;
35449                    }
35450                    // ripple centuries up to millennia
35451                    ts.millennia++;
35452                    ts.centuries = 0;
35453                /* falls through */
35454            }
35455        }
35456
35457        /**
35458         * Ripple up partial units one place
35459         *
35460         * @private
35461         * @param {Timespan} ts timespan
35462         * @param {number} frac accumulated fractional value
35463         * @param {string} fromUnit source unit name
35464         * @param {string} toUnit target unit name
35465         * @param {number} conversion multiplier between units
35466         * @param {number} digits max number of decimal digits to output
35467         * @return {number} new fractional value
35468         */
35469        function fraction(ts, frac, fromUnit, toUnit, conversion, digits) {
35470            if (ts[fromUnit] >= 0) {
35471                frac += ts[fromUnit];
35472                delete ts[fromUnit];
35473            }
35474
35475            frac /= conversion;
35476            if (frac + 1 <= 1) {
35477                // drop if below machine epsilon
35478                return 0;
35479            }
35480
35481            if (ts[toUnit] >= 0) {
35482                // ensure does not have more than specified number of digits
35483                ts[toUnit] = +(ts[toUnit] + frac).toFixed(digits);
35484                rippleRounded(ts, toUnit);
35485                return 0;
35486            }
35487
35488            return frac;
35489        }
35490
35491        /**
35492         * Ripple up partial units to next existing
35493         *
35494         * @private
35495         * @param {Timespan} ts
35496         * @param {number} digits max number of decimal digits to output
35497         */
35498        function fractional(ts, digits) {
35499            var frac = fraction(ts, 0, 'milliseconds', 'seconds', MILLISECONDS_PER_SECOND, digits);
35500            if (!frac) { return; }
35501
35502            frac = fraction(ts, frac, 'seconds', 'minutes', SECONDS_PER_MINUTE, digits);
35503            if (!frac) { return; }
35504
35505            frac = fraction(ts, frac, 'minutes', 'hours', MINUTES_PER_HOUR, digits);
35506            if (!frac) { return; }
35507
35508            frac = fraction(ts, frac, 'hours', 'days', HOURS_PER_DAY, digits);
35509            if (!frac) { return; }
35510
35511            frac = fraction(ts, frac, 'days', 'weeks', DAYS_PER_WEEK, digits);
35512            if (!frac) { return; }
35513
35514            frac = fraction(ts, frac, 'weeks', 'months', daysPerMonth(ts.refMonth)/DAYS_PER_WEEK, digits);
35515            if (!frac) { return; }
35516
35517            frac = fraction(ts, frac, 'months', 'years', daysPerYear(ts.refMonth)/daysPerMonth(ts.refMonth), digits);
35518            if (!frac) { return; }
35519
35520            frac = fraction(ts, frac, 'years', 'decades', YEARS_PER_DECADE, digits);
35521            if (!frac) { return; }
35522
35523            frac = fraction(ts, frac, 'decades', 'centuries', DECADES_PER_CENTURY, digits);
35524            if (!frac) { return; }
35525
35526            frac = fraction(ts, frac, 'centuries', 'millennia', CENTURIES_PER_MILLENNIUM, digits);
35527
35528            // should never reach this with remaining fractional value
35529            if (frac) { throw new Error('Fractional unit overflow'); }
35530        }
35531
35532        /**
35533         * Borrow any underflow units, carry any overflow units
35534         *
35535         * @private
35536         * @param {Timespan} ts
35537         */
35538        function ripple(ts) {
35539            var x;
35540
35541            if (ts.milliseconds < 0) {
35542                // ripple seconds down to milliseconds
35543                x = ceil(-ts.milliseconds / MILLISECONDS_PER_SECOND);
35544                ts.seconds -= x;
35545                ts.milliseconds += x * MILLISECONDS_PER_SECOND;
35546
35547            } else if (ts.milliseconds >= MILLISECONDS_PER_SECOND) {
35548                // ripple milliseconds up to seconds
35549                ts.seconds += floor(ts.milliseconds / MILLISECONDS_PER_SECOND);
35550                ts.milliseconds %= MILLISECONDS_PER_SECOND;
35551            }
35552
35553            if (ts.seconds < 0) {
35554                // ripple minutes down to seconds
35555                x = ceil(-ts.seconds / SECONDS_PER_MINUTE);
35556                ts.minutes -= x;
35557                ts.seconds += x * SECONDS_PER_MINUTE;
35558
35559            } else if (ts.seconds >= SECONDS_PER_MINUTE) {
35560                // ripple seconds up to minutes
35561                ts.minutes += floor(ts.seconds / SECONDS_PER_MINUTE);
35562                ts.seconds %= SECONDS_PER_MINUTE;
35563            }
35564
35565            if (ts.minutes < 0) {
35566                // ripple hours down to minutes
35567                x = ceil(-ts.minutes / MINUTES_PER_HOUR);
35568                ts.hours -= x;
35569                ts.minutes += x * MINUTES_PER_HOUR;
35570
35571            } else if (ts.minutes >= MINUTES_PER_HOUR) {
35572                // ripple minutes up to hours
35573                ts.hours += floor(ts.minutes / MINUTES_PER_HOUR);
35574                ts.minutes %= MINUTES_PER_HOUR;
35575            }
35576
35577            if (ts.hours < 0) {
35578                // ripple days down to hours
35579                x = ceil(-ts.hours / HOURS_PER_DAY);
35580                ts.days -= x;
35581                ts.hours += x * HOURS_PER_DAY;
35582
35583            } else if (ts.hours >= HOURS_PER_DAY) {
35584                // ripple hours up to days
35585                ts.days += floor(ts.hours / HOURS_PER_DAY);
35586                ts.hours %= HOURS_PER_DAY;
35587            }
35588
35589            while (ts.days < 0) {
35590                // NOTE: never actually seen this loop more than once
35591
35592                // ripple months down to days
35593                ts.months--;
35594                ts.days += borrowMonths(ts.refMonth, 1);
35595            }
35596
35597            // weeks is always zero here
35598
35599            if (ts.days >= DAYS_PER_WEEK) {
35600                // ripple days up to weeks
35601                ts.weeks += floor(ts.days / DAYS_PER_WEEK);
35602                ts.days %= DAYS_PER_WEEK;
35603            }
35604
35605            if (ts.months < 0) {
35606                // ripple years down to months
35607                x = ceil(-ts.months / MONTHS_PER_YEAR);
35608                ts.years -= x;
35609                ts.months += x * MONTHS_PER_YEAR;
35610
35611            } else if (ts.months >= MONTHS_PER_YEAR) {
35612                // ripple months up to years
35613                ts.years += floor(ts.months / MONTHS_PER_YEAR);
35614                ts.months %= MONTHS_PER_YEAR;
35615            }
35616
35617            // years is always non-negative here
35618            // decades, centuries and millennia are always zero here
35619
35620            if (ts.years >= YEARS_PER_DECADE) {
35621                // ripple years up to decades
35622                ts.decades += floor(ts.years / YEARS_PER_DECADE);
35623                ts.years %= YEARS_PER_DECADE;
35624
35625                if (ts.decades >= DECADES_PER_CENTURY) {
35626                    // ripple decades up to centuries
35627                    ts.centuries += floor(ts.decades
35627 / DECADES_PER_CENTURY);
35628                    ts.decades %= DECADES_PER_CENTURY;
35629
35630                    if (ts.centuries >= CENTURIES_PER_MILLENNIUM) {
35631                        // ripple centuries up to millennia
35632                        ts.millennia += floor(ts.centuries / CENTURIES_PER_MILLENNIUM);
35633                        ts.centuries %= CENTURIES_PER_MILLENNIUM;
35634                    }
35635                }
35636            }
35637        }
35638
35639        /**
35640         * Remove any units not requested
35641         *
35642         * @private
35643         * @param {Timespan} ts
35644         * @param {number} units the units to populate
35645         * @param {number} max number of labels to output
35646         * @param {number} digits max number of decimal digits to output
35647         */
35648        function pruneUnits(ts, units, max, digits) {
35649            var count = 0;
35650
35651            // Calc from largest unit to smallest to prevent underflow
35652            if (!(units & MILLENNIA) || (count >= max)) {
35653                // ripple millennia down to centuries
35654                ts.centuries += ts.millennia * CENTURIES_PER_MILLENNIUM;
35655                delete ts.millennia;
35656
35657            } else if (ts.millennia) {
35658                count++;
35659            }
35660
35661            if (!(units & CENTURIES) || (count >= max)) {
35662                // ripple centuries down to decades
35663                ts.decades += ts.centuries * DECADES_PER_CENTURY;
35664                delete ts.centuries;
35665
35666            } else if (ts.centuries) {
35667                count++;
35668            }
35669
35670            if (!(units & DECADES) || (count >= max)) {
35671                // ripple decades down to years
35672                ts.years += ts.decades * YEARS_PER_DECADE;
35673                delete ts.decades;
35674
35675            } else if (ts.decades) {
35676                count++;
35677            }
35678
35679            if (!(units & YEARS) || (count >= max)) {
35680                // ripple years down to months
35681                ts.months += ts.years * MONTHS_PER_YEAR;
35682                delete ts.years;
35683
35684            } else if (ts.years) {
35685                count++;
35686            }
35687
35688            if (!(units & MONTHS) || (count >= max)) {
35689                // ripple months down to days
35690                if (ts.months) {
35691                    ts.days += borrowMonths(ts.refMonth, ts.months);
35692                }
35693                delete ts.months;
35694
35695                if (ts.days >= DAYS_PER_WEEK) {
35696                    // ripple day overflow back up to weeks
35697                    ts.weeks += floor(ts.days / DAYS_PER_WEEK);
35698                    ts.days %= DAYS_PER_WEEK;
35699                }
35700
35701            } else if (ts.months) {
35702                count++;
35703            }
35704
35705            if (!(units & WEEKS) || (count >= max)) {
35706                // ripple weeks down to days
35707                ts.days += ts.weeks * DAYS_PER_WEEK;
35708                delete ts.weeks;
35709
35710            } else if (ts.weeks) {
35711                count++;
35712            }
35713
35714            if (!(units & DAYS) || (count >= max)) {
35715                //ripple days down to hours
35716                ts.hours += ts.days * HOURS_PER_DAY;
35717                delete ts.days;
35718
35719            } else if (ts.days) {
35720                count++;
35721            }
35722
35723            if (!(units & HOURS) || (count >= max)) {
35724                // ripple hours down to minutes
35725                ts.minutes += ts.hours * MINUTES_PER_HOUR;
35726                delete ts.hours;
35727
35728            } else if (ts.hours) {
35729                count++;
35730            }
35731
35732            if (!(units & MINUTES) || (count >= max)) {
35733                // ripple minutes down to seconds
35734                ts.seconds += ts.minutes * SECONDS_PER_MINUTE;
35735                delete ts.minutes;
35736
35737            } else if (ts.minutes) {
35738                count++;
35739            }
35740
35741            if (!(units & SECONDS) || (count >= max)) {
35742                // ripple seconds down to milliseconds
35743                ts.milliseconds += ts.seconds * MILLISECONDS_PER_SECOND;
35744                delete ts.seconds;
35745
35746            } else if (ts.seconds) {
35747                count++;
35748            }
35749
35750            // nothing to ripple milliseconds down to
35751            // so ripple back up to smallest existing unit as a fractional value
35752            if (!(units & MILLISECONDS) || (count >= max)) {
35753                fractional(ts, digits);
35754            }
35755        }
35756
35757        /**
35758         * Populates the Timespan object
35759         *
35760         * @private
35761         * @param {Timespan} ts
35762         * @param {Date} start the starting date
35763         * @param {Date} end the ending date
35764         * @param {number} units the units to populate
35765         * @param {number} max number of labels to output
35766         * @param {number} digits max number of decimal digits to output
35767         */
35768        function populate(ts, start, end, units, max, digits) {
35769            ts.start = start;
35770            ts.end = end;
35771            ts.units = units;
35772
35773            ts.value = end.getTime() - start.getTime();
35774            if (ts.value < 0) {
35775                // swap if reversed
35776                var temp = end;
35777                end = start;
35778                start = temp;
35779            }
35780
35781            // reference month for determining days in month
35782            ts.refMonth = new Date(start.getFullYear(), start.getMonth(), 15);
35783            try {
35784                // reset to initial deltas
35785                ts.millennia = 0;
35786                ts.centuries = 0;
35787                ts.decades = 0;
35788                ts.years = end.getUTCFullYear() - start.getUTCFullYear();
35789                ts.months = end.getUTCMonth() - start.getUTCMonth();
35790                ts.weeks = 0;
35791                ts.days = end.getUTCDate() - start.getUTCDate();
35792                ts.hours = end.getUTCHours() - start.getUTCHours();
35793                ts.minutes = end.getUTCMinutes() - start.getUTCMinutes();
35794                ts.seconds = end.getUTCSeconds() - start.getUTCSeconds();
35795                ts.milliseconds = end.getUTCMilliseconds() - start.getUTCMilliseconds();
35796
35797                ripple(ts);
35798                pruneUnits(ts, units, max, digits);
35799
35800            } finally {
35801                delete ts.refMonth;
35802            }
35803
35804            return ts;
35805        }
35806
35807        /**
35808         * Determine an appropriate refresh rate based upon units
35809         *
35810         * @private
35811         * @param {number} units the units to populate
35812         * @return {number} milliseconds to delay
35813         */
35814        function getDelay(units) {
35815            if (units & MILLISECONDS) {
35816                // refresh very quickly
35817                return MILLISECONDS_PER_SECOND / 30; //30Hz
35818            }
35819
35820            if (units & SECONDS) {
35821                // refresh every second
35822                return MILLISECONDS_PER_SECOND; //1Hz
35823            }
35824
35825            if (units & MINUTES) {
35826                // refresh every minute
35827                return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE;
35828            }
35829
35830            if (units & HOURS) {
35831                // refresh hourly
35832                return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
35833            }
35834
35835            if (units & DAYS) {
35836                // refresh daily
35837                return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY;
35838            }
35839
35840            // refresh the rest weekly
35841            return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * DAYS_PER_WEEK;
35842        }
35843
35844        /**
35845         * API entry point
35846         *
35847         * @public
35848         * @param {Date|number|null|function(Timespan,number)} start the starting date
35849         * @param {Date|number|null|function(Timespan,number)} end the ending date
35850         * @param {number} units the units to populate
35851         * @param {number} max number of labels to output
35852         * @param {number} digits max number of decimal digits to output
35853         * @return {Timespan|number}
35854         */
35855        function countdown(start, end, units, max, digits) {
35856            var callback;
35857
35858            // ensure some units or use defaults
35859            units = +units || DEFAULTS;
35860            // max must be positive
35861            max = (max > 0) ? max : NaN;
35862            // clamp digits to an integer between [0, 20]
35863            digits = (digits > 0) ? (digits < 20) ? Math.round(digits) : 20 : 0;
35864
35865            // ensure start date
35866            if ('function' === typeof start) {
35867                callback = start;
35868                start = null;
35869
35870            } else if (!(start instanceof Date)) {
35871                start = (start !== null && isFinite(start)) ? new Date(start) : null;
35872            }
35873
35874            // ensure end date
35875            if ('function' === typeof end) {
35876                callback = end;
35877                end = null;
35878
35879            } else if (!(end instanceof Date)) {
35880                end = (end !== null && isFinite(end)) ? new Date(end) : null;
35881            }
35882
35883            if (!start && !end) {
35884                // used for unit testing
35885                return new Timespan();
35886            }
35887
35888            if (!callback) {
35889                return populate(new Timespan(), /** @type{Date} */(start||new Date()), /** @type{Date} */(end||new Date()), units, max, digits);
35890            }
35891
35892            // base delay off units
35893            var delay = getDelay(units),
35894                timerId,
35895                fn = function() {
35896                    callback(
35897                        populate(new Timespan(), /** @type{Date} */(start||new Date()), /** @type{Date} */(end||new Date()), units, max, digits),
35898                        timerId
35899                    );
35900                };
35901
35902            fn();
35903            return (timerId = setInterval(fn, delay));
35904        }
35905
35906        /**
35907         * @public
35908         * @const
35909         * @type {number}
35910         */
35911        countdown.MILLISECONDS = MILLISECONDS;
35912
35913        /**
35914         * @public
35915         * @const
35916         * @type {number}
35917         */
35918        countdown.SECONDS = SECONDS;
35919
35920        /**
35921         * @public
35922         * @const
35923         * @type {number}
35924         */
35925        countdown.MINUTES = MINUTES;
35926
35927        /**
35928         * @public
35929         * @const
35930         * @type {number}
35931         */
35932        countdown.HOURS = HOURS;
35933
35934        /**
35935         * @public
35936         * @const
35937         * @type {number}
35938         */
35939        countdown.DAYS = DAYS;
35940
35941        /**
35942         * @public
35943         * @const
35944         * @type {number}
35945         */
35946        countdown.WEEKS = WEEKS;
35947
35948        /**
35949         * @public
35950         * @const
35951         * @type {number}
35952         */
35953        countdown.MONTHS = MONTHS;
35954
35955        /**
35956         * @public
35957         * @const
35958         * @type {number}
35959         */
35960        countdown.YEARS = YEARS;
35961
35962        /**
35963         * @public
35964         * @const
35965         * @type {number}
35966         */
35967        countdown.DECADES = DECADES;
35968
35969        /**
35970         * @public
35971         * @const
35972         * @type {number}
35973         */
35974        countdown.CENTURIES = CENTURIES;
35975
35976        /**
35977         * @public
35978         * @const
35979         * @type {number}
35980         */
35981        countdown.MILLENNIA = MILLENNIA;
35982
35983        /**
35984         * @public
35985         * @const
35986         * @type {number}
35987         */
35988        countdown.DEFAULTS = DEFAULTS;
35989
35990        /**
35991         * @public
35992         * @const
35993         * @type {number}
35994         */
35995        countdown.ALL = MILLENNIA|CENTURIES|DECADES|YEARS|MONTHS|WEEKS|DAYS|HOURS|MINUTES|SECONDS|MILLISECONDS;
35996
35997        /**
35998         * Override the unit labels
35999         * @public
36000         * @param {string|Array} singular a pipe ('|') delimited list of singular unit name overrides
36001         * @param {string|Array} plural a pipe ('|') delimited list of plural unit name overrides
36002         */
36003        var setLabels = countdown.setLabels = function(singular, plural) {
36004            singular = singular || [];
36005            if (singular.split) {
36006                singular = singular.split('|');
36007            }
36008            plural = plural || [];
36009            if (plural.split) {
36010                plural = plural.split('|');
36011            }
36012
36013            for (var i=LABEL_MILLISECONDS; i<=LABEL_MILLENNIA; i++) {
36014                // override any specified units
36015                LABELS_SINGLUAR[i] = singular[i] || LABELS_SINGLUAR[i];
36016                LABELS_PLURAL[i] = plural[i] || LABELS_PLURAL[i];
36017            }
36018        };
36019
36020        /**
36021         * Revert to the default unit labels
36022         * @public
36023         */
36024        var resetLabels = countdown.resetLabels = function() {
36025            LABELS_SINGLUAR = 'millisecond|second|minute|hour|day|week|month|year|decade|century|millennium'.split('|');
36026            LABELS_PLURAL = 'milliseconds|seconds|minutes|hours|days|weeks|months|years|decades|centuries|millennia'.split('|');
36027        };
36028
36029        resetLabels();
36030
36031        if (module && module.exports) {
36032            module.exports = countdown;
36033        }
36034
36035        return countdown;
36036
36037    })(module);
36038(function() {
36039  App.Widgets.Countdown = (function() {
36040    function Countdown() {}
36041
36042    Countdown.prototype.initialize = function() {
36043      this.$el = $('.js-countdown');
36044      return this.$el.each(function(index, el) {
36045        var time, utc;
36046        utc = $(el).data('timestamp');
36047        time = parseDate(utc);
36048        return countdown(time, (function(_this) {
36049          return function(ts) {
36050            $(el).find('.countdown__months').text(ts.months + "M");
36051            $(el).find('.countdown__days').text(ts.days + "d");
36052            $(el).find('.countdown__hours').text(ts.hours + "h");
36053            $(el).find('.countdown__minutes').text(ts.minutes + "m");
36054            return $(el).find('.countdown__seconds').text(ts.seconds + "s");
36055          };
36056        })(this));
36057      });
36058    };
36059
36060    return Countdown;
36061
36062  })();
36063
36064}).call(this);
36065(function() {
36066  App.Widgets.CreditsUsedInfoTooltip = (function() {
36067    function CreditsUsedInfoTooltip() {}
36068
36069    CreditsUsedInfoTooltip.prototype.initialize = function() {
36070      this.numberFormat = '$0.00';
36071      this.$tooltip = $('.js-credits-used-info-tooltip');
36072      if (this.$tooltip.length > 0) {
36073        return this.initTooltip();
36074      }
36075    };
36076
36077    CreditsUsedInfoTooltip.prototype.initTooltip = function() {
36078      var bonusCartCreditsUsed, bonusCreditsTitle, cartValue, content, storeCreditsUsed, temporaryCreditsActive, temporaryCreditsUsed, totalCreditsUsed;
36079      storeCreditsUsed = this.$tooltip.data('store-credits-used');
36080      bonusCartCreditsUsed = this.$tooltip.data('bonus-credits-used');
36081      cartValue = this.$tooltip.data('cart-value');
36082      temporaryCreditsActive = this.$tooltip.data('temporary-credits-active');
36083      temporaryCreditsUsed = this.$tooltip.data('temporary-credits-used');
36084      bonusCreditsTitle = this.$tooltip.data('bonus-credits-title');
36085      totalCreditsUsed = this.$tooltip.data('total-credits-used');
36086      if (totalCreditsUsed > 0) {
36087        content = I18n.t('tooltips.common_credits_used_tooltip_html', {
36088          storeCreditsUsedText: this.storeCreditsUsedText(storeCreditsUsed),
36089          temporaryCreditsUsedText: this.temporaryCreditsUsedText(temporaryCreditsActive, bonusCreditsTitle, temporaryCreditsUsed),
36090          totalCreditsUsed: numeral(totalCreditsUsed).format(this.numberFormat)
36091        });
36092      } else {
36093        content = I18n.t('tooltips.no_credits_used_html');
36094      }
36095      return this.$tooltip.tooltipster({
36096        theme: 'tooltip--light tooltip--popover tooltip--spaced',
36097        animation: 'grow',
36098        contentAsHTML: true,
36099        content: content,
36100        interactive: true,
36101        maxWidth: 350
36102      });
36103    };
36104
36105    CreditsUsedInfoTooltip.prototype.storeCreditsUsedText = function(storeCreditsUsed) {
36106      if (storeCreditsUsed > 0) {
36107        return I18n.t('tooltips.store_credits_used_html', {
36108          storeCreditsUsed: numeral(storeCreditsUsed).format(this.numberFormat)
36109        });
36110      } else {
36111        return '';
36112      }
36113    };
36114
36115    CreditsUsedInfoTooltip.prototype.temporaryCreditsUsedText = function(temporaryCreditsActive, bonusCreditsTitle, temporaryCreditsUsed) {
36116      if (temporaryCreditsActive) {
36117        return I18n.t('tooltips.temporary_credits_used_html', {
36118          bonusCreditsTitle: bonusCreditsTitle,
36119          temporaryCreditsUsed: numeral(temporaryCreditsUsed).format(this.numberFormat)
36120        });
36121      } else {
36122        return '';
36123      }
36124    };
36125
36126    return CreditsUsedInfoTooltip;
36127
36128  })();
36129
36130}).call(this);
36131(function() {
36132  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
36133
36134  App.Widgets.CustomItemRequest = (function() {
36135    function CustomItemRequest() {
36136      this.onPriceRangeUpdate = bind(this.onPriceRangeUpdate, this);
36137      this.onPriceRangeChanged = bind(this.onPriceRangeChanged, this);
36138      this.initPriceRangeFilter = bind(this.initPriceRangeFilter, this);
36139      this.prefillCheckboxesInformation = bind(this.prefillCheckboxesInformation, this);
36140      this.showOrHideNotRelatedTo3dPrintInformation = bind(this.showOrHideNotRelatedTo3dPrintInformation, this);
36141      this.resetSelectedPrice = bind(this.resetSelectedPrice, this);
36142      this.onFormSubmitComplete = bind(this.onFormSubmitComplete, this);
36143      this.initValidation = bind(this.initValidation, this);
36144      this.onModalClosedEvent = bind(this.onModalClosedEvent, this);
36145      this.hideOnClickOutside = bind(this.hideOnClickOutside, this);
36146      this.bindCustomEvents = bind(this.bindCustomEvents, this);
36147      this.parseSearchForm = bind(this.parseSearchForm, this);
36148      this.initRequestModal = bind(this.initRequestModal, this);
36149    }
36150
36151    CustomItemRequest.prototype.maxPrice = 500;
36152
36153    CustomItemRequest.prototype.filterCheckboxes = ['print_ready', 'low_poly', 'animated', 'pbr', 'rigged', 'collection'];
36154
36155    CustomItemRequest.prototype.initialize = function() {
36156      return this.bindEvents();
36157    };
36158
36159    CustomItemRequest.prototype.bindEvents = function() {
36160      return $(document).on('click', '.js-request-custom-item', this.initRequestModal);
36161    };
36162
36163    CustomItemRequest.prototype.initRequestModal = function() {
36164      this.$modal = $(JST["modals/custom_item_request_modal"](this.parseSearchForm()));
36165      $('body').append(this.$modal);
36166      this.$form = this.$modal.find('.js-request-form');
36167      this.$checkboxesWithout3dPrint = this.$modal.find('input[type=checkbox]:not(#print_ready_request)');
36168      this.$checkboxesContainersWithout3dPrint = this.$checkboxesWithout3dPrint.closest('.checkbox-block');
36169      this.$polygonsRow = this.$modal.find('.js-polygons-row');
36170      this.prefillCheckboxesInformation();
36171      this.initPriceRangeFilter($('#price-range-request'));
36172      this.$modal.makeMeModal();
36173      this.bindCustomEvents();
36174      return this.initValidation();
36175    };
36176
36177    CustomItemRequest.prototype.parseSearchForm = function() {
36178      var authenticityToken, data, formats, licenses, polygons, price, searchQuery, selectedFormatsArr, selectedLicensesArr, selectedPolygons, selectedPriceRange;
36179      selectedPriceRange = $.parseJSON($('input[name=price]')[0].value) || {
36180        from: 0,
36181        to: this.maxPrice
36182      };
36183      price = '$' + selectedPriceRange.from + '-' + selectedPriceRange.to;
36184      selectedFormatsArr = $('.active-filters__item[data-filter="file_types"]').toArray();
36185      formats = selectedFormatsArr.map(function(el) {
36186        return $.trim(el.textContent);
36187      });
36188      selectedPolygons = $('.active-filters__item[data-filter="polygons"]')[0] || [];
36189      polygons = $.trim(selectedPolygons.textContent);
36190      selectedLicensesArr = $('.active-filters__item[data-filter="licenses"]').toArray();
36191      licenses = selectedLicensesArr.map(function(el) {
36192        return $.trim(el.textContent);
36193      });
36194      searchQuery = $('input[name=keywords]')[0].value;
36195      authenticityToken = $('meta[name="csrf-token"]').attr('content');
36196      return data = {
36197        signedIn: App.config.userSignedIn,
36198        price: price,
36199        formats: formats,
36200        polygons: polygons,
36201        licenses: licenses,
36202        keywords: searchQuery,
36203        authenticityToken: authenticityToken
36204      };
36205    };
36206
36207    CustomItemRequest.prototype.bindCustomEvents = function() {
36208      this.$modal.on('click', '.js-modal-close', this.onModalClosedEvent);
36209      this.$modal.on('click', '.js-reset-price', this.resetSelectedPrice);
36210      this.$modal.on('click', 'input#print_ready_request', this.showOrHideNotRelatedTo3dPrintInformation);
36211      this.$form.on('ajax:complete', this.onFormSubmitComplete);
36212      return this.hideOnClickOutside('#modal-request-custom-item', 'modal-backdrop');
36213    };
36214
36215    CustomItemRequest.prototype.hideOnClickOutside = function(selector, backdrop) {
36216      return $(document).click((function(_this) {
36217        return function(event) {
36218          if ($(selector).is(':visible') && $(event.target).hasClass(backdrop)) {
36219            return _this.onModalClosedEvent();
36220          }
36221        };
36222      })(this));
36223    };
36224
36225    CustomItemRequest.prototype.onModalClosedEvent = function() {
36226      this.$modal.makeMeModal('close');
36227      return this.$modal.remove();
36228    };
36229
36230    CustomItemRequest.prototype.validationSettings = function() {
36231      return {
36232        rules: {
36233          'custom_model_request[deadline]': {
36234            required: true
36235          },
36236          'custom_model_request[email]': {
36237            required: true,
36238            email: true
36239          },
36240          'custom_model_request[details]': {
36241            maxlength: 255
36242          }
36243        }
36244      };
36245    };
36246
36247    CustomItemRequest.prototype.initValidation = function() {
36248      return this.$form.validate(this.validationSettings());
36249    };
36250
36251    CustomItemRequest.prototype.onFormSubmitComplete = function() {
36252      this.$modal.find('.modal-light__body').addClass('is-invisible');
36253      return this.$modal.find('.js-success-screen').removeClass('is-hidden');
36254    };
36255
36256    CustomItemRequest.prototype.resetSelectedPrice = function() {
36257      return $('#price-range-request')[0].noUiSlider.set([0, this.maxPrice]);
36258    };
36259
36260    CustomItemRequest.prototype.showOrHideNotRelatedTo3dPrintInformation = function() {
36261      if ($('input#print_ready_request').is(':checked')) {
36262        this.$checkboxesContainersWithout3dPrint.remove();
36263        return this.$polygonsRow.remove();
36264      } else {
36265        this.$modal.find('.js-checkboxes-container').append(this.$checkboxesContainersWithout3dPrint);
36266        return this.$modal.find('.js-polygons-container').append(this.$polygonsRow);
36267      }
36268    };
36269
36270    CustomItemRequest.prototype.prefillCheckboxesInformation = function() {
36271      var checkboxName, i, len, ref;
36272      ref = this.filterCheckboxes;
36273      for (i = 0, len = ref.length; i < len; i++) {
36274        checkboxName = ref[i];
36275        if ($("input[name=" + checkboxName + "]").is(':checked')) {
36276          $("input#" + checkboxName + "_request").prop('checked', true);
36277        }
36278      }
36279      return this.showOrHideNotRelatedTo3dPrintInformation();
36280    };
36281
36282    CustomItemRequest.prototype.initPriceRangeFilter = function($priceRequest) {
36283      var ref, startRange;
36284      $priceRequest.tooltipster({
36285        theme: 'tooltip--light',
36286        animation: 'grow',
36287        delay: 0,
36288        autoClose: true,
36289        trigger: 'custom',
36290        updateAnimation: false,
36291        timer: 500
36292      });
36293      startRange = (ref = JSON.parse($('#price').val())) != null ? ref : {
36294        from: 0,
36295        to: this.maxPrice
36296      };
36297      noUiSlider.create($priceRequest[0], {
36298        start: [startRange.from, startRange.to],
36299        connect: true,
36300        step: 1,
36301        range: {
36302          'min': 0,
36303          'max': this.maxPrice
36304        }
36305      });
36306      $priceRequest[0].noUiSlider.on('change', this.onPriceRangeChanged);
36307      $priceRequest[0].noUiSlider.on('set', this.onPriceRangeChanged);
36308      return $priceRequest[0].noUiSlider.on('update', (function(_this) {
36309        return function(values, handle) {
36310          return _this.onPriceRangeUpdate(values, handle, $priceRequest);
36311        };
36312      })(this));
36313    };
36314
36315    CustomItemRequest.prototype.onPriceRangeChanged = function(values) {
36316      var from, to, updatedPrice;
36317      from = parseInt(values[0]);
36318      to = parseInt(values[1]);
36319      updatedPrice = '$' + from + '-' + to;
36320      this.$modal.find('.js-price-input').val(updatedPrice);
36321      return this.$modal.find('.js-price-filter').text(updatedPrice);
36322    };
36323
36324    CustomItemRequest.prototype.onPriceRangeUpdate = function(values, handle, $priceRequest) {
36325      var value;
36326      value = values[handle];
36327      if ($priceRequest.priceRangeChangeCounter == null) {
36328        $priceRequest.priceRangeChangeCounter = 0;
36329      }
36330      $priceRequest.priceRangeChangeCounter += 1;
36331      if ($priceRequest.priceRangeChangeCounter < 10) {
36332        return;
36333      }
36334      if (+value === this.maxPrice) {
36335        value = this.maxPrice + " or more";
36336      }
36337      return $priceRequest.tooltipster('content', "$" + value).tooltipster('show');
36338    };
36339
36340    return CustomItemRequest;
36341
36342  })();
36343
36344}).call(this);
36345(function() {
36346  App.Widgets.CVCTooltip = (function() {
36347    function CVCTooltip() {}
36348
36349    CVCTooltip.prototype.initialize = function() {
36350      this.$tooltip = $('.js-cvc-tooltip');
36351      if (this.$tooltip.length > 0) {
36352        return this.initTooltip();
36353      }
36354    };
36355
36356    CVCTooltip.prototype.initTooltip = function() {
36357      var content;
36358      content = "Last three digits on the back of your credit card";
36359      return this.$tooltip.tooltipster({
36360        theme: 'tooltip--light tooltip--popover u-text-center',
36361        animation: 'grow',
36362        contentAsHTML: true,
36363        content: content,
36364        position: 'top',
36365        interactive: true,
36366        maxWidth: 400
36367      });
36368    };
36369
36370    return CVCTooltip;
36371
36372  })();
36373
36374}).call(this);
36375/*rateYo V2.3.2, A simple and flexible star rating plugin
36376prashanth pamidi (https://github.com/prrashi)*/
36377
36378!function(a){"use strict";function b(){var a=!1;return function(b){(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(b)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(b.substr(0,4)))&&(a=!0)}(navigator.userAgent||navigator.vendor||window.opera),a}function c(a,b,c){return a===b?a=b:a===c&&(a=c),a}function d(a,b,c){if(!(a>=b&&a<=c))throw Error("Invalid Rating, expected value between "+b+" and "+c);return a}function e(a){return void 0!==a}function f(a,b,c){var d=c/100*(b-a);return d=Math.round(a+d).toString(16),1===d.length&&(d="0"+d),d}function g(a,b,c){if(!a||!b)return null;c=e(c)?c:0,a=q(a),b=q(b);var d=f(a.r,b.r,c),g=f(a.b,b.b,c);return"#"+d+f(a.g,b.g,c)+g}function h(f,i){function k(a){e(a)||(a=i.rating),Z=a;var b=a/P,c=b*R;b>1&&(c+=(Math.ceil(b)-1)*T),r(i.ratedFill),c=i.rtl?100-c:c,c<0?c=0:c>100&&(c=100),X.css("width",c+"%")}function l(){U=Q*i.numStars+S*(i.numStars-1),R=Q/U*100,T=S/U*100,f.width(U),k()}function n(a){var b=i.starWidth=a;return Q=window.parseFloat(i.starWidth.replace("px","")),W.find("svg").attr({width:i.starWidth,height:b}),X.find("svg").attr({width:i.starWidth,height:b}),l(),f}function p(a){return i.spacing=a,S=parseFloat(i.spacing.replace("px","")),W.find("svg:not(:first-child)").css({"margin-left":a}),X.find("svg:not(:first-child)").css({"margin-left":a}),l(),f}function q(a){return i.normalFill=a,(i.rtl?X:W).find("svg").attr({fill:i.normalFill}),f}function r(a){if(i.multiColor){var b=Z-Y,c=b/i.maxValue*100,d=i.multiColor||{};a=g(d.startColor||o.startColor,d.endColor||o.endColor,c)}else _=a;return i.ratedFill=a,(i.rtl?W:X).find("svg").attr({fill:i.ratedFill}),f}function s(a){a=!!a,i.rtl=a,q(i.normalFill),k()}function t(a){i.multiColor=a,r(a?a:_)}function u(b){i.numStars=b,P=i.maxValue/i.numStars,W.empty(),X.empty();for(var c=0;c<i.numStars;c++)W.append(a(i.starSvg||m)),X.append(a(i.starSvg||m));return n(i.starWidth),q(i.normalFill),p(i.spacing),k(),f}function v(a){return i.maxValue=a,P=i.maxValue/i.numStars,i.rating>a&&C(a),k(),f}function w(a){return i.precision=a,C(i.rating),f}function x(a){return i.halfStar=a,f}function y(a){return i.fullStar=a,f}function z(a){var b=a%P,c=P/2,d=i.halfStar,e=i.fullStar;return e||d?(e||d&&b>c?a+=P-b:(a-=b,b>0&&(a+=c)),a):a}function A(a){var b=W.offset(),c=b.left,d=c+W.width(),e=i.maxValue,f=a.pageX,g=0;if(f<c)g=Y;else if(f>d)g=e;else{var h=(f-c)/(d-c);if(S>0){h*=100;for(var j=h;j>0;)j>R?(g+=P,j-=R+T):(g+=j/R*P,j=0)}else g=h*i.maxValue;g=z(g)}return i.rtl&&(g=e-g),parseFloat(g)}function B(a){return i.readOnly=a,f.attr("readonly",!0),N(),a||(f.removeAttr("readonly"),M()),f}function C(a){var b=a,e=i.maxValue;return"string"==typeof b&&("%"===b[b.length-1]&&(b=b.substr(0,b.length-1),e=100,v(e)),b=parseFloat(b)),d(b,Y,e),b=parseFloat(b.toFixed(i.precision)),c(parseFloat(b),Y,e),i.rating=b,k(),$&&f.trigger("rateyo.set",{rating:b}),f}function D(a){return i.onInit=a,f}function E(a){return i.onSet=a,f}function F(a){return i.onChange=a,f}function G(a){var b=A(a).toFixed(i.precision),d=i.maxValue;b=c(parseFloat(b),Y,d),k(b),f.trigger("rateyo.change",{rating:b})}function H(){b()||(k(),f.trigger("rateyo.change",{rating:i.rating}))}function I(a){var b=A(a).toFixed(i.precision);b=parseFloat(b),O.rating(b)}function J(a,b){i.onInit&&"function"==typeof i.onInit&&i.onInit.apply(this,[b.rating,O])}function K(a,b){i.onChange&&"function"==typeof i.onChange&&i.onChange.apply(this,[b.rating,O])}function L(a,b){i.onSet&&"function"==typeof i.onSet&&i.onSet.apply(this,[b.rating,O])}function M(){f.on("mousemove",G).on("mouseenter",G).on("mouseleave",H).on("click",I).on("rateyo.init",J).on("rateyo.change",K).on("rateyo.set",L
36378)}function N(){f.off("mousemove",G).off("mouseenter",G).off("mouseleave",H).off("click",I).off("rateyo.init",J).off("rateyo.change",K).off("rateyo.set",L)}this.node=f.get(0);var O=this;f.empty().addClass("jq-ry-container");var P,Q,R,S,T,U,V=a("<div/>").addClass("jq-ry-group-wrapper").appendTo(f),W=a("<div/>").addClass("jq-ry-normal-group").addClass("jq-ry-group").appendTo(V),X=a("<div/>").addClass("jq-ry-rated-group").addClass("jq-ry-group").appendTo(V),Y=0,Z=i.rating,$=!1,_=i.ratedFill;this.rating=function(a){return e(a)?(C(a),f):i.rating},this.destroy=function(){return i.readOnly||N(),h.prototype.collection=j(f.get(0),this.collection),f.removeClass("jq-ry-container").children().remove(),f},this.method=function(a){if(!a)throw Error("Method name not specified!");if(!e(this[a]))throw Error("Method "+a+" doesn't exist!");var b=Array.prototype.slice.apply(arguments,[]),c=b.slice(1);return this[a].apply(this,c)},this.option=function(a,b){if(!e(a))return i;var c;switch(a){case"starWidth":c=n;break;case"numStars":c=u;break;case"normalFill":c=q;break;case"ratedFill":c=r;break;case"multiColor":c=t;break;case"maxValue":c=v;break;case"precision":c=w;break;case"rating":c=C;break;case"halfStar":c=x;break;case"fullStar":c=y;break;case"readOnly":c=B;break;case"spacing":c=p;break;case"rtl":c=s;break;case"onInit":c=D;break;case"onSet":c=E;break;case"onChange":c=F;break;default:throw Error("No such option as "+a)}return e(b)?c(b):i[a]},u(i.numStars),B(i.readOnly),i.rtl&&s(i.rtl),this.collection.push(this),this.rating(i.rating,!0),$=!0,f.trigger("rateyo.init",{rating:i.rating})}function i(b,c){var d;return a.each(c,function(){if(b===this.node)return d=this,!1}),d}function j(b,c){return a.each(c,function(a){if(b===this.node){var d=c.slice(0,a),e=c.slice(a+1,c.length);return c=d.concat(e),!1}}),c}function k(b){var c=h.prototype.collection,d=a(this);if(0===d.length)return d;var e=Array.prototype.slice.apply(arguments,[]);if(0===e.length)b=e[0]={};else{if(1!==e.length||"object"!=typeof e[0]){if(e.length>=1&&"string"==typeof e[0]){var f=e[0],g=e.slice(1),j=[];return a.each(d,function(a,b){var d=i(b,c);if(!d)throw Error("Trying to set options before even initialization");var e=d[f];if(!e)throw Error("Method "+f+" does not exist!");var h=e.apply(d,g);j.push(h)}),j=1===j.length?j[0]:j}throw Error("Invalid Arguments")}b=e[0]}return b=a.extend({},n,b),a.each(d,function(){var d=i(this,c);if(d)return d;var e=a(this),f={},g=a.extend({},b);return a.each(e.data(),function(a,b){if(0===a.indexOf("rateyo")){var c=a.replace(/^rateyo/,"");c=c[0].toLowerCase()+c.slice(1),f[c]=b,delete g[c]}}),new h(a(this),a.extend({},f,g))})}function l(){return k.apply(this,Array.prototype.slice.apply(arguments,[]))}var m='<?xml version="1.0" encoding="utf-8"?><svg version="1.1"xmlns="http://www.w3.org/2000/svg"viewBox="0 12.705 512 486.59"x="0px" y="0px"xml:space="preserve"><polygon points="256.814,12.705 317.205,198.566 512.631,198.566 354.529,313.435 414.918,499.295 256.814,384.427 98.713,499.295 159.102,313.435 1,198.566 196.426,198.566 "/></svg>',n={starWidth:"32px",normalFill:"gray",ratedFill:"#f39c12",numStars:5,maxValue:5,precision:1,rating:0,fullStar:!1,halfStar:!1,readOnly:!1,spacing:"0px",rtl:!1,multiColor:null,onInit:null,onChange:null,onSet:null,starSvg:null},o={startColor:"#c0392b",endColor:"#f1c40f"},p=/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i,q=function(a){if(!p.test(a))return null;var b=p.exec(a);return{r:parseInt(b[1],16),g:parseInt(b[2],16),b:parseInt(b[3],16)}};h.prototype.collection=[],window.RateYo=h,a.fn.rateYo=l}(window.jQuery);
36379(function() {
36380  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
36381
36382  App.Widgets.Downloads = (function() {
36383    var isUpdatingRating;
36384
36385    function Downloads() {
36386      this.onClientFeedbackQuestionPreviousClick = bind(this.onClientFeedbackQuestionPreviousClick, this);
36387      this.prepareNewClientFeedbackForm = bind(this.prepareNewClientFeedbackForm, this);
36388      this.onClientFeedbackSet = bind(this.onClientFeedbackSet, this);
36389      this.onClientFeedbackSubmit = bind(this.onClientFeedbackSubmit, this);
36390      this.onClientFeedbackUpadate = bind(this.onClientFeedbackUpadate, this);
36391      this.onDownloadClick = bind(this.onDownloadClick, this);
36392    }
36393
36394    isUpdatingRating = false;
36395
36396    Downloads.prototype.clientFeedbacks = {
36397      old: {}
36398    };
36399
36400    Downloads.prototype.initialize = function() {
36401      return $(document).on('click', '.js-download', this.onDownloadClick);
36402    };
36403
36404    Downloads.prototype.initRatingStars = function() {
36405      return $('.js-rating-stars').rateYo({
36406        starWidth: '35px',
36407        fullStar: true,
36408        onSet: this.onClientFeedbackSet
36409      });
36410    };
36411
36412    Downloads.prototype.onDownloadClick = function(e) {
36413      var $el;
36414      $el = $(e.currentTarget);
36415      this.$modal = $($el.data('target'));
36416      $el.addClass('is-disabled');
36417      return $.ajax({
36418        url: $el.data('href'),
36419        complete: (function(_this) {
36420          return function(response) {
36421            if (response.status !== 200) {
36422              window.sendErrorToSentry(new Error('Download error'), {
36423                tags: {
36424                  section: 'Purchase download'
36425                },
36426                extra: response
36427              });
36428            }
36429            $el.removeClass('is-disabled');
36430            if (!ReactRailsUJS) {
36431              window.sendErrorToSentry(new Error('Uninitialised ReactRailsUJS in download modal!'), {
36432                tags: {
36433                  section: 'Purchase download Component initialize'
36434                },
36435                extra: response
36436              });
36437            }
36438            if (response.success) {
36439              _this.$modal.find('.js-downloads-list').html(response.responseText);
36440            }
36441            if (!_this.$modal.hasClass('js-react-modal')) {
36442              _this.$modal.makeMeModal();
36443            }
36444            ReactRailsUJS.mountComponents();
36445            _this.initRatingStars();
36446            $('.js-client-feedback-next').on('click', _this.onClientFeedbackQuestionNextClick);
36447            return $('.js-client-feedback-previous').on('click', _this.onClientFeedbackQuestionPreviousClick);
36448          };
36449        })(this)
36450      });
36451    };
36452
36453    Downloads.prototype.onClientFeedbackUpadate = function() {
36454      return $.ajax({
36455        url: '/client_feedbacks/' + this.clientFeedbacks.old.old_feedback_id,
36456        method: 'PUT',
36457        data: {
36458          client_feedback: this.clientFeedbacks.old
36459        },
36460        complete: (function(_this) {
36461          return function(response) {
36462            return _this.prepareNewClientFeedbackForm(response.responseJSON.next_feedback_question);
36463          };
36464        })(this)
36465      });
36466    };
36467
36468    Downloads.prototype.onClientFeedbackSubmit = function() {
36469      return $.ajax({
36470        url: '/client_feedbacks',
36471        method: 'POST',
36472        data: {
36473          client_feedback: this.clientFeedbacks.old
36474        },
36475        complete: (function(_this) {
36476          return function(response) {
36477            return _this.prepareNewClientFeedbackForm(response.responseJSON.next_feedback_question);
36478          };
36479        })(this)
36480      });
36481    };
36482
36483    Downloads.prototype.onClientFeedbackSet = function(rating) {
36484      $('.js-rating-field').val(rating);
36485      $('.js-client-feedback').fadeOut();
36486      this.clientFeedbacks.old.rating = $('.js-rating-field')[0].value;
36487      this.clientFeedbacks.old.question = $('.js-client-feedback-question').text();
36488      this.clientFeedbacks.old.client_feedback_question_id = $('#client-feedback-question')[0].value;
36489      this.clientFeedbacks.old.info = {
36490        good_value: $('.js-good-value').text(),
36491        bad_value: $('.js-bad-value').text()
36492      };
36493      if (this.isUpdatingRating) {
36494        this.onClientFeedbackUpadate();
36495      } else {
36496        this.onClientFeedbackSubmit();
36497      }
36498      return this.isUpdatingRating = false;
36499    };
36500
36501    Downloads.prototype.prepareNewClientFeedbackForm = function(newFeedbackQuestion) {
36502      if (newFeedbackQuestion.id) {
36503        this.clientFeedbacks.old.feedback_id = newFeedbackQuestion.old_feedback_id;
36504        $('.js-good-value').text(newFeedbackQuestion.info.good_value);
36505        $('.js-bad-value').text(newFeedbackQuestion.info.bad_value);
36506        $('.js-client-feedback-question').text(newFeedbackQuestion.question);
36507        $('#client-feedback-question').val(newFeedbackQuestion.id);
36508        $('.js-rating-field').val(0);
36509        $('.js-client-feedback-answered').fadeOut();
36510        $('.js-client-feedback-answered').removeClass('is-hidden');
36511        return $('.js-client-feedback-answered').fadeIn();
36512      }
36513    };
36514
36515    Downloads.prototype.onClientFeedbackQuestionNextClick = function() {
36516      $('.js-client-feedback-answered').fadeOut();
36517      return setTimeout(function() {
36518        return $('.js-client-feedback').fadeIn();
36519      }, 400);
36520    };
36521
36522    Downloads.prototype.onClientFeedbackQuestionPreviousClick = function() {
36523      this.isUpdatingRating = true;
36524      $('.js-good-value').text(this.clientFeedbacks.old.info.good_value);
36525      $('.js-bad-value').text(this.clientFeedbacks.old.info.bad_value);
36526      $('.js-client-feedback-question').text(this.clientFeedbacks.old.question);
36527      $('.js-rating-field').val(this.clientFeedbacks.old.rating);
36528      $('#client-feedback-question').val(this.clientFeedbacks.old.client_feedback_question_id);
36529      $('.js-client-feedback-answered').fadeOut();
36530      return setTimeout(function() {
36531        return $('.js-client-feedback').fadeIn();
36532      }, 400);
36533    };
36534
36535    return Downloads;
36536
36537  })();
36538
36539}).call(this);
36540(function() {
36541  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
36542
36543  App.Widgets.Follow = (function() {
36544    function Follow() {
36545      this.onAjaxSuccess = bind(this.onAjaxSuccess, this);
36546      this.onAjaxBefore = bind(this.onAjaxBefore, this);
36547    }
36548
36549    Follow.prototype.initialize = function() {
36550      this.$el = $('.js-follow');
36551      this.$form = $('.user-follow-form');
36552      return this.bindEvents();
36553    };
36554
36555    Follow.prototype.bindEvents = function() {
36556      this.$form.on('ajax:before', this.onAjaxBefore);
36557      return this.$form.on('ajax:success', this.onAjaxSuccess);
36558    };
36559
36560    Follow.prototype.showFollow = function() {
36561      var $icon;
36562      this.$el.find('span').text('Follow');
36563      $icon = this.$el.find('.fa');
36564      $icon.removeClass('fa-check-circle').addClass('fa-plus-circle');
36565      this.$el.data('method', 'post');
36566      this.$el.attr('href', this.$el.data('follow-url'));
36567      return this.$el.addClass('button--alt');
36568    };
36569
36570    Follow.prototype.showUnfollow = function() {
36571      var $icon;
36572      this.$el.find('span').text('Following');
36573      $icon = this.$el.find('.fa');
36574      $icon.removeClass('fa-plus-circle').addClass('fa-check-circle');
36575      this.$el.data('method', 'delete');
36576      this.$el.attr('href', this.$el.data('unfollow-url'));
36577      return this.$el.removeClass('button--alt');
36578    };
36579
36580    Follow.prototype.onAjaxBefore = function() {
36581      this.$el.addClass('is-disabled');
36582      if (App.config.userSignedIn) {
36583        return;
36584      }
36585      this.$el.removeClass('is-disabled');
36586      return false;
36587    };
36588
36589    Follow.prototype.onAjaxSuccess = function() {
36590      this.$el.removeClass('is-disabled');
36591      if (this.$el.data('method') === 'post') {
36592        return this.showUnfollow();
36593      } else {
36594        return this.showFollow();
36595      }
36596    };
36597
36598    return Follow;
36599
36600  })();
36601
36602}).call(this);
36603(function() {
36604  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
36605
36606  App.Widgets.FreeDownloadLink = (function() {
36607    function FreeDownloadLink() {
36608      this.handleSocialShare = bind(this.handleSocialShare, this);
36609      this.bindSocialSharingEvents = bind(this.bindSocialSharingEvents, this);
36610    }
36611
36612    FreeDownloadLink.prototype.lastDownloadLink = null;
36613
36614    FreeDownloadLink.prototype.initialize = function() {
36615      this.$modal = $('#modal-free-download');
36616      return this.bindEvents();
36617    };
36618
36619    FreeDownloadLink.prototype.bindEvents = function() {
36620      App.events.subscribe('twitter:tweet', this.handleSocialShare);
36621      App.events.subscribe('twitter:follow', this.handleSocialShare);
36622      App.events.subscribe('facebook:share', this.handleSocialShare);
36623      return App.events.subscribe('facebook:like', this.handleSocialShare);
36624    };
36625
36626    FreeDownloadLink.prototype.bindSocialSharingEvents = function() {
36627      FB.init({
36628        appId: App.config.facebookAppId,
36629        xfbml: true,
36630        version: 'v2.4'
36631      });
36632      twttr.ready(function(twttr) {
36633        twttr.events.bind('tweet', function() {
36634          return App.events.publish('twitter:tweet', 'twitter#tweet');
36635        });
36636        return twttr.events.bind('follow', function() {
36637          return App.events.publish('twitter:follow', 'twitter#follow');
36638        });
36639      });
36640      FB.Event.subscribe('edge.create', function() {
36641        return App.events.publish('facebook:like', 'facebook#like');
36642      });
36643      return $('.js-facebook-share').on('click', this.shareOnFacebook);
36644    };
36645
36646    FreeDownloadLink.prototype.shareOnFacebook = function(event) {
36647      mute(event);
36648      return FB.ui({
36649        method: 'share',
36650        href: $('.js-facebook-share').attr('data-href')
36651      }, function(response) {
36652        if (response && !response.error_message) {
36653          return App.events.publish('facebook:share', 'facebook#share');
36654        }
36655      });
36656    };
36657
36658    FreeDownloadLink.prototype.handleSocialShare = function(social_network) {
36659      $('#modal-free-download').makeMeModal('close');
36660      return $.ajax({
36661        url: '/users/update_bonus_downloads',
36662        type: 'POST',
36663        data: {
36664          origin: social_network
36665        },
36666        success: (function(_this) {
36667          return function(data) {
36668            if (data.success) {
36669              App.events.publish('app:notification:success', "Thank you! Now you can download " + data.bonus_free_downloads + " new free models!");
36670              return window.location = _this.lastDownloadLink;
36671            } else {
36672              return App.events.publish('app:notification:error', 'Could not record your social share.');
36673            }
36674          };
36675        })(this)
36676      });
36677    };
36678
36679    return FreeDownloadLink;
36680
36681  })();
36682
36683}).call(this);
36684(function() {
36685  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
36686
36687  App.Widgets.Likes = (function() {
36688    function Likes() {
36689      this.onLikeBind = bind(this.onLikeBind, this);
36690    }
36691
36692    Likes.prototype.initialize = function() {
36693      this.setButtonVisibility();
36694      return this.bindEvents();
36695    };
36696
36697    Likes.prototype.setButtonVisibility = function() {
36698      var $el;
36699      $el = $('.js-like-control');
36700      if ($el.lenght === 0) {
36701        return;
36702      }
36703      if (App.config.userSignedIn) {
36704        $el.removeClass('is-disabled');
36705      }
36706      if (gon.is_liked) {
36707        $el.find('.button--like__text').text('Liked');
36708        return $el.addClass('is-active');
36709      } else {
36710        return $el.find('.button--like__text').text('Like');
36711      }
36712    };
36713
36714    Likes.prototype.bindEvents = function() {
36715      App.events.subscribe('app:likes:like', this.onLike);
36716      App.events.subscribe('app:likes:default:trigger', this.updateUI);
36717      App.events.subscribe('app:likes:default:trigger', this.sync);
36718      App.events.subscribe('app:likes:error', this.likeError);
36719      App.events.subscribe('app:likes:bind', this.onLikeBind);
36720      return App.events.publish('app:likes:bind', $('body'));
36721    };
36722
36723    Likes.prototype.onLike = function(id, type) {
36724      if (!App.config.userSignedIn) {
36725        return;
36726      }
36727      return $.get('/api/internal/likes/valid_like', function(response) {
36728        if (response) {
36729          return App.events.publish('app:likes:default:trigger', id, type);
36730        } else {
36731          return App.events.publish('app:likes:error');
36732        }
36733      });
36734    };
36735
36736    Likes.prototype.updateUI = function(id, type) {
36737      var $el;
36738      $el = $(".js-like[data-item-id=" + id + "][data-item-type=" + type + "]");
36739      this.$itemLikesCounter = $el.find('.js-likes-counter');
36740      if (!$el.hasClass('is-active')) {
36741        $el.addClass('is-active');
36742        $el.find('.like-button__text').text('You like this');
36743        $el.find('.button--like__text').text('Liked');
36744        $el.find('.like-button__counter, .js-likes-counter').eq(0).text(parseInt($el.find('.like-button__counter, .js-likes-counter').eq(0).text(), 10) + 1);
36745        return this.$itemLikesCounter.text(parseInt($el.find('.like-button__counter, .js-likes-counter').eq(0).text(), 10));
36746      } else {
36747        $el.find('.like-button__text').text('Like this');
36748        $el.find('.button--like__text').text('Like');
36749        $el.removeClass('is-active');
36750        $el.find('.like-button__counter, .js-likes-counter').eq(0).text(parseInt($el.find('.like-button__counter, .js-likes-counter').eq(0).text(), 10) - 1);
36751        return this.$itemLikesCounter.text(parseInt($el.find('.like-button__counter, .js-likes-counter').eq(0).text(), 10));
36752      }
36753    };
36754
36755    Likes.prototype.sync = function(id, type) {
36756      return $.post('/api/internal/likes/like', {
36757        like: {
36758          likeable_id: id,
36759          likeable_type: type
36760        }
36761      });
36762    };
36763
36764    Likes.prototype.likeError = function() {
36765      return App.events.publish('app:notification:flash:error', I18n.t('likes.like_failure'));
36766    };
36767
36768    Likes.prototype.onLikeClick = function(e) {
36769      var $el;
36770      $el = $(e.currentTarget);
36771      return App.events.publish('app:likes:like', $el.attr('data-item-id'), $el.attr('data-item-type'));
36772    };
36773
36774    Likes.prototype.onLikeBind = function($scope) {
36775      return $scope.find('.js-like').on('click', this.onLikeClick);
36776    };
36777
36778    return Likes;
36779
36780  })();
36781
36782}).call(this);
36783(function() {
36784  App.Widgets.LinkFaker = (function() {
36785    function LinkFaker() {}
36786
36787    LinkFaker.prototype.initialize = function() {
36788      return this.bindEvents();
36789    };
36790
36791    LinkFaker.prototype.bindEvents = function() {
36792      return $('.js-acts-as-link').on('click', this.onClick);
36793    };
36794
36795    LinkFaker.prototype.onClick = function(e) {
36796      var $el;
36797      e.preventDefault();
36798      $el = $(e.currentTarget);
36799      return window.location = $el.data('url');
36800    };
36801
36802    return LinkFaker;
36803
36804  })();
36805
36806}).call(this);
36807(function() {
36808  App.Widgets.LoyaltyLevelProgressInfoTooltip = (function() {
36809    function LoyaltyLevelProgressInfoTooltip() {}
36810
36811    LoyaltyLevelProgressInfoTooltip.prototype.initialize = function() {
36812      this.$infoTooltip = $('.js-loyalty-level-info-tooltip');
36813      this.$currentLevelTooltip = $('.js-current-level-tooltip');
36814      this.$nextLevelTooltip = $('.js-next-level-tooltip');
36815      if (this.$infoTooltip.length > 0) {
36816        this.initInfoTooltip();
36817      }
36818      if (this.$currentLevelTooltip.length > 0) {
36819        this.initCurrentLevelTooltip();
36820      }
36821      if (this.$nextLevelTooltip.length > 0) {
36822        return this.initNextLevelTooltip();
36823      }
36824    };
36825
36826    LoyaltyLevelProgressInfoTooltip.prototype.initInfoTooltip = function() {
36827      var infoTooltipMessage;
36828      infoTooltipMessage = I18n.t('tooltips.buyer_loyalty_program_html', {
36829        buyer_loyalty_rate: $('.js-buyer-loyalty-rate-value').text()
36830      });
36831      return this.$infoTooltip.tooltipster({
36832        theme: 'tooltip--light tooltip--popover u-text-center',
36833        animation: 'grow',
36834        contentAsHTML: true,
36835        content: infoTooltipMessage,
36836        maxWidth: 520
36837      });
36838    };
36839
36840    LoyaltyLevelProgressInfoTooltip.prototype.initCurrentLevelTooltip = function() {
36841      var currentLevelTooltipMessage;
36842      currentLevelTooltipMessage = I18n.t('tooltips.buyer_loyalty_program_current_level_html', {
36843        total_price_paid: $('.js-price-paid').text(),
36844        buyer_loyalty_rate: $('.js-buyer-loyalty-rate-value').text()
36845      });
36846      return this.$currentLevelTooltip.tooltipster({
36847        theme: 'tooltip--light tooltip--popover u-text-center',
36848        animation: 'grow',
36849        contentAsHTML: true,
36850        content: currentLevelTooltipMessage,
36851        maxWidth: 300
36852      });
36853    };
36854
36855    LoyaltyLevelProgressInfoTooltip.prototype.initNextLevelTooltip = function() {
36856      var nextLevelTooltipMessage;
36857      nextLevelTooltipMessage = I18n.t('tooltips.buyer_loyalty_program_next_level_html', {
36858        next_lvl_expenses: $('.js-next-lvl-expenses').text(),
36859        next_lvl_expenses_rate: $('.js-next-lvl-expenses-rate').text(),
36860        left_until_next_lvl: $('.js-next-lvl-expenses').text().match(/\d+/) - $('.js-price-paid').text().match(/\d+/)
36861      });
36862      return this.$nextLevelTooltip.tooltipster({
36863        theme: 'tooltip--light tooltip--popover u-text-center',
36864        animation: 'grow',
36865        contentAsHTML: true,
36866        content: nextLevelTooltipMessage,
36867        maxWidth: 250
36868      });
36869    };
36870
36871    return LoyaltyLevelProgressInfoTooltip;
36872
36873  })();
36874
36875}).call(this);
36876/* Notify.js - http://notifyjs.com/ Copyright (c) 2015 MIT */
36877
36878(function (factory) {
36879	// UMD start
36880	// https://github.com/umdjs/umd/blob/master/jqueryPluginCommonjs.js
36881	if (typeof define === 'function' && define.amd) {
36882		// AMD. Register as an anonymous module.
36883		define(['jquery'], factory);
36884	} else if (typeof module === 'object' && module.exports) {
36885		// Node/CommonJS
36886		module.exports = function( root, jQuery ) {
36887			if ( jQuery === undefined ) {
36888				// require('jQuery') returns a factory that requires window to
36889				// build a jQuery instance, we normalize how we use modules
36890				// that require this pattern but the window provided is a noop
36891				// if it's defined (how jquery works)
36892				if ( typeof window !== 'undefined' ) {
36893					jQuery = require('jquery');
36894				}
36895				else {
36896					jQuery = require('jquery')(root);
36897				}
36898			}
36899			factory(jQuery);
36900			return jQuery;
36901		};
36902	} else {
36903		// Browser globals
36904		factory(jQuery);
36905	}
36906}(function ($) {
36907	//IE8 indexOf polyfill
36908	var indexOf = [].indexOf || function(item) {
36909		for (var i = 0, l = this.length; i < l; i++) {
36910			if (i in this && this[i] === item) {
36911				return i;
36912			}
36913		}
36914		return -1;
36915	};
36916
36917	var pluginName = "notify";
36918	var pluginClassName = pluginName + "js";
36919	var blankFieldName = pluginName + "!blank";
36920
36921	var positions = {
36922		t: "top",
36923		m: "middle",
36924		b: "bottom",
36925		l: "left",
36926		c: "center",
36927		r: "right"
36928	};
36929	var hAligns = ["l", "c", "r"];
36930	var vAligns = ["t", "m", "b"];
36931	var mainPositions = ["t", "b", "l", "r"];
36932	var opposites = {
36933		t: "b",
36934		m: null,
36935		b: "t",
36936		l: "r",
36937		c: null,
36938		r: "l"
36939	};
36940
36941	var parsePosition = function(str) {
36942		var pos;
36943		pos = [];
36944		$.each(str.split(/\W+/), function(i, word) {
36945			var w;
36946			w = word.toLowerCase().charAt(0);
36947			if (positions[w]) {
36948				return pos.push(w);
36949			}
36950		});
36951		return pos;
36952	};
36953
36954	var styles = {};
36955
36956	var coreStyle = {
36957		name: "core",
36958		html: "<div class=\"" + pluginClassName + "-wrapper\">\n	<div class=\"" + pluginClassName + "-arrow\"></div>\n	<div class=\"" + pluginClassName + "-container\"></div>\n</div>",
36959		css: "." + pluginClassName + "-corner {\n	position: fixed;\n	margin: 5px;\n	z-index: 1050;\n}\n\n." + pluginClassName + "-corner ." + pluginClassName + "-wrapper,\n." + pluginClassName + "-corner ." + pluginClassName + "-container {\n	position: relative;\n	display: block;\n	height: inherit;\n	width: inherit;\n	margin: 3px;\n}\n\n." + pluginClassName + "-wrapper {\n	z-index: 1;\n	position: absolute;\n	display: inline-block;\n	height: 0;\n	width: 0;\n}\n\n." + pluginClassName + "-container {\n	display: none;\n	z-index: 1;\n	position: absolute;\n}\n\n." + pluginClassName + "-hidable {\n	cursor: pointer;\n}\n\n[data-notify-text],[data-notify-html] {\n	position: relative;\n}\n\n." + pluginClassName + "-arrow {\n	position: absolute;\n	z-index: 2;\n	width: 0;\n	height: 0;\n}"
36960	};
36961
36962	var stylePrefixes = {
36963		"border-radius": ["-webkit-", "-moz-"]
36964	};
36965
36966	var getStyle = function(name) {
36967		return styles[name];
36968	};
36969
36970	var removeStyle = function(name) {
36971		if (!name) {
36972			throw "Missing Style name";
36973		}
36974		if (styles[name]) {
36975			delete styles[name];
36976		}
36977	};
36978
36979	var addStyle = function(name, def) {
36980		if (!name) {
36981			throw "Missing Style name";
36982		}
36983		if (!def) {
36984			throw "Missing Style definition";
36985		}
36986		if (!def.html) {
36987			throw "Missing Style HTML";
36988		}
36989		//remove existing style
36990		var existing = styles[name];
36991		if (existing && existing.cssElem) {
36992			if (window.console) {
36993				console.warn(pluginName + ": overwriting style '" + name + "'");
36994			}
36995			styles[name].cssElem.remove();
36996		}
36997		def.name = name;
36998		styles[name] = def;
36999		var cssText = "";
37000		if (def.classes) {
37001			$.each(def.classes, function(className, props) {
37002				cssText += "." + pluginClassName + "-" + def.name + "-" + className + " {\n";
37003				$.each(props, function(name, val) {
37004					if (stylePrefixes[name]) {
37005						$.each(stylePrefixes[name], function(i, prefix) {
37006							return cssText += "	" + prefix + name + ": " + val + ";\n";
37007						});
37008					}
37009					return cssText += "	" + name + ": " + val + ";\n";
37010				});
37011				return cssText += "}\n";
37012			});
37013		}
37014		if (def.css) {
37015			cssText += "/* styles for " + def.name + " */\n" + def.css;
37016		}
37017		if (cssText) {
37018			def.cssElem = insertCSS(cssText);
37019			def.cssElem.attr("id", "notify-" + def.name);
37020		}
37021		var fields = {};
37022		var elem = $(def.html);
37023		findFields("html", elem, fields);
37024		findFields("text", elem, fields);
37025		def.fields = fields;
37026	};
37027
37028	var insertCSS = function(cssText) {
37029		var e, elem, error;
37030		elem = createElem("style");
37031		elem.attr("type", 'text/css');
37032		$("head").append(elem);
37033		try {
37034			elem.html(cssText);
37035		} catch (_) {
37036			elem[0].styleSheet.cssText = cssText;
37037		}
37038		return elem;
37039	};
37040
37041	var findFields = function(type, elem, fields) {
37042		var attr;
37043		if (type !== "html") {
37044			type = "text";
37045		}
37046		attr = "data-notify-" + type;
37047		return find(elem, "[" + attr + "]").each(function() {
37048			var name;
37049			name = $(this).attr(attr);
37050			if (!name) {
37051				name = blankFieldName;
37052			}
37053			fields[name] = type;
37054		});
37055	};
37056
37057	var find = function(elem, selector) {
37058		if (elem.is(selector)) {
37059			return elem;
37060		} else {
37061			return elem.find(selector);
37062		}
37063	};
37064
37065	var pluginOptions = {
37066		clickToHide: true,
37067		autoHide: true,
37068		autoHideDelay: 5000,
37069		arrowShow: true,
37070		arrowSize: 5,
37071		breakNewLines: true,
37072		elementPosition: "bottom",
37073		globalPosition: "top right",
37074		style: "bootstrap",
37075		className: "error",
37076		showAnimation: "slideDown",
37077		showDuration: 400,
37078		hideAnimation: "slideUp",
37079		hideDuration: 200,
37080		gap: 5
37081	};
37082
37083	var inherit = function(a, b) {
37084		var F;
37085		F = function() {};
37086		F.prototype = a;
37087		return $.extend(true, new F(), b);
37088	};
37089
37090	var defaults = function(opts) {
37091		return $.extend(pluginOptions, opts);
37092	};
37093
37094	var createElem = function(tag) {
37095		return $("<" + tag + "></" + tag + ">");
37096	};
37097
37098	var globalAnchors = {};
37099
37100	var getAnchorElement = function(element) {
37101		var radios;
37102		if (element.is('[type=radio]')) {
37103			radios = element.parents('form:first').find('[type=radio]').filter(function(i, e) {
37104				return $(e).attr("name") === element.attr("name");
37105			});
37106			element = radios.first();
37107		}
37108		return element;
37109	};
37110
37111	var incr = function(obj, pos, val) {
37112		var opp, temp;
37113		if (typeof val === "string") {
37114			val = parseInt(val, 10);
37115		} else if (typeof val !== "number") {
37116			return;
37117		}
37118		if (isNaN(val)) {
37119			return;
37120		}
37121		opp = positions[opposites[pos.charAt(0)]];
37122		temp = pos;
37123		if (obj[opp] !== undefined) {
37124			pos = positions[opp.charAt(0)];
37125			val = -val;
37126		}
37127		if (obj[pos] === undefined) {
37128			obj[pos] = val;
37129		} else {
37130			obj[pos] += val;
37131		}
37132		return null;
37133	};
37134
37135	var realign = function(alignment, inner, outer) {
37136		if (alignment === "l" || alignment === "t") {
37137			return 0;
37138		} else if (alignment === "c" || alignment === "m") {
37139			return outer / 2 - inner / 2;
37140		} else if (alignment === "r" || alignment === "b") {
37141			return outer - inner;
37142		}
37143		throw "Invalid alignment";
37144	};
37145
37146	var encode = function(text) {
37147		encode.e = encode.e || createElem("div");
37148		return encode.e.text(text).html();
37149	};
37150
37151	function Notification(elem, data, options) {
37152		if (typeof options === "string") {
37153			options = {
37154				className: options
37155			};
37156		}
37157		this.options = inherit(pluginOptions, $.isPlainObject(options) ? options : {});
37158		this.loadHTML();
37159		this.wrapper = $(coreStyle.html);
37160		if (this.options.clickToHide) {
37161			this.wrapper.addClass(pluginClassName + "-hidable");
37162		}
37163		this.wrapper.data(pluginClassName, this);
37164		this.arrow = this.wrapper.find("." + pluginClassName + "-arrow");
37165		this.container = this.wrapper.find("." + pluginClassName + "-container");
37166		this.container.append(this.userContainer);
37167		if (elem && elem.length) {
37168			this.elementType = elem.attr("type");
37169			this.originalElement = elem;
37170			this.elem = getAnchorElement(elem);
37171			this.elem.data(pluginClassName, this);
37172			this.elem.before(this.wrapper);
37173		}
37174		this.container.hide();
37175		this.run(data);
37176	}
37177
37178	Notification.prototype.loadHTML = function() {
37179		var style;
37180		style = this.getStyle();
37181		this.userContainer = $(style.html);
37182		this.userFields = style.fields;
37183	};
37184
37185	Notification.prototype.show = function(show, userCallback) {
37186		var args, callback, elems, fn, hidden;
37187		callback = (function(_this) {
37188			return function() {
37189				if (!show && !_this.elem) {
37190					_this.destroy();
37191				}
37192				if (userCallback) {
37193					return userCallback();
37194				}
37195			};
37196		})(this);
37197		hidden = this.container.parent().parents(':hidden').length > 0;
37198		elems = this.container.add(this.arrow);
37199		args = [];
37200		if (hidden && show) {
37201			fn = "show";
37202		} else if (hidden && !show) {
37203			fn = "hide";
37204		} else if (!hidden && show) {
37205			fn = this.options.showAnimation;
37206			args.push(this.options.showDuration);
37207		} else if (!hidden && !show) {
37208			fn = this.options.hideAnimation;
37209			args.push(this.options.hideDuration);
37210		} else {
37211			return callback();
37212		}
37213		args.push(callback);
37214		return elems[fn].apply(elems, args);
37215	};
37216
37217	Notification.prototype.setGlobalPosition = function() {
37218		var p = this.getPosition();
37219		var pMain = p[0];
37220		var pAlign = p[1];
37221		var main = positions[pMain];
37222		var align = positions[pAlign];
37223		var key = pMain + "|" + pAlign;
37224		var anchor = globalAnchors[key];
37225		if (!anchor || !document.body.contains(anchor[0])) {
37226			anchor = globalAnchors[key] = createElem("div");
37227			var css = {};
37228			css[main] = 0;
37229			if (align === "middle") {
37230				css.top = '45%';
37231			} else if (align === "center") {
37232				css.left = '45%';
37233			} else {
37234				css[align] = 0;
37235			}
37236			anchor.css(css).addClass(pluginClassName + "-corner");
37237			$("body").append(anchor);
37238		}
37239		return anchor.prepend(this.wrapper);
37240	};
37241
37242	Notification.prototype.setElementPosition = function() {
37243		var arrowColor, arrowCss, arrowSize, color, contH, contW, css, elemH, elemIH, elemIW, elemPos, elemW, gap, j, k, len, len1, mainFull, margin, opp, oppFull, pAlign, pArrow, pMain, pos, posFull, position, ref, wrapPos;
37244		position = this.getPosition();
37245		pMain = position[0];
37246		pAlign = position[1];
37247		pArrow = position[2];
37248		elemPos = this.elem.position();
37249		elemH = this.elem.outerHeight();
37250		elemW = this.elem.outerWidth();
37251		elemIH = this.elem.innerHeight();
37252		elemIW = this.elem.innerWidth();
37253		wrapPos = this.wrapper.position();
37254		contH = this.container.height();
37255		contW = this.container.width();
37256		mainFull = positions[pMain];
37257		opp = opposites[pMain];
37258		oppFull = positions[opp];
37259		css = {};
37260		css[oppFull] = pMain === "b" ? elemH : pMain === "r" ? elemW : 0;
37261		incr(css, "top", elemPos.top - wrapPos.top);
37262		incr(css, "left", elemPos.left - wrapPos.left);
37263		ref = ["top", "left"];
37264		for (j = 0, len = ref.length; j < len; j++) {
37265			pos = ref[j];
37266			margin = parseInt(this.elem.css("margin-" + pos), 10);
37267			if (margin) {
37268				incr(css, pos, margin);
37269			}
37270		}
37271		gap = Math.max(0, this.options.gap - (this.options.arrowShow ? arrowSize : 0));
37272		incr(css, oppFull, gap);
37273		if (!this.options.arrowShow) {
37274			this.arrow.hide();
37275		} else {
37276			arrowSize = this.options.arrowSize;
37277			arrowCss = $.extend({}, css);
37278			arrowColor = this.userContainer.css("border-color") || this.userContainer.css("border-top-color") || this.userContainer.css("background-color") || "white";
37279			for (k = 0, len1 = mainPositions.length; k < len1; k++) {
37280				pos = mainPositions[k];
37281				posFull = positions[pos];
37282				if (pos === opp) {
37283					continue;
37284				}
37285				color = posFull === mainFull ? arrowColor : "transparent";
37286				arrowCss["border-" + posFull] = arrowSize + "px solid " + color;
37287			}
37288			incr(css, positions[opp], arrowSize);
37289			if (indexOf.call(mainPositions, pAlign) >= 0) {
37290				incr(arrowCss, positions[pAlign], arrowSize * 2);
37291			}
37292		}
37293		if (indexOf.call(vAligns, pMain) >= 0) {
37294			incr(css, "left", realign(pAlign, contW, elemW));
37295			if (arrowCss) {
37296				incr(arrowCss, "left", realign(pAlign, arrowSize, elemIW));
37297			}
37298		} else if (indexOf.call(hAligns, pMain) >= 0) {
37299			incr(css, "top", realign(pAlign, contH, elemH));
37300			if (arrowCss) {
37301				incr(arrowCss, "top", realign(pAlign, arrowSize, elemIH));
37302			}
37303		}
37304		if (this.container.is(":visible")) {
37305			css.display = "block";
37306		}
37307		this.container.removeAttr("style").css(css);
37308		if (arrowCss) {
37309			return this.arrow.removeAttr("style").css(arrowCss);
37310		}
37311	};
37312
37313	Notification.prototype.getPosition = function() {
37314		var pos, ref, ref1, ref2, ref3, ref4, ref5, text;
37315		text = this.options.position || (this.elem ? this.options.elementPosition : this.options.globalPosition);
37316		pos = parsePosition(text);
37317		if (pos.length === 0) {
37318			pos[0] = "b";
37319		}
37320		if (ref = pos[0], indexOf.call(mainPositions, ref) < 0) {
37321			throw "Must be one of [" + mainPositions + "]";
37322		}
37323		if (pos.length === 1 || ((ref1 = pos[0], indexOf.call(vAligns, ref1) >= 0) && (ref2 = pos[1], indexOf.call(hAligns, ref2) < 0)) || ((ref3 = pos[0], indexOf.call(hAligns, ref3) >= 0) && (ref4 = pos[1], indexOf.call(vAligns, ref4) < 0))) {
37324			pos[1] = (ref5 = pos[0], indexOf.call(hAligns, ref5) >= 0) ? "m" : "l";
37325		}
37326		if (pos.length === 2) {
37327			pos[2] = pos[1];
37328		}
37329		return pos;
37330	};
37331
37332	Notification.prototype.getStyle = function(name) {
37333		var style;
37334		if (!name) {
37335			name = this.options.style;
37336		}
37337		if (!name) {
37338			name = "default";
37339		}
37340		style = styles[name];
37341		if (!style) {
37342			throw "Missing style: " + name;
37343		}
37344		return style;
37345	};
37346
37347	Notification.prototype.updateClasses = function() {
37348		var classes, style;
37349		classes = ["base"];
37350		if ($.isArray(this.options.className)) {
37351			classes = classes.concat(this.options.className);
37352		} else if (this.options.className) {
37353			classes.push(this.options.className);
37354		}
37355		style = this.getStyle();
37356		classes = $.map(classes, function(n) {
37357			return pluginClassName + "-" + style.name + "-" + n;
37358		}).join(" ");
37359		return this.userContainer.attr("class", classes);
37360	};
37361
37362	Notification.prototype.run = function(data, options) {
37363		var d, datas, name, type, value;
37364		if ($.isPlainObject(options)) {
37365			$.extend(this.options, options);
37366		} else if ($.type(options) === "string") {
37367			this.options.className = options;
37368		}
37369		if (this.container && !data) {
37370			this.show(false);
37371			return;
37372		} else if (!this.container && !data) {
37373			return;
37374		}
37375		datas = {};
37376		if ($.isPlainObject(data)) {
37377			datas = data;
37378		} else {
37379			datas[blankFieldName] = data;
37380		}
37381		for (name in datas) {
37382			d = datas[name];
37383			type = this.userFields[name];
37384			if (!type) {
37385				continue;
37386			}
37387			if (type === "text") {
37388				d = encode(d);
37389				if (this.options.breakNewLines) {
37390					d = d.replace(/\n/g, '<br/>');
37391				}
37392			}
37393			value = name === blankFieldName ? '' : '=' + name;
37394			find(this.userContainer, "[data-notify-" + type + value + "]").html(d);
37395		}
37396		this.updateClasses();
37397		if (this.elem) {
37398			this.setElementPosition();
37399		} else {
37400			this.setGlobalPosition();
37401		}
37402		this.show(true);
37403		if (this.options.autoHide) {
37404			clearTimeout(this.autohideTimer);
37405			this.autohideTimer = setTimeout(this.show.bind(this, false), this.options.autoHideDelay);
37406		}
37407	};
37408
37409	Notification.prototype.destroy = function() {
37410		this.wrapper.data(pluginClassName, null);
37411		this.wrapper.remove();
37412	};
37413
37414	$[pluginName] = function(elem, data, options) {
37415		if ((elem && elem.nodeName) || elem.jquery) {
37416			$(elem)[pluginName](data, options);
37417		} else {
37418			options = data;
37419			data = elem;
37420			new Notification(null, data, options);
37421		}
37422		return elem;
37423	};
37424
37425	$.fn[pluginName] = function(data, options) {
37426		$(this).each(function() {
37427			var prev = getAnchorElement($(this)).data(pluginClassName);
37428			if (prev) {
37429				prev.destroy();
37430			}
37431			var curr = new Notification($(this), data, options);
37432		});
37433		return this;
37434	};
37435
37436	$.extend($[pluginName], {
37437		defaults: defaults,
37438		addStyle: addStyle,
37439		removeStyle: removeStyle,
37440		pluginOptions: pluginOptions,
37441		getStyle: getStyle,
37442		insertCSS: insertCSS
37443	});
37444
37445	//always include the default bootstrap style
37446	addStyle("bootstrap", {
37447		html: "<div>\n<span data-notify-text></span>\n</div>",
37448		classes: {
37449			base: {
37450				"font-weight": "bold",
37451				"padding": "8px 15px 8px 14px",
37452				"text-shadow": "0 1px 0 rgba(255, 255, 255, 0.5)",
37453				"background-color": "#fcf8e3",
37454				"border": "1px solid #fbeed5",
37455				"border-radius": "4px",
37456				"white-space": "nowrap",
37457				"padding-left": "25px",
37458				"background-repeat": "no-repeat",
37459				"background-position": "3px 7px"
37460			},
37461			error: {
37462				"color": "#B94A48",
37463				"background-color": "#F2DEDE",
37464				"border-color": "#EED3D7",
37465				"background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtRJREFUeNqkVc1u00AQHq+dOD+0poIQfkIjalW0SEGqRMuRnHos3DjwAH0ArlyQeANOOSMeAA5VjyBxKBQhgSpVUKKQNGloFdw4cWw2jtfMOna6JOUArDTazXi/b3dm55socPqQhFka++aHBsI8GsopRJERNFlY88FCEk9Yiwf8RhgRyaHFQpPHCDmZG5oX2ui2yilkcTT1AcDsbYC1NMAyOi7zTX2Agx7A9luAl88BauiiQ/cJaZQfIpAlngDcvZZMrl8vFPK5+XktrWlx3/ehZ5r9+t6e+WVnp1pxnNIjgBe4/6dAysQc8dsmHwPcW9C0h3fW1hans1ltwJhy0GxK7XZbUlMp5Ww2eyan6+ft/f2FAqXGK4CvQk5HueFz7D6GOZtIrK+srupdx1GRBBqNBtzc2AiMr7nPplRdKhb1q6q6zjFhrklEFOUutoQ50xcX86ZlqaZpQrfbBdu2R6/G19zX6XSgh6RX5ubyHCM8nqSID6ICrGiZjGYYxojEsiw4PDwMSL5VKsC8Yf4VRYFzMzMaxwjlJSlCyAQ9l0CW44PBADzXhe7xMdi9HtTrdYjFYkDQL0cn4Xdq2/EAE+InCnvADTf2eah4Sx9vExQjkqXT6aAERICMewd/UAp/IeYANM2joxt+q5VI+ieq2i0Wg3l6DNzHwTERPgo1ko7XBXj3vdlsT2F+UuhIhYkp7u7CarkcrFOCtR3H5JiwbAIeImjT/YQKKBtGjRFCU5IUgFRe7fF4cCNVIPMYo3VKqxwjyNAXNepuopyqnld602qVsfRpEkkz+GFL1wPj6ySXBpJtWVa5xlhpcyhBNwpZHmtX8AGgfIExo0ZpzkWVTBGiXCSEaHh62/PoR0p/vHaczxXGnj4bSo+G78lELU80h1uogBwWLf5YlsPmgDEd4M236xjm+8nm4IuE/9u+/PH2JXZfbwz4zw1WbO+SQPpXfwG/BBgAhCNZiSb/pOQAAAAASUVORK5CYII=)"
37466			},
37467			success: {
37468				"color": "#468847",
37469				"background-color": "#DFF0D8",
37470				"border-color": "#D6E9C6",
37471				"background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAutJREFUeNq0lctPE0Ecx38zu/RFS1EryqtgJFA08YCiMZIAQQ4eRG8eDGdPJiYeTIwHTfwPiAcvXIwXLwoXPaDxkWgQ6islKlJLSQWLUraPLTv7Gme32zoF9KSTfLO7v53vZ3d/M7/fIth+IO6INt2jjoA7bjHCJoAlzCRw59YwHYjBnfMPqAKWQYKjGkfCJqAF0xwZjipQtA3MxeSG87VhOOYegVrUCy7UZM9S6TLIdAamySTclZdYhFhRHloGYg7mgZv1Zzztvgud7V1tbQ2twYA34LJmF4p5dXF1KTufnE+SxeJtuCZNsLDCQU0+RyKTF27Unw101l8e6hns3u0PBalORVVVkcaEKBJDgV3+cGM4tKKmI+ohlIGnygKX00rSBfszz/n2uXv81wd6+rt1orsZCHRdr1Imk2F2Kob3hutSxW8thsd8AXNaln9D7CTfA6O+0UgkMuwVvEFFUbbAcrkcTA8+AtOk8E6KiQiDmMFSDqZItAzEVQviRkdDdaFgPp8HSZKAEAL5Qh7Sq2lIJBJwv2scUqkUnKoZgNhcDKhKg5aH+1IkcouCAdFGAQsuWZYhOjwFHQ96oagWgRoUov1T9kRBEODAwxM2QtEUl+Wp+Ln9VRo6BcMw4ErHRYjH4/B26AlQoQQTRdHWwcd9AH57+UAXddvDD37DmrBBV34WfqiXPl61g+vr6xA9zsGeM9gOdsNXkgpEtTwVvwOklXLKm6+/p5ezwk4B+j6droBs2CsGa/gNs6RIxazl4Tc25mpTgw/apPR1LYlNRFAzgsOxkyXYLIM1V8NMwyAkJSctD1eGVKiq5wWjSPdjmeTkiKvVW4f2YPHWl3GAVq6ymcyCTgovM3FzyRiDe2TaKcEKsLpJvNHjZgPNqEtyi6mZIm4SRFyLMUsONSSdkPeFtY1n0mczoY3BHTLhwPRy9/lzcziCw9ACI+yql0VLzcGAZbYSM5CCSZg1/9oc/nn7+i8N9p/8An4JMADxhH+xHfuiKwAAAABJRU5ErkJggg==)"
37472			},
37473			info: {
37474				"color": "#3A87AD",
37475				"background-color": "#D9EDF7",
37476				"border-color": "#BCE8F1",
37477				"background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzg
37477nBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=)"
37478			},
37479			warn: {
37480				"color": "#C09853",
37481				"background-color": "#FCF8E3",
37482				"border-color": "#FBEED5",
37483				"background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABJlBMVEXr6eb/2oD/wi7/xjr/0mP/ykf/tQD/vBj/3o7/uQ//vyL/twebhgD/4pzX1K3z8e349vK6tHCilCWbiQymn0jGworr6dXQza3HxcKkn1vWvV/5uRfk4dXZ1bD18+/52YebiAmyr5S9mhCzrWq5t6ufjRH54aLs0oS+qD751XqPhAybhwXsujG3sm+Zk0PTwG6Shg+PhhObhwOPgQL4zV2nlyrf27uLfgCPhRHu7OmLgAafkyiWkD3l49ibiAfTs0C+lgCniwD4sgDJxqOilzDWowWFfAH08uebig6qpFHBvH/aw26FfQTQzsvy8OyEfz20r3jAvaKbhgG9q0nc2LbZxXanoUu/u5WSggCtp1anpJKdmFz/zlX/1nGJiYmuq5Dx7+sAAADoPUZSAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfdBgUBGhh4aah5AAAAlklEQVQY02NgoBIIE8EUcwn1FkIXM1Tj5dDUQhPU502Mi7XXQxGz5uVIjGOJUUUW81HnYEyMi2HVcUOICQZzMMYmxrEyMylJwgUt5BljWRLjmJm4pI1hYp5SQLGYxDgmLnZOVxuooClIDKgXKMbN5ggV1ACLJcaBxNgcoiGCBiZwdWxOETBDrTyEFey0jYJ4eHjMGWgEAIpRFRCUt08qAAAAAElFTkSuQmCC)"
37484			}
37485		}
37486	});
37487
37488	$(function() {
37489		insertCSS(coreStyle.css).attr("id", "core-notify");
37490		$(document).on("click", "." + pluginClassName + "-hidable", function(e) {
37491			$(this).trigger("notify-hide");
37492		});
37493		$(document).on("notify-hide", "." + pluginClassName + "-wrapper", function(e) {
37494			var elem = $(this).data(pluginClassName);
37495			if(elem) {
37496				elem.show(false);
37497			}
37498		});
37499	});
37500
37501}));
37502(function() {
37503  App.Widgets.Notification = (function() {
37504    function Notification() {}
37505
37506    Notification.prototype.initialize = function() {
37507      this.$el = $(".notification-bar");
37508      this.doOptOut = 0;
37509      if ($.notify) {
37510        $.notify.addStyle('custom', {
37511          html: '<div>' + '<div class="notification-icon js-notification-content" />' + '<div class="notification-content notifyjs-notification-text js-notification-content" data-notify-html />' + '<div class="notification-close-icon js-close-notification" />' + '</div>'
37512        });
37513      }
37514      App.events.subscribe('app:notification:flash:success', (function(_this) {
37515        return function(message, autohide, handleOptOut) {
37516          if (handleOptOut && handleOptOut.optOutCallback) {
37517            _this.doOptOut = handleOptOut.optOutCallback;
37518          }
37519          return _this.spawn(message, 'success', autohide);
37520        };
37521      })(this));
37522      App.events.subscribe('app:notification:flash:error', (function(_this) {
37523        return function(message, autohide) {
37524          return _this.spawn(message, 'error', autohide);
37525        };
37526      })(this));
37527      App.events.subscribe("app:notification:flash:warning", (function(_this) {
37528        return function(message) {
37529          return _this.spawn(message, 'warning');
37530        };
37531      })(this));
37532      App.events.subscribe('app:notification:flash:info', (function(_this) {
37533        return function(message) {
37534          return _this.spawn(message, 'info');
37535        };
37536      })(this));
37537      App.events.subscribe('app:notification:success', (function(_this) {
37538        return function(message) {
37539          return _this.spawn(message, 'success', false);
37540        };
37541      })(this));
37542      App.events.subscribe('app:notification:error', (function(_this) {
37543        return function(message) {
37544          return _this.spawn(message, 'error', false);
37545        };
37546      })(this));
37547      App.events.subscribe('app:notification:warning', (function(_this) {
37548        return function(message) {
37549          return _this.spawn(message, 'warning', false);
37550        };
37551      })(this));
37552      App.events.subscribe('app:notification:info', (function(_this) {
37553        return function(message) {
37554          return _this.spawn(message, 'info', false);
37555        };
37556      })(this));
37557      if (this.$el.hasClass('js-active-notification')) {
37558        this.spawn(this.$el.text(), 'error', false);
37559      }
37560      return this.triggerGonNotifications();
37561    };
37562
37563    Notification.prototype.spawn = function(message, type, autohide) {
37564      var $corner, topOffset;
37565      if (autohide == null) {
37566        autohide = true;
37567      }
37568      if (autohide) {
37569        $.notify(message, {
37570          style: 'custom',
37571          className: type,
37572          autoHide: true,
37573          autoHideDelay: 7000,
37574          showDuration: 200
37575        });
37576      } else {
37577        $.notify(message, {
37578          style: 'custom',
37579          className: type,
37580          autoHide: false,
37581          showDuration: 200
37582        });
37583      }
37584      $corner = $('.notifyjs-corner');
37585      topOffset = $('.wayfinder-container').height();
37586      return $corner.css('top', topOffset);
37587    };
37588
37589    Notification.prototype.triggerGonNotifications = function() {
37590      if (gon.js_success_message != null) {
37591        App.events.publish('app:notification:flash:success', gon.js_success_message, gon.autohide);
37592      }
37593      if (gon.js_error_message != null) {
37594        return App.events.publish('app:notification:flash:error', gon.js_error_message, gon.autohide);
37595      }
37596    };
37597
37598    return Notification;
37599
37600  })();
37601
37602}).call(this);
37603(function() {
37604  App.Widgets.PaymentAgreement = (function() {
37605    function PaymentAgreement() {}
37606
37607    PaymentAgreement.prototype.initialize = function() {
37608      if ((typeof BarsOrdering !== "undefined" && BarsOrdering !== null) && BarsOrdering('renderSignAgreementNotification')) {
37609        return this.showNotificationBar();
37610      }
37611    };
37612
37613    PaymentAgreement.prototype.showNotificationBar = function() {
37614      var $notificationBar;
37615      $notificationBar = $(JST["payment_agreement/notification_bar"]());
37616      return $('body').prepend($notificationBar);
37617    };
37618
37619    return PaymentAgreement;
37620
37621  })();
37622
37623}).call(this);
37624(function() {
37625  App.Widgets.Pinger = (function() {
37626    function Pinger() {}
37627
37628    Pinger.prototype.initialize = function() {
37629      if (App.config.environment !== 'test') {
37630        this.ping();
37631        return setTimeout(this.ping, App.config.userPingInterval);
37632      }
37633    };
37634
37635    Pinger.prototype.ping = function() {
37636      if (!App.config.userSignedIn) {
37637        return;
37638      }
37639      return $.post("/api/internal/users/ping");
37640    };
37641
37642    return Pinger;
37643
37644  })();
37645
37646}).call(this);
37647(function() {
37648  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
37649
37650  App.Widgets.PopoverBox = (function() {
37651    function PopoverBox() {
37652      this.onTriggerClick = bind(this.onTriggerClick, this);
37653      this.onOutsideClick = bind(this.onOutsideClick, this);
37654      this.open = bind(this.open, this);
37655      this.position = bind(this.position, this);
37656    }
37657
37658    PopoverBox.prototype.options = {
37659      animationDuration: 400,
37660      easing: 'easeInOutCirc'
37661    };
37662
37663    PopoverBox.prototype.$target = null;
37664
37665    PopoverBox.prototype.$el = null;
37666
37667    PopoverBox.prototype.initialize = function() {
37668      if (!this.$el.exists) {
37669        return;
37670      }
37671      return this.bindEvents();
37672    };
37673
37674    PopoverBox.prototype.bindEvents = function() {
37675      $(window).on('resize', this.position);
37676      this.$target.on('click', this.onTriggerClick);
37677      return this.$target.add(this.$el).hideOnOutsideClick(this.onOutsideClick);
37678    };
37679
37680    PopoverBox.prototype.position = function() {
37681      var offset;
37682      offset = this.$target.offset();
37683      if (!offset) {
37684        return;
37685      }
37686      return this.$el.css({
37687        top: offset.top + this.$target.height(),
37688        left: offset.left - this.$el.width() + this.$target.width()
37689      });
37690    };
37691
37692    PopoverBox.prototype.toggle = function() {
37693      if (this.$target.hasClass('is-active')) {
37694        this.hideCallback();
37695      } else {
37696        this.position();
37697        this.openCallback();
37698      }
37699      this.$target.toggleClass('is-active');
37700      return this.$el.slideToggle(this.options).promise().done((function(_this) {
37701        return function() {
37702          return _this.$el.toggleClass('is-active');
37703        };
37704      })(this));
37705    };
37706
37707    PopoverBox.prototype.hide = function() {
37708      if (!this.$target.hasClass('is-active')) {
37709        return;
37710      }
37711      this.hideCallback();
37712      this.$target.removeClass('is-active');
37713      return this.$el.slideUp(this.options).promise().done((function(_this) {
37714        return function() {
37715          return _this.$el.removeClass('is-active');
37716        };
37717      })(this));
37718    };
37719
37720    PopoverBox.prototype.open = function() {
37721      if (this.$target.hasClass('is-active')) {
37722        return;
37723      }
37724      this.position();
37725      this.openCallback();
37726      return this.$el.slideDown(this.options).promise().done((function(_this) {
37727        return function() {
37728          _this.$el.addClass('is-active');
37729          return _this.$target.addClass('is-active');
37730        };
37731      })(this));
37732    };
37733
37734    PopoverBox.prototype.onOutsideClick = function(e) {
37735      if (e != null) {
37736        if (typeof e.preventDefault === "function") {
37737          e.preventDefault();
37738        }
37739      }
37740      return this.hide();
37741    };
37742
37743    PopoverBox.prototype.onTriggerClick = function(e) {
37744      e.preventDefault();
37745      return this.toggle();
37746    };
37747
37748    PopoverBox.prototype.openCallback = function() {};
37749
37750    PopoverBox.prototype.hideCallback = function() {};
37751
37752    return PopoverBox;
37753
37754  })();
37755
37756}).call(this);
37757(function() {
37758  App.Widgets.PostalCodeTooltip = (function() {
37759    function PostalCodeTooltip() {}
37760
37761    PostalCodeTooltip.prototype.initialize = function() {
37762      this.$tooltip = $('.js-postal-code-tooltip');
37763      if (this.$tooltip.length > 0) {
37764        return this.initTooltip();
37765      }
37766    };
37767
37768    PostalCodeTooltip.prototype.initTooltip = function() {
37769      var content;
vendor: 10,914 bytes, lines 37770-38214
37770      content = "Some banks require address information in order to approve transactions. We recommend providing postal code and\ncountry as it may help to successfully authorize your payment.";
37771      return this.$tooltip.tooltipster({
37772        theme: 'tooltip--light tooltip--popover u-text-center',
37773        animation: 'grow',
37774        contentAsHTML: true,
37775        content: content,
37776        position: 'top',
37777        interactive: true,
37778        maxWidth: 400
37779      });
37780    };
37781
37782    return PostalCodeTooltip;
37783
37784  })();
37785
37786}).call(this);
37787(function() {
37788  App.Widgets.PotentialBuyerManager = (function() {
37789    function PotentialBuyerManager() {}
37790
37791    PotentialBuyerManager.prototype.initialize = function() {
37792      return setTimeout(function() {
37793        if (App.config.userIsPotentialBuyer) {
37794          return hj('trigger', 'UserIsPotentialBuyer');
37795        }
37796      }, 2000);
37797    };
37798
37799    return PotentialBuyerManager;
37800
37801  })();
37802
37803}).call(this);
37804/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
37805 * @license MIT */
37806
37807
37808;(function(root, factory) {
37809
37810  if (typeof define === 'function' && define.amd) {
37811    define(factory);
37812  } else if (typeof exports === 'object') {
37813    module.exports = factory();
37814  } else {
37815    root.NProgress = factory();
37816  }
37817
37818})(this, function() {
37819  var NProgress = {};
37820
37821  NProgress.version = '0.2.0';
37822
37823  var Settings = NProgress.settings = {
37824    minimum: 0.08,
37825    easing: 'ease',
37826    positionUsing: '',
37827    speed: 200,
37828    trickle: true,
37829    trickleRate: 0.02,
37830    trickleSpeed: 800,
37831    showSpinner: true,
37832    barSelector: '[role="bar"]',
37833    spinnerSelector: '[role="spinner"]',
37834    parent: 'body',
37835    template: '<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'
37836  };
37837
37838  /**
37839   * Updates configuration.
37840   *
37841   *     NProgress.configure({
37842   *       minimum: 0.1
37843   *     });
37844   */
37845  NProgress.configure = function(options) {
37846    var key, value;
37847    for (key in options) {
37848      value = options[key];
37849      if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value;
37850    }
37851
37852    return this;
37853  };
37854
37855  /**
37856   * Last number.
37857   */
37858
37859  NProgress.status = null;
37860
37861  /**
37862   * Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
37863   *
37864   *     NProgress.set(0.4);
37865   *     NProgress.set(1.0);
37866   */
37867
37868  NProgress.set = function(n) {
37869    var started = NProgress.isStarted();
37870
37871    n = clamp(n, Settings.minimum, 1);
37872    NProgress.status = (n === 1 ? null : n);
37873
37874    var progress = NProgress.render(!started),
37875        bar      = progress.querySelector(Settings.barSelector),
37876        speed    = Settings.speed,
37877        ease     = Settings.easing;
37878
37879    progress.offsetWidth; /* Repaint */
37880
37881    queue(function(next) {
37882      // Set positionUsing if it hasn't already been set
37883      if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS();
37884
37885      // Add transition
37886      css(bar, barPositionCSS(n, speed, ease));
37887
37888      if (n === 1) {
37889        // Fade out
37890        css(progress, {
37891          transition: 'none',
37892          opacity: 1
37893        });
37894        progress.offsetWidth; /* Repaint */
37895
37896        setTimeout(function() {
37897          css(progress, {
37898            transition: 'all ' + speed + 'ms linear',
37899            opacity: 0
37900          });
37901          setTimeout(function() {
37902            NProgress.remove();
37903            next();
37904          }, speed);
37905        }, speed);
37906      } else {
37907        setTimeout(next, speed);
37908      }
37909    });
37910
37911    return this;
37912  };
37913
37914  NProgress.isStarted = function() {
37915    return typeof NProgress.status === 'number';
37916  };
37917
37918  /**
37919   * Shows the progress bar.
37920   * This is the same as setting the status to 0%, except that it doesn't go backwards.
37921   *
37922   *     NProgress.start();
37923   *
37924   */
37925  NProgress.start = function() {
37926    if (!NProgress.status) NProgress.set(0);
37927
37928    var work = function() {
37929      setTimeout(function() {
37930        if (!NProgress.status) return;
37931        NProgress.trickle();
37932        work();
37933      }, Settings.trickleSpeed);
37934    };
37935
37936    if (Settings.trickle) work();
37937
37938    return this;
37939  };
37940
37941  /**
37942   * Hides the progress bar.
37943   * This is the *sort of* the same as setting the status to 100%, with the
37944   * difference being `done()` makes some placebo effect of some realistic motion.
37945   *
37946   *     NProgress.done();
37947   *
37948   * If `true` is passed, it will show the progress bar even if its hidden.
37949   *
37950   *     NProgress.done(true);
37951   */
37952
37953  NProgress.done = function(force) {
37954    if (!force && !NProgress.status) return this;
37955
37956    return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
37957  };
37958
37959  /**
37960   * Increments by a random amount.
37961   */
37962
37963  NProgress.inc = function(amount) {
37964    var n = NProgress.status;
37965
37966    if (!n) {
37967      return NProgress.start();
37968    } else {
37969      if (typeof amount !== 'number') {
37970        amount = (1 - n) * clamp(Math.random() * n, 0.1, 0.95);
37971      }
37972
37973      n = clamp(n + amount, 0, 0.994);
37974      return NProgress.set(n);
37975    }
37976  };
37977
37978  NProgress.trickle = function() {
37979    return NProgress.inc(Math.random() * Settings.trickleRate);
37980  };
37981
37982  /**
37983   * Waits for all supplied jQuery promises and
37984   * increases the progress as the promises resolve.
37985   *
37986   * @param $promise jQUery Promise
37987   */
37988  (function() {
37989    var initial = 0, current = 0;
37990
37991    NProgress.promise = function($promise) {
37992      if (!$promise || $promise.state() === "resolved") {
37993        return this;
37994      }
37995
37996      if (current === 0) {
37997        NProgress.start();
37998      }
37999
38000      initial++;
38001      current++;
38002
38003      $promise.always(function() {
38004        current--;
38005        if (current === 0) {
38006            initial = 0;
38007            NProgress.done();
38008        } else {
38009            NProgress.set((initial - current) / initial);
38010        }
38011      });
38012
38013      return this;
38014    };
38015
38016  })();
38017
38018  /**
38019   * (Internal) renders the progress bar markup based on the `template`
38020   * setting.
38021   */
38022
38023  NProgress.render = function(fromStart) {
38024    if (NProgress.isRendered()) return document.getElementById('nprogress');
38025
38026    addClass(document.documentElement, 'nprogress-busy');
38027
38028    var progress = document.createElement('div');
38029    progress.id = 'nprogress';
38030    progress.innerHTML = Settings.template;
38031
38032    var bar      = progress.querySelector(Settings.barSelector),
38033        perc     = fromStart ? '-100' : toBarPerc(NProgress.status || 0),
38034        parent   = document.querySelector(Settings.parent),
38035        spinner;
38036
38037    css(bar, {
38038      transition: 'all 0 linear',
38039      transform: 'translate3d(' + perc + '%,0,0)'
38040    });
38041
38042    if (!Settings.showSpinner) {
38043      spinner = progress.querySelector(Settings.spinnerSelector);
38044      spinner && removeElement(spinner);
38045    }
38046
38047    if (parent != document.body) {
38048      addClass(parent, 'nprogress-custom-parent');
38049    }
38050
38051    parent.appendChild(progress);
38052    return progress;
38053  };
38054
38055  /**
38056   * Removes the element. Opposite of render().
38057   */
38058
38059  NProgress.remove = function() {
38060    removeClass(document.documentElement, 'nprogress-busy');
38061    removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent');
38062    var progress = document.getElementById('nprogress');
38063    progress && removeElement(progress);
38064  };
38065
38066  /**
38067   * Checks if the progress bar is rendered.
38068   */
38069
38070  NProgress.isRendered = function() {
38071    return !!document.getElementById('nprogress');
38072  };
38073
38074  /**
38075   * Determine which positioning CSS rule to use.
38076   */
38077
38078  NProgress.getPositioningCSS = function() {
38079    // Sniff on document.body.style
38080    var bodyStyle = document.body.style;
38081
38082    // Sniff prefixes
38083    var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' :
38084                       ('MozTransform' in bodyStyle) ? 'Moz' :
38085                       ('msTransform' in bodyStyle) ? 'ms' :
38086                       ('OTransform' in bodyStyle) ? 'O' : '';
38087
38088    if (vendorPrefix + 'Perspective' in bodyStyle) {
38089      // Modern browsers with 3D support, e.g. Webkit, IE10
38090      return 'translate3d';
38091    } else if (vendorPrefix + 'Transform' in bodyStyle) {
38092      // Browsers without 3D support, e.g. IE9
38093      return 'translate';
38094    } else {
38095      // Browsers without translate() support, e.g. IE7-8
38096      return 'margin';
38097    }
38098  };
38099
38100  /**
38101   * Helpers
38102   */
38103
38104  function clamp(n, min, max) {
38105    if (n < min) return min;
38106    if (n > max) return max;
38107    return n;
38108  }
38109
38110  /**
38111   * (Internal) converts a percentage (`0..1`) to a bar translateX
38112   * percentage (`-100%..0%`).
38113   */
38114
38115  function toBarPerc(n) {
38116    return (-1 + n) * 100;
38117  }
38118
38119
38120  /**
38121   * (Internal) returns the correct CSS for changing the bar's
38122   * position given an n percentage, and speed and ease from Settings
38123   */
38124
38125  function barPositionCSS(n, speed, ease) {
38126    var barCSS;
38127
38128    if (Settings.positionUsing === 'translate3d') {
38129      barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' };
38130    } else if (Settings.positionUsing === 'translate') {
38131      barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' };
38132    } else {
38133      barCSS = { 'margin-left': toBarPerc(n)+'%' };
38134    }
38135
38136    barCSS.transition = 'all '+speed+'ms '+ease;
38137
38138    return barCSS;
38139  }
38140
38141  /**
38142   * (Internal) Queues a function to be executed.
38143   */
38144
38145  var queue = (function() {
38146    var pending = [];
38147
38148    function next() {
38149      var fn = pending.shift();
38150      if (fn) {
38151        fn(next);
38152      }
38153    }
38154
38155    return function(fn) {
38156      pending.push(fn);
38157      if (pending.length == 1) next();
38158    };
38159  })();
38160
38161  /**
38162   * (Internal) Applies css properties to an element, similar to the jQuery
38163   * css method.
38164   *
38165   * While this helper does assist with vendor prefixed property names, it
38166   * does not perform any manipulation of values prior to setting styles.
38167   */
38168
38169  var css = (function() {
38170    var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ],
38171        cssProps    = {};
38172
38173    function camelCase(string) {
38174      return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) {
38175        return letter.toUpperCase();
38176      });
38177    }
38178
38179    function getVendorProp(name) {
38180      var style = document.body.style;
38181      if (name in style) return name;
38182
38183      var i = cssPrefixes.length,
38184          capName = name.charAt(0).toUpperCase() + name.slice(1),
38185          vendorName;
38186      while (i--) {
38187        vendorName = cssPrefixes[i] + capName;
38188        if (vendorName in style) return vendorName;
38189      }
38190
38191      return name;
38192    }
38193
38194    function getStyleProp(name) {
38195      name = camelCase(name);
38196      return cssProps[name] || (cssProps[name] = getVendorProp(name));
38197    }
38198
38199    function applyCss(element, prop, value) {
38200      prop = getStyleProp(prop);
38201      element.style[prop] = value;
38202    }
38203
38204    return function(element, properties) {
38205      var args = arguments,
38206          prop,
38207          value;
38208
38209      if (args.length == 2) {
38210        for (prop in properties) {
38211          value = properties[prop];
38212          if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value);
38213        }
38214      } else {
38215        applyCss(element, args[1], args[2]);
38216      }
38217    }
38218  })();
38219
38220  /**
38221   * (Internal) Determines if an element or space separated list of class names contains a class name.
38222   */
38223
38224  function hasClass(element, name) {
38225    var list = typeof element == 'string' ? element : classList(element);
38226    return list.indexOf(' ' + name + ' ') >= 0;
38227  }
38228
38229  /**
38230   * (Internal) Adds a class to an element.
38231   */
38232
38233  function addClass(element, name) {
38234    var oldList = classList(element),
38235        newList = oldList + name;
38236
38237    if (hasClass(oldList, name)) return;
38238
38239    // Trim the opening space.
38240    element.className = newList.substring(1);
38241  }
38242
38243  /**
38244   * (Internal) Removes a class from an element.
38245   */
38246
38247  function removeClass(element, name) {
38248    var oldList = classList(element),
38249        newList;
38250
38251    if (!hasClass(element, name)) return;
38252
38253    // Replace the class name.
38254    newList = oldList.replace(' ' + name + ' ', ' ');
38255
38256    // Trim the opening and closing spaces.
38257    element.className = newList.substring(1, newList.length - 1);
38258  }
38259
38260  /**
38261   * (Internal) Gets a space separated list of the class names on the element.
38262   * The list is wrapped with a single space on each end to facilitate finding
38263   * matches within the list.
38264   */
38265
38266  function classList(element) {
38267    return (' ' + (element.className || '') + ' ').replace(/\s+/gi, ' ');
38268  }
38269
38270  /**
38271   * (Internal) Removes an element from the DOM.
38272   */
38273
38274  function removeElement(element) {
38275    element && element.parentNode && element.parentNode.removeChild(element);
38276  }
38277
38278  return NProgress;
38279});
38280(function() {
38281  App.Widgets.ProgressBar = (function() {
38282    function ProgressBar() {}
38283
38284    ProgressBar.prototype.initialize = function() {
38285      this.bindEvents();
38286      return this.configProgress();
38287    };
38288
38289    ProgressBar.prototype.bindEvents = function() {
38290      App.events.subscribe('app:progress:start', this.startProgress);
38291      return App.events.subscribe('app:progress:finish', this.finishProgress);
38292    };
38293
38294    ProgressBar.prototype.configProgress = function() {
38295      return NProgress.configure({
38296        ease: 'ease',
38297        trickleSpeed: 100
38298      });
38299    };
38300
38301    ProgressBar.prototype.startProgress = function() {
38302      return NProgress.start();
38303    };
38304
38305    ProgressBar.prototype.finishProgress = function() {
38306      return NProgress.done();
38307    };
38308
38309    return ProgressBar;
38310
38311  })();
38312
38313}).call(this);
38314(function() {
38315  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
38316
38317  App.Widgets.ReportContent = (function() {
38318    function ReportContent() {
38319      this.sendReport = bind(this.sendReport, this);
38320    }
38321
38322    ReportContent.prototype.initialize = function() {
38323      this.$el = $('#modal-report-content');
38324      return this.bindEvents();
38325    };
38326
38327    ReportContent.prototype.bindEvents = function() {
38328      $('.report-content').on('click', (function(_this) {
38329        return function(e) {
38330          return _this.btn = e.target;
38331        };
38332      })(this));
38333      return this.$el.find('#report-content').on('click', this.sendReport);
38334    };
38335
38336    ReportContent.prototype.sendReport = function() {
38337      var data;
38338      data = {
38339        report: {
38340          category: this.$el.find('#report-category').val(),
38341          report: this.$el.find('#report-content-message').val(),
38342          content_type: $(this.btn).data('report-type'),
38343          content_id: $(this.btn).data('report-id')
38344        }
38345      };
38346      return $.post("/api/internal/content_reports", data, (function(_this) {
38347        return function(response) {
38348          if (response.success) {
38349            App.events.publish("app:notification:flash:success", response.message);
38350            return _this.$el.makeMeModal('close');
38351          } else {
38352            return App.events.publish("app:notification:flash:error", response.message);
38353          }
38354        };
38355      })(this));
38356    };
38357
38358    return ReportContent;
38359
38360  })();
38361
38362}).call(this);
38363(function() {
38364  Cookies.set('screen_width', window.innerWidth);
38365
38366}).call(this);
38367(function() {
38368  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
38369
38370  App.Widgets.SearchItems = (function() {
38371    function SearchItems() {
38372      this.onSearchFormSubmit = bind(this.onSearchFormSubmit, this);
38373      this.onFieldClick = bind(this.onFieldClick, this);
38374      this.onLostFocus = bind(this.onLostFocus, this);
38375      this.onFieldFocus = bind(this.onFieldFocus, this);
38376      this.onSearchResults = bind(this.onSearchResults, this);
38377      this.updateActiveElementOnHover = bind(this.updateActiveElementOnHover, this);
38378      this.onSearchResultsHover = bind(this.onSearchResultsHover, this);
38379      this.onArrowNavigation = bind(this.onArrowNavigation, this);
38380      this.onSearchFieldChange = bind(this.onSearchFieldChange, this);
38381      this.updateLastKeyPressed = bind(this.updateLastKeyPressed, this);
38382    }
38383
38384    SearchItems.prototype.endPoint = '/api/internal/items/live_search.json';
38385
38386    SearchItems.prototype.lastKeyPressed = null;
38387
38388    SearchItems.prototype.activeElementIndex = null;
38389
38390    SearchItems.prototype.enterKeyCode = 13;
38391
38392    SearchItems.prototype.leftArrowKeyCode = 37;
38393
38394    SearchItems.prototype.upArrowKeyCode = 38;
38395
38396    SearchItems.prototype.rightArrowKeyCode = 39;
38397
38398    SearchItems.prototype.downArrowKeyCode = 40;
38399
38400    SearchItems.prototype.formerKeywords = '';
38401
38402    SearchItems.prototype.options = {
38403      threshold: 200,
38404      minLength: 1,
38405      animationSpeed: 100
38406    };
38407
38408    SearchItems.prototype.initialize = function() {
38409      var onSearchFieldChangeThrottled;
38410      this.$productListWrapper = $('#products-list .content-box-wrapper');
38411      this.elements = {
38412        $searchContainer: $('.site-search'),
38413        $searchSuggestions: $('.js-search-suggestions'),
38414        $searchField: $('.js-search-suggestion-trigger'),
38415        $searchResultsContainer: $('.js-search-results-container'),
38416        $searchResults: $('.js-search-results'),
38417        $queries: $('.js-search-top-queries'),
38418        $searchForm: $('#search-form')
38419      };
38420      this.elements.$searchSuggestions.hide().removeClass('is-hidden');
38421      this.elements.$searchResultsContainer.hide().removeClass('is-hidden');
38422      onSearchFieldChangeThrottled = _.throttle(this.onSearchFieldChange, this.options.threshold);
38423      this.elements.$searchField.on('keydown', this.updateLastKeyPressed);
38424      this.elements.$searchField.on('keyup', onSearchFieldChangeThrottled);
38425      this.elements.$searchField.on('focus', this.onFieldFocus);
38426      this.elements.$searchField.on('click', this.onFieldClick);
38427      this.elements.$searchSuggestions.hideOnOutsideClick(this.onLostFocus);
38428      this.elements.$searchForm.submit(this.onSearchFormSubmit);
38429      return App.events.subscribe('browser:filters:changed', (function(_this) {
38430        return function() {
38431          return _this.hideSuggestions();
38432        };
38433      })(this));
38434    };
38435
38436    SearchItems.prototype.updateLastKeyPressed = function(e) {
38437      var $activeElementLink, newKeyword;
38438      this.lastKeyPressed = e.keyCode;
38439      if (this.lastKeyPressed === this.upArrowKeyCode || this.lastKeyPressed === this.downArrowKeyCode) {
38440        e.preventDefault();
38441        this.onArrowNavigation();
38442      }
38443      if (this.lastKeyPressed === this.enterKeyCode && this.elements.$searchResults.find('li.is-active').length !== 0) {
38444        e.preventDefault();
38445        $activeElementLink = this.elements.$searchResults.find('li.is-active a');
38446        if (App.Backbone.Routers.ItemsSearch.instance) {
38447          newKeyword = $activeElementLink.data('keyword');
38448          return App.Backbone.Routers.ItemsSearch.instance.applyFilter('keywords', newKeyword, {
38449            reload: true
38450          });
38451        } else {
38452          return window.location = $activeElementLink.attr('href');
38453        }
38454      }
38455    };
38456
38457    SearchItems.prototype.search = function(query) {
38458      this.elements.$searchContainer.find('.js-search-submit').addClass('is-loading');
38459      this.formerKeywords = query;
38460      return $.get(this.endPoint, {
38461        query: query
38462      }, this.onSearchResults);
38463    };
38464
38465    SearchItems.prototype.showSuggestions = function() {
38466      if (this.elements.$searchField.val().length > 0) {
38467        this.showResults();
38468      } else {
38469        this.showQueries();
38470      }
38471      return this.elements.$searchSuggestions.fadeIn(this.options.animationSpeed);
38472    };
38473
38474    SearchItems.prototype.hideSuggestions = function() {
38475      return this.elements.$searchSuggestions.fadeOut(this.options.animationSpeed);
38476    };
38477
38478    SearchItems.prototype.showQueries = function() {
38479      this.elements.$queries.show();
38480      return this.elements.$searchResultsContainer.hide();
38481    };
38482
38483    SearchItems.prototype.showResults = function() {
38484      this.elements.$queries.hide();
38485      return this.elements.$searchResultsContainer.show();
38486    };
38487
38488    SearchItems.prototype.onSearchFieldChange = function(e) {
38489      var keywords;
38490      keywords = this.elements.$searchField.val();
38491      if (_.contains([this.leftArrowKeyCode, this.upArrowKeyCode, this.rightArrowKeyCode, this.downArrowKeyCode], e.keyCode)) {
38492        return;
38493      }
38494      if (this.formerKeywords === keywords) {
38495        return;
38496      }
38497      if (keywords.length >= this.options.minLength) {
38498        return this.search(keywords);
38499      } else if (keywords.length === 0) {
38500        this.formerKeywords = '';
38501        return this.showQueries();
38502      } else {
38503        return this.hideSuggestions();
38504      }
38505    };
38506
38507    SearchItems.prototype.onArrowNavigation = function() {
38508      this.$activeElement = this.elements.$searchResults.find('li.is-active');
38509      this.activeElementIndex = this.$activeElement.index();
38510      this.lastElementIndex = this.elements.$searchResults.find('li').length - 1;
38511      if (this.lastKeyPressed === this.downArrowKeyCode) {
38512        this.activeElementIndex += 1;
38513      } else if (this.$activeElement.length === 0) {
38514        this.activeElementIndex = this.lastElementIndex;
38515      } else {
38516        this.activeElementIndex -= 1;
38517      }
38518      return this.updateActiveElement();
38519    };
38520
38521    SearchItems.prototype.onSearchResultsHover = function() {
38522      return this.elements.$searchResults.find('li').on('mouseenter', this.updateActiveElementOnHover);
38523    };
38524
38525    SearchItems.prototype.updateActiveElementOnHover = function(e) {
38526      this.activeElementIndex = $(e.currentTarget).index();
38527      return this.updateActiveElement();
38528    };
38529
38530    SearchItems.prototype.updateActiveElement = function() {
38531      this.elements.$searchResults.find('li').removeClass('is-active');
38532      if (this.activeElementIndex == null) {
38533        return;
38534      }
38535      return $(this.elements.$searchResults.find('li')[this.activeElementIndex]).addClass('is-active');
38536    };
38537
38538    SearchItems.prototype.onSearchResults = function(phrases) {
38539      var $container, pattern, searchQuery;
38540      this.elements.$searchContainer.find('.js-search-submit').removeClass('is-loading');
38541      if (_.size(phrases) > 0 && this.lastKeyPressed !== this.enterKeyCode) {
38542        this.showSuggestions();
38543      } else {
38544        this.hideSuggestions();
38545      }
38546      searchQuery = this.elements.$searchForm.find('.js-search-suggestion-trigger').val();
38547      searchQuery = searchQuery.replace(/[ \t]+$/, "");
38548      pattern = new RegExp('(' + searchQuery + ')', 'gi');
38549      $container = this.elements.$searchSuggestions.find('.js-search-results');
38550      $container.html('');
38551      _(phrases).forEach(function(phraseDetails) {
38552        var counts, phrase, phraseHtml, view, viewData;
38553        phrase = phraseDetails[0], counts = phraseDetails[1];
38554        phraseHtml = phrase.replace(pattern, '<strong>$&</strong>');
38555        viewData = {
38556          suggestion: phrase,
38557          suggestionHtml: phraseHtml,
38558          cgCount: counts.cg,
38559          lowpolyCount: counts.lowpoly,
38560          total: counts.total,
38561          thumbnailUrl: counts.thumbnailUrl,
38562          abGroup: counts.abGroup
38563        };
38564        view = new App.Backbone.Views.SearchSuggestionViewV2({
38565          data: viewData
38566        });
38567        return view.render().$el.appendTo($container);
38568      });
38569      return this.onSearchResultsHover();
38570    };
38571
38572    SearchItems.prototype.onFieldFocus = function() {
38573      if (this.elements.$searchField.val().length === 0) {
38574        return this.showSuggestions();
38575      }
38576    };
38577
38578    SearchItems.prototype.onLostFocus = function(e) {
38579      if ($(e.target).hasClass('js-search-suggestion-trigger')) {
38580        return;
38581      }
38582      return this.hideSuggestions();
38583    };
38584
38585    SearchItems.prototype.onFieldClick = function() {
38586      this.activeElementIndex = null;
38587      return this.updateActiveElement();
38588    };
38589
38590    SearchItems.prototype.onSearchFormSubmit = function(e) {
38591      if (this.$productListWrapper.length !== 0) {
38592        return this.$productListWrapper.addClass('is-updating-with-spinner spinner--top is-fixed');
38593      }
38594    };
38595
38596    return SearchItems;
38597
38598  })();
38599
38600}).call(this);
38601(function() {
38602  App.Widgets.StoreCreditsInfoTooltip = (function() {
38603    function StoreCreditsInfoTooltip() {}
38604
38605    StoreCreditsInfoTooltip.prototype.initialize = function() {
38606      this.$tooltip = $('.js-new-store-credits-info-tooltip');
38607      if (this.$tooltip.length > 0) {
38608        return this.initTooltip();
38609      }
38610    };
38611
38612    StoreCreditsInfoTooltip.prototype.initTooltip = function() {
38613      var content, storeCreditsComming;
38614      storeCreditsComming = this.$tooltip.data('store-credits-earned');
38615      content = "You will get " + storeCreditsComming + " CGTrader credits if you complete this purchase. <br/>\n<a href=\"https://www.cgtrader.com/pages/cgtrader-loyalty-discounts\" target='_blank'>More information</a>";
38616      return this.$tooltip.tooltipster({
38617        theme: 'tooltip--light tooltip--popover u-text-center',
38618        animation: 'grow',
38619        contentAsHTML: true,
38620        content: content,
38621        interactive: true,
38622        maxWidth: 220
38623      });
38624    };
38625
38626    return StoreCreditsInfoTooltip;
38627
38628  })();
38629
38630}).call(this);
38631(function() {
38632  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
38633
38634  App.Widgets.TipForDesigner = (function() {
38635    function TipForDesigner() {
38636      this.closeTip = bind(this.closeTip, this);
38637    }
38638
38639    TipForDesigner.prototype.initialize = function() {
38640      if ((typeof BarsOrdering !== "undefined" && BarsOrdering !== null) && BarsOrdering('tipForDesigner')) {
38641        return this.showNotificationBar();
38642      }
38643    };
38644
38645    TipForDesigner.prototype.showNotificationBar = function() {
38646      this.$notificationBar = $(JST["tip_for_designer/notification_bar"]({
38647        body: App.config.tipForDesigner
38648      }));
38649      $('body').prepend(this.$notificationBar);
38650      return this.$notificationBar.find('.js-tip-close').on('click', this.closeTip);
38651    };
38652
38653    TipForDesigner.prototype.closeTip = function() {
38654      return this.$notificationBar.hide();
38655    };
38656
38657    return TipForDesigner;
38658
38659  })();
38660
38661}).call(this);
38662(function() {
38663  App.Widgets.TopNavigation = (function() {
38664    function TopNavigation() {}
38665
38666    TopNavigation.prototype.initialize = function() {
38667      if (!($("input[autofocus]").length > 0)) {
38668        return this.autoFocusSearch();
38669      }
38670    };
38671
38672    TopNavigation.prototype.autoFocusSearch = function() {
38673      var currentValue;
38674      if ($('.site-search .site-search__field').length > 1) {
38675        return;
38676      }
38677      currentValue = $('.site-search .site-search__field').val();
38678      return $('.site-search input').focus().val('').val(currentValue);
38679    };
38680
38681    return TopNavigation;
38682
38683  })();
38684
38685}).call(this);
38686(function() {
38687  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
38688
38689  App.Widgets.TranslationsNotice = (function() {
38690    function TranslationsNotice() {
38691      this.handleClose = bind(this.handleClose, this);
38692      this.show = bind(this.show, this);
38693      this.reposition = bind(this.reposition, this);
38694    }
38695
38696    TranslationsNotice.prototype.initialize = function() {
38697      $('.notice').on('click', (function(_this) {
38698        return function() {
38699          return setTimeout(_this.reposition, 50);
38700        };
38701      })(this));
38702      if (I18n.locale !== I18n.defaultLocale) {
38703        this.show();
38704        return this.reposition();
38705      }
38706    };
38707
38708    TranslationsNotice.prototype.reposition = function() {
38709      var highestNoticeHeight;
38710      if (!this.$bar) {
38711        return;
38712      }
38713      highestNoticeHeight = 0;
38714      if (this.bottom == null) {
38715        this.bottom = 0;
38716      }
38717      $('.notice:visible').not('.notice-translation').each((function(_this) {
38718        return function(index, el) {
38719          var height;
38720          height = $(el).outerHeight();
38721          highestNoticeHeight = Math.max(height, highestNoticeHeight);
38722          return _this.bottom = parseInt($(el).css('bottom').replace('px', ''), 10);
38723        };
38724      })(this));
38725      if ($('.actions-bar').css('position') === 'fixed') {
38726        this.bottom += 42;
38727      }
38728      return this.$bar.css('bottom', highestNoticeHeight + this.bottom);
38729    };
38730
38731    TranslationsNotice.prototype.show = function() {
38732      var href;
38733      if ((typeof localStorage !== "undefined" && localStorage !== null ? localStorage.getItem('notice-translation') : void 0) === 'closed') {
38734        return;
38735      }
38736      this.$bar = $(JST["translations_notice/notification_bar"]());
38737      href = $('link[hreflang="en"]').attr('href') || '/';
38738      this.$bar.find('a').attr('href', href);
38739      $('body').append(this.$bar);
38740      return this.$bar.find('.close-notice').on('click', this.handleClose);
38741    };
38742
38743    TranslationsNotice.prototype.handleClose = function() {
38744      this.$bar.remove();
38745      return typeof localStorage !== "undefined" && localStorage !== null ? localStorage.setItem('notice-translation', 'closed') : void 0;
38746    };
38747
38748    return TranslationsNotice;
38749
38750  })();
38751
38752}).call(this);
38753(function() {
38754  App.Widgets.UserSubscriptionsModal = (function() {
38755    function UserSubscriptionsModal() {}
38756
38757    UserSubscriptionsModal.prototype.initialize = function() {
38758      if (App.config.showUserSubscriptionsModal) {
38759        return this.showUserSubscriptionsModal();
38760      }
38761    };
38762
38763    UserSubscriptionsModal.prototype.showUserSubscriptionsModal = function() {
38764      if (this.userSubscriptionsModal == null) {
38765        this.userSubscriptionsModal = new App.Backbone.Views.Modals.UserSubscriptionsView();
38766      }
38767      return this.userSubscriptionsModal.render();
38768    };
38769
38770    return UserSubscriptionsModal;
38771
38772  })();
38773
38774}).call(this);
38775(function() {
38776  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
38777
38778  App.Widgets.V2WishLists = (function() {
38779    function V2WishLists() {
38780      this.wishListId = bind(this.wishListId, this);
38781      this.createDefaultWishList = bind(this.createDefaultWishList, this);
38782      this.onCreateListItemFailure = bind(this.onCreateListItemFailure, this);
38783      this.addToFirstWishList = bind(this.addToFirstWishList, this);
38784      this.addToWishList = bind(this.addToWishList, this);
38785      this.onAddClick = bind(this.onAddClick, this);
38786    }
38787
38788    V2WishLists.prototype.initialize = function() {
38789      var button, i, len, ref, results;
38790      this.$addToWishListButtons = $('.js-add-to-wish-list');
38791      ref = this.$addToWishListButtons;
38792      results = [];
38793      for (i = 0, len = ref.length; i < len; i++) {
38794        button = ref[i];
38795        results.push((function(_this) {
38796          return function(button) {
38797            var buttonInstance;
38798            buttonInstance = $(button);
38799            return buttonInstance.on("click", function() {
38800              return _this.onAddClick(buttonInstance);
38801            });
38802          };
38803        })(this)(button));
38804      }
38805      return results;
38806    };
38807
38808    V2WishLists.prototype.onAddClick = function(buttonInstance) {
38809      if (!App.config.userSignedIn) {
38810        return;
38811      }
38812      if (this.wishLists == null) {
38813        this.wishLists = new App.Backbone.Collections.WishListsCollection();
38814      }
38815      return this.wishLists.fetch({
38816        reset: true,
38817        success: (function(_this) {
38818          return function(collection, response) {
38819            return _this.addToWishList(buttonInstance, collection, response);
38820          };
38821        })(this)
38822      });
38823    };
38824
38825    V2WishLists.prototype.addToWishList = function(buttonInstance, collection, response) {
38826      buttonInstance.closest('.cgt-item-card').addClass('is-updating');
38827      buttonInstance.closest('.js-cart-item').addClass('is-updating');
38828      if (response.data && response.data.id) {
38829        return this.addToFirstWishList(buttonInstance);
38830      } else {
38831        return this.createDefaultWishList();
38832      }
38833    };
38834
38835    V2WishLists.prototype.addToFirstWishList = function(buttonInstance) {
38836      var itemId, wishListItem;
38837      itemId = buttonInstance.data('item-id');
38838      wishListItem = new App.Backbone.Models.WishListItem({
38839        data: {
38840          type: 'wishListItems',
38841          attributes: {
38842            wish_list_id: this.wishListId(),
38843            item_id: itemId
38844          }
38845        }
38846      });
38847      return wishListItem.save({}, {
38848        error: (function(_this) {
38849          return function(model, response) {
38850            return _this.onCreateListItemFailure(model, response, buttonInstance);
38851          };
38852        })(this),
38853        success: (function(_this) {
38854          return function() {
38855            return _this.onCreateWishListItemSuccess(buttonInstance);
38856          };
38857        })(this)
38858      });
38859    };
38860
38861    V2WishLists.prototype.onCreateWishListItemSuccess = function(buttonInstance) {
38862      var path;
38863      path = "/api/internal/" + (buttonInstance.data('item-type')) + "/items/" + (buttonInstance.data('item-id')) + "/cart_items/" + (buttonInstance.data('cart-item-id'));
38864      return $.ajax({
38865        url: path,
38866        type: 'DELETE',
38867        success: function() {
38868          App.events.publish("app:notification:flash:success", 'Model was successfully added to wishlist!');
38869          return window.location.reload();
38870        }
38871      });
38872    };
38873
38874    V2WishLists.prototype.onCreateListItemFailure = function(model, response, buttonInstance) {
38875      App.events.publish("app:notification:flash:error", this.parseErrorMessages(response.responseJSON.errors));
38876      return buttonInstance.closest('.cgt-item-card').removeClass('is-updating');
38877    };
38878
38879    V2WishLists.prototype.createDefaultWishList = function() {
38880      return this.wishLists.create({
38881        data: {
38882          type: 'wishLists',
38883          attributes: {
38884            title: 'Default'
38885          }
38886        }
38887      }, {
38888        success: this.addToFirstWishList
38889      });
38890    };
38891
38892    V2WishLists.prototype.wishListId = function() {
38893      return this.wishLists.first().id || this.wishLists.first().attributes.data.id;
38894    };
38895
38896    V2WishLists.prototype.parseErrorMessages = function(errors) {
38897      var attribute, error, errorMessages, parts;
38898      errorMessages = [];
38899      for (error in errors) {
38900        parts = errors[error].source.pointer.split('/');
38901        attribute = parts[parts.length - 1];
38902        errorMessages.push(attribute + " " + errors[error].detail);
38903      }
38904      return errorMessages.join('.');
38905    };
38906
38907    return V2WishLists;
38908
38909  })();
38910
38911}).call(this);
38912(function() {
38913  App.Widgets.VatTaxTooltip = (function() {
38914    function VatTaxTooltip() {}
38915
38916    VatTaxTooltip.prototype.initialize = function() {
38917      this.$tooltip = $('.js-vat-tax-tooltip');
38918      if (this.$tooltip.length > 0) {
38919        return this.initTooltip();
38920      }
38921    };
38922
38923    VatTaxTooltip.prototype.initTooltip = function() {
38924      var content;
38925      content = "Value Added Tax (VAT), also known as Sales tax or Goods <br/>\nand services tax (GST), is a tax applied at the point of sale <br/>\nto the end buyer and paid to the government of the buyer’s country.";
38926      return this.$tooltip.tooltipster({
38927        theme: 'tooltip--light tooltip--popover u-text-center',
38928        animation: 'grow',
38929        contentAsHTML: true,
38930        content: content,
38931        position: 'left',
38932        interactive: true,
38933        maxWidth: 400
38934      });
38935    };
38936
38937    return VatTaxTooltip;
38938
38939  })();
38940
38941}).call(this);
38942(function() {
38943  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
38944
38945  App.Widgets.WishLists = (function() {
38946    function WishLists() {
38947      this.wishListId = bind(this.wishListId, this);
38948      this.createDefaultWishList = bind(this.createDefaultWishList, this);
38949      this.onCreateListItemFailure = bind(this.onCreateListItemFailure, this);
38950      this.onCreateWishListItemSuccess = bind(this.onCreateWishListItemSuccess, this);
38951      this.addToFirstWishList = bind(this.addToFirstWishList, this);
38952      this.addToWishList = bind(this.addToWishList, this);
38953      this.onRemoveClick = bind(this.onRemoveClick, this);
38954      this.onAddClick = bind(this.onAddClick, this);
38955    }
38956
38957    WishLists.prototype.initialize = function() {
38958      this.$addToWishListButton = $('.js-add-to-wish-list');
38959      this.$removeFromWishListButton = $('.js-remove-from-wish-list');
38960      this.$removeFromWishListButton.on('click', this.onRemoveClick);
38961      this.$addToWishListButton.on('click', this.onAddClick);
38962      if (gon.in_the_wish_list) {
38963        return this.$removeFromWishListButton.removeClass('is-hidden');
38964      } else {
38965        return this.$addToWishListButton.removeClass('is-hidden');
38966      }
38967    };
38968
38969    WishLists.prototype.onAddClick = function() {
38970      if (!App.config.userSignedIn) {
38971        return;
38972      }
38973      if (this.wishLists == null) {
38974        this.wishLists = new App.Backbone.Collections.WishListsCollection();
38975      }
38976      return this.wishLists.fetch({
38977        reset: true,
38978        success: this.addToWishList
38979      });
38980    };
38981
38982    WishLists.prototype.onRemoveClick = function() {
38983      if (!App.config.userSignedIn) {
38984        return;
38985      }
38986      return $.ajax({
38987        url: "/api/internal/wish-lists/" + (this.$removeFromWishListButton.data('wish-list-id')) + "/items/0",
38988        type: 'DELETE',
38989        data: {
38990          item_ids: this.$removeFromWishListButton.data('item-id')
38991        },
38992        success: (function(_this) {
38993          return function() {
38994            App.events.publish("app:notification:flash:success", 'Model was successfully removed from wishlist!');
38995            return _this.updateRemoveLinks();
38996          };
38997        })(this)
38998      });
38999    };
39000
39001    WishLists.prototype.addToWishList = function(collection, response) {
39002      if (response.data.length > 0) {
39003        return this.addToFirstWishList();
39004      } else {
39005        return this.createDefaultWishList();
39006      }
39007    };
39008
39009    WishLists.prototype.addToFirstWishList = function() {
39010      var itemId, wishListItem;
39011      itemId = this.$addToWishListButton.data('item-id');
39012      wishListItem = new App.Backbone.Models.WishListItem({
39013        data: {
39014          type: 'wishListItems',
39015          attributes: {
39016            wish_list_id: this.wishListId(),
39017            item_id: itemId
39018          }
39019        }
39020      });
39021      return wishListItem.save({}, {
39022        success: this.onCreateWishListItemSuccess,
39023        error: this.onCreateListItemFailure
39024      });
39025    };
39026
39027    WishLists.prototype.onCreateWishListItemSuccess = function() {
39028      this.$removeFromWishListButton.data('wish-list-id', this.wishListId());
39029      App.events.publish("app:notification:flash:success", 'Model was successfully added to wishlist!');
39030      return this.updateCreateLinks();
39031    };
39032
39033    WishLists.prototype.onCreateListItemFailure = function(model, response) {
39034      return App.events.publish("app:notification:flash:error", this.parseErrorMessages(response.responseJSON.errors));
39035    };
39036
39037    WishLists.prototype.updateCreateLinks = function() {
39038      this.$addToWishListButton.addClass('is-hidden');
39039      return this.$removeFromWishListButton.removeClass('is-hidden');
39040    };
39041
39042    WishLists.prototype.updateRemoveLinks = function() {
39043      this.$removeFromWishListButton.addClass('is-hidden');
39044      return this.$addToWishListButton.removeClass('is-hidden');
39045    };
39046
39047    WishLists.prototype.createDefaultWishList = function() {
39048      return this.wishLists.create({
39049        data: {
39050          type: 'wishLists',
39051          attributes: {
39052            title: 'Default'
39053          }
39054        }
39055      }, {
39056        success: this.addToFirstWishList
39057      });
39058    };
39059
39060    WishLists.prototype.wishListId = function() {
39061      return this.wishLists.first().id || this.wishLists.first().attributes.data.id;
39062    };
39063
39064    WishLists.prototype.parseErrorMessages = function(errors) {
39065      var attribute, error, errorMessages, parts;
39066      errorMessages = [];
39067      for (error in errors) {
39068        parts = errors[error].source.pointer.split('/');
39069        attribute = parts[parts.length - 1];
39070        errorMessages.push(attribute.capitalizeFirst + " " + errors[error].detail);
39071      }
39072      return errorMessages.join('.');
39073    };
39074
39075    return WishLists;
39076
39077  })();
39078
39079}).call(this);
39080(function() {
39081  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
39082    hasProp = {}.hasOwnProperty;
39083
39084  App.Pages.ApiLandingPageDevelopers = (function(superClass) {
39085    extend(ApiLandingPageDevelopers, superClass);
39086
39087    function ApiLandingPageDevelopers() {
39088      return ApiLandingPageDevelopers.__super__.constructor.apply(this, arguments);
39089    }
39090
39091    ApiLandingPageDevelopers.prototype.initialize = function() {
39092      this.$form = $('#api-access-request');
39093      return this.initValidation();
39094    };
39095
39096    ApiLandingPageDevelopers.prototype.validationSettings = function() {
39097      return {
39098        rules: {
39099          'api_access_request[name]': {
39100            required: true
39101          },
39102          'api_access_request[email]': {
39103            required: true,
39104            email: true
39105          },
39106          'api_access_request[use_case]': {
39107            required: true
39108          }
39109        }
39110      };
39111    };
39112
39113    ApiLandingPageDevelopers.prototype.initValidation = function() {
39114      return this.$form.validate(this.validationSettings());
39115    };
39116
39117    return ApiLandingPageDevelopers;
39118
39119  })(App.Core.Page);
39120
39121}).call(this);
39122(function() {
39123  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
39124    hasProp = {}.hasOwnProperty;
39125
39126  App.Pages.ArsenalIndex = (function(superClass) {
39127    extend(ArsenalIndex, superClass);
39128
39129    function ArsenalIndex() {
39130      return ArsenalIndex.__super__.constructor.apply(this, arguments);
39131    }
39132
39133    ArsenalIndex.prototype.initialize = function() {
39134      this.bindEvents();
39135      return $(document).ready((function(_this) {
39136        return function() {
39137          var hash, i, len, results, sectionHashes;
39138          sectionHashes = [
39139            {
39140              link: '#lets-talk',
39141              anchor: '#lets-talk'
39142            }, {
39143              link: '#arsenal-pricing',
39144              anchor: '#arsenal-pricing-anchor'
39145            }, {
39146              link: '#fatboy',
39147              anchor: '#fatboy-anchor'
39148            }, {
39149              link: '#garten-und-freizeit',
39150              anchor: '#garten-und-freizeit-anchor'
39151            }
39152          ];
39153          results = [];
39154          for (i = 0, len = sectionHashes.length; i < len; i++) {
39155            hash = sectionHashes[i];
39156            if (window.location.hash === hash.link) {
39157              results.push(_this.onLinkClick(null, 2000, hash.anchor));
39158            } else {
39159              results.push(void 0);
39160            }
39161          }
39162          return results;
39163        };
39164      })(this));
39165    };
39166
39167    ArsenalIndex.prototype.bindEvents = function() {
39168      return $('.js-arsenal-scroll').on('click', (function(_this) {
39169        return function(e) {
39170          return _this.onLinkClick(e, 2000, '#lets-talk');
39171        };
39172      })(this));
39173    };
39174
39175    ArsenalIndex.prototype.onLinkClick = function(e, timeout, target) {
39176      if (e) {
39177        e.preventDefault();
39178      }
39179      return $([document.documentElement, document.body]).animate({
39180        scrollTop: $(target).offset().top
39181      }, timeout);
39182    };
39183
39184    return ArsenalIndex;
39185
39186  })(App.Core.Page);
39187
39188}).call(this);
39189/*!
vendor: 5,735 bytes, lines 39190-39402
39190 * fancyBox - jQuery Plugin
39191 * version: 2.1.5 (Fri, 14 Jun 2013)
39192 * @requires jQuery v1.6 or later
39193 *
39194 * Examples at http://fancyapps.com/fancybox/
39195 * License: www.fancyapps.com/fancybox/#license
39196 *
39197 * Copyright 2012 Janis Skarnelis - [email protected]
39198 *
39199 */
39200
39201
39202(function (window, document, $, undefined) {
39203	"use strict";
39204
39205	var H = $("html"),
39206		W = $(window),
39207		D = $(document),
39208		F = $.fancybox = function () {
39209			F.open.apply( this, arguments );
39210		},
39211		IE =  navigator.userAgent.match(/msie/i),
39212		didUpdate	= null,
39213		isTouch		= document.createTouch !== undefined,
39214
39215		isQuery	= function(obj) {
39216			return obj && obj.hasOwnProperty && obj instanceof $;
39217		},
39218		isString = function(str) {
39219			return str && $.type(str) === "string";
39220		},
39221		isPercentage = function(str) {
39222			return isString(str) && str.indexOf('%') > 0;
39223		},
39224		isScrollable = function(el) {
39225			return (el && !(el.style.overflow && el.style.overflow === 'hidden') && ((el.clientWidth && el.scrollWidth > el.clientWidth) || (el.clientHeight && el.scrollHeight > el.clientHeight)));
39226		},
39227		getScalar = function(orig, dim) {
39228			var value = parseInt(orig, 10) || 0;
39229
39230			if (dim && isPercentage(orig)) {
39231				value = F.getViewport()[ dim ] / 100 * value;
39232			}
39233
39234			return Math.ceil(value);
39235		},
39236		getValue = function(value, dim) {
39237			return getScalar(value, dim) + 'px';
39238		};
39239
39240	$.extend(F, {
39241		// The current version of fancyBox
39242		version: '2.1.5',
39243
39244		defaults: {
39245			padding : 15,
39246			margin  : 20,
39247
39248			width     : 800,
39249			height    : 600,
39250			minWidth  : 100,
39251			minHeight : 100,
39252			maxWidth  : 9999,
39253			maxHeight : 9999,
39254			pixelRatio: 1, // Set to 2 for retina display support
39255
39256			autoSize   : true,
39257			autoHeight : false,
39258			autoWidth  : false,
39259
39260			autoResize  : true,
39261			autoCenter  : !isTouch,
39262			fitToView   : true,
39263			aspectRatio : false,
39264			topRatio    : 0.5,
39265			leftRatio   : 0.5,
39266
39267			scrolling : 'auto', // 'auto', 'yes' or 'no'
39268			wrapCSS   : '',
39269
39270			arrows     : true,
39271			closeBtn   : true,
39272			closeClick : false,
39273			nextClick  : false,
39274			mouseWheel : true,
39275			autoPlay   : false,
39276			playSpeed  : 3000,
39277			preload    : 3,
39278			modal      : false,
39279			loop       : true,
39280
39281			ajax  : {
39282				dataType : 'html',
39283				headers  : { 'X-fancyBox': true }
39284			},
39285			iframe : {
39286				scrolling : 'auto',
39287				preload   : true
39288			},
39289			swf : {
39290				wmode: 'transparent',
39291				allowfullscreen   : 'true',
39292				allowscriptaccess : 'always'
39293			},
39294
39295			keys  : {
39296				next : {
39297					13 : 'left', // enter
39298					34 : 'up',   // page down
39299					39 : 'left', // right arrow
39300					40 : 'up'    // down arrow
39301				},
39302				prev : {
39303					8  : 'right',  // backspace
39304					33 : 'down',   // page up
39305					37 : 'right',  // left arrow
39306					38 : 'down'    // up arrow
39307				},
39308				close  : [27], // escape key
39309				play   : [32], // space - start/stop slideshow
39310				toggle : [70]  // letter "f" - toggle fullscreen
39311			},
39312
39313			direction : {
39314				next : 'left',
39315				prev : 'right'
39316			},
39317
39318			scrollOutside  : true,
39319
39320			// Override some properties
39321			index   : 0,
39322			type    : null,
39323			href    : null,
39324			content : null,
39325			title   : null,
39326
39327			// HTML templates
39328			tpl: {
39329				wrap     : '<div class="fancybox-wrap" tabIndex="-1"><div class="fancybox-skin"><div class="fancybox-outer"><div class="fancybox-inner"></div></div></div></div>',
39330				image    : '<img class="fancybox-image" src="{href}" alt="" />',
39331				iframe   : '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" frameborder="0" vspace="0" hspace="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen' + (IE ? ' allowtransparency="true"' : '') + '></iframe>',
39332				error    : '<p class="fancybox-error">The requested content cannot be loaded.<br/>Please try again later.</p>',
39333				closeBtn : '<a title="Close" class="fancybox-item fancybox-close" href="javascript:;"></a>',
39334				next     : '<a title="Next" class="fancybox-nav fancybox-next" href="javascript:;"><span></span></a>',
39335				prev     : '<a title="Previous" class="fancybox-nav fancybox-prev" href="javascript:;"><span></span></a>'
39336			},
39337
39338			// Properties for each animation type
39339			// Opening fancyBox
39340			openEffect  : 'fade', // 'elastic', 'fade' or 'none'
39341			openSpeed   : 250,
39342			openEasing  : 'swing',
39343			openOpacity : true,
39344			openMethod  : 'zoomIn',
39345
39346			// Closing fancyBox
39347			closeEffect  : 'fade', // 'elastic', 'fade' or 'none'
39348			closeSpeed   : 250,
39349			closeEasing  : 'swing',
39350			closeOpacity : true,
39351			closeMethod  : 'zoomOut',
39352
39353			// Changing next gallery item
39354			nextEffect : 'elastic', // 'elastic', 'fade' or 'none'
39355			nextSpeed  : 250,
39356			nextEasing : 'swing',
39357			nextMethod : 'changeIn',
39358
39359			// Changing previous gallery item
39360			prevEffect : 'elastic', // 'elastic', 'fade' or 'none'
39361			prevSpeed  : 250,
39362			prevEasing : 'swing',
39363			prevMethod : 'changeOut',
39364
39365			// Enable default helpers
39366			helpers : {
39367				overlay : true,
39368				title   : true
39369			},
39370
39371			// Callbacks
39372			onCancel     : $.noop, // If canceling
39373			beforeLoad   : $.noop, // Before loading
39374			afterLoad    : $.noop, // After loading
39375			beforeShow   : $.noop, // Before changing in current item
39376			afterShow    : $.noop, // After opening
39377			beforeChange : $.noop, // Before changing gallery item
39378			beforeClose  : $.noop, // Before closing
39379			afterClose   : $.noop  // After closing
39380		},
39381
39382		//Current state
39383		group    : {}, // Selected group
39384		opts     : {}, // Group options
39385		previous : null,  // Previous element
39386		coming   : null,  // Element being loaded
39387		current  : null,  // Currently loaded element
39388		isActive : false, // Is activated
39389		isOpen   : false, // Is currently open
39390		isOpened : false, // Have been fully opened at least once
39391
39392		wrap  : null,
39393		skin  : null,
39394		outer : null,
39395		inner : null,
39396
39397		player : {
39398			timer    : null,
39399			isActive : false
39400		},
39401
39402		// Loaders
vendor: 12,016 bytes, lines 39403-39950
39403		ajaxLoad   : null,
39404		imgPreload : null,
39405
39406		// Some collections
39407		transitions : {},
39408		helpers     : {},
39409
39410		/*
39411		 *	Static methods
39412		 */
39413
39414		open: function (group, opts) {
39415			if (!group) {
39416				return;
39417			}
39418
39419			if (!$.isPlainObject(opts)) {
39420				opts = {};
39421			}
39422
39423			// Close if already active
39424			if (false === F.close(true)) {
39425				return;
39426			}
39427
39428			// Normalize group
39429			if (!$.isArray(group)) {
39430				group = isQuery(group) ? $(group).get() : [group];
39431			}
39432
39433			// Recheck if the type of each element is `object` and set content type (image, ajax, etc)
39434			$.each(group, function(i, element) {
39435				var obj = {},
39436					href,
39437					title,
39438					content,
39439					type,
39440					rez,
39441					hrefParts,
39442					selector;
39443
39444				if ($.type(element) === "object") {
39445					// Check if is DOM element
39446					if (element.nodeType) {
39447						element = $(element);
39448					}
39449
39450					if (isQuery(element)) {
39451						obj = {
39452							href    : element.data('fancybox-href') || element.attr('href'),
39453							title   : element.data('fancybox-title') || element.attr('title'),
39454							isDom   : true,
39455							element : element
39456						};
39457
39458						if ($.metadata) {
39459							$.extend(true, obj, element.metadata());
39460						}
39461
39462					} else {
39463						obj = element;
39464					}
39465				}
39466
39467				href  = opts.href  || obj.href || (isString(element) ? element : null);
39468				title = opts.title !== undefined ? opts.title : obj.title || '';
39469
39470				content = opts.content || obj.content;
39471				type    = content ? 'html' : (opts.type  || obj.type);
39472
39473				if (!type && obj.isDom) {
39474					type = element.data('fancybox-type');
39475
39476					if (!type) {
39477						rez  = element.prop('class').match(/fancybox\.(\w+)/);
39478						type = rez ? rez[1] : null;
39479					}
39480				}
39481
39482				if (isString(href)) {
39483					// Try to guess the content type
39484					if (!type) {
39485						if (F.isImage(href)) {
39486							type = 'image';
39487
39488						} else if (F.isSWF(href)) {
39489							type = 'swf';
39490
39491						} else if (href.charAt(0) === '#') {
39492							type = 'inline';
39493
39494						} else if (isString(element)) {
39495							type    = 'html';
39496							content = element;
39497						}
39498					}
39499
39500					// Split url into two pieces with source url and content selector, e.g,
39501					// "/mypage.html #my_id" will load "/mypage.html" and display element having id "my_id"
39502					if (type === 'ajax') {
39503						hrefParts = href.split(/\s+/, 2);
39504						href      = hrefParts.shift();
39505						selector  = hrefParts.shift();
39506					}
39507				}
39508
39509				if (!content) {
39510					if (type === 'inline') {
39511						if (href) {
39512							content = $( isString(href) ? href.replace(/.*(?=#[^\s]+$)/, '') : href ); //strip for ie7
39513
39514						} else if (obj.isDom) {
39515							content = element;
39516						}
39517
39518					} else if (type === 'html') {
39519						content = href;
39520
39521					} else if (!type && !href && obj.isDom) {
39522						type    = 'inline';
39523						content = element;
39524					}
39525				}
39526
39527				$.extend(obj, {
39528					href     : href,
39529					type     : type,
39530					content  : content,
39531					title    : title,
39532					selector : selector
39533				});
39534
39535				group[ i ] = obj;
39536			});
39537
39538			// Extend the defaults
39539			F.opts = $.extend(true, {}, F.defaults, opts);
39540
39541			// All options are merged recursive except keys
39542			if (opts.keys !== undefined) {
39543				F.opts.keys = opts.keys ? $.extend({}, F.defaults.keys, opts.keys) : false;
39544			}
39545
39546			F.group = group;
39547
39548			return F._start(F.opts.index);
39549		},
39550
39551		// Cancel image loading or abort ajax request
39552		cancel: function () {
39553			var coming = F.coming;
39554
39555			if (!coming || false === F.trigger('onCancel')) {
39556				return;
39557			}
39558
39559			F.hideLoading();
39560
39561			if (F.ajaxLoad) {
39562				F.ajaxLoad.abort();
39563			}
39564
39565			F.ajaxLoad = null;
39566
39567			if (F.imgPreload) {
39568				F.imgPreload.onload = F.imgPreload.onerror = null;
39569			}
39570
39571			if (coming.wrap) {
39572				coming.wrap.stop(true, true).trigger('onReset').remove();
39573			}
39574
39575			F.coming = null;
39576
39577			// If the first item has been canceled, then clear everything
39578			if (!F.current) {
39579				F._afterZoomOut( coming );
39580			}
39581		},
39582
39583		// Start closing animation if is open; remove immediately if opening/closing
39584		close: function (event) {
39585			F.cancel();
39586
39587			if (false === F.trigger('beforeClose')) {
39588				return;
39589			}
39590
39591			F.unbindEvents();
39592
39593			if (!F.isActive) {
39594				return;
39595			}
39596
39597			if (!F.isOpen || event === true) {
39598				$('.fancybox-wrap').stop(true).trigger('onReset').remove();
39599
39600				F._afterZoomOut();
39601
39602			} else {
39603				F.isOpen = F.isOpened = false;
39604				F.isClosing = true;
39605
39606				$('.fancybox-item, .fancybox-nav').remove();
39607
39608				F.wrap.stop(true, true).removeClass('fancybox-opened');
39609
39610				F.transitions[ F.current.closeMethod ]();
39611			}
39612		},
39613
39614		// Manage slideshow:
39615		//   $.fancybox.play(); - toggle slideshow
39616		//   $.fancybox.play( true ); - start
39617		//   $.fancybox.play( false ); - stop
39618		play: function ( action ) {
39619			var clear = function () {
39620					clearTimeout(F.player.timer);
39621				},
39622				set = function () {
39623					clear();
39624
39625					if (F.current && F.player.isActive) {
39626						F.player.timer = setTimeout(F.next, F.current.playSpeed);
39627					}
39628				},
39629				stop = function () {
39630					clear();
39631
39632					D.unbind('.player');
39633
39634					F.player.isActive = false;
39635
39636					F.trigger('onPlayEnd');
39637				},
39638				start = function () {
39639					if (F.current && (F.current.loop || F.current.index < F.group.length - 1)) {
39640						F.player.isActive = true;
39641
39642						D.bind({
39643							'onCancel.player beforeClose.player' : stop,
39644							'onUpdate.player'   : set,
39645							'beforeLoad.player' : clear
39646						});
39647
39648						set();
39649
39650						F.trigger('onPlayStart');
39651					}
39652				};
39653
39654			if (action === true || (!F.player.isActive && action !== false)) {
39655				start();
39656			} else {
39657				stop();
39658			}
39659		},
39660
39661		// Navigate to next gallery item
39662		next: function ( direction ) {
39663			var current = F.current;
39664
39665			if (current) {
39666				if (!isString(direction)) {
39667					direction = current.direction.next;
39668				}
39669
39670				F.jumpto(current.index + 1, direction, 'next');
39671			}
39672		},
39673
39674		// Navigate to previous gallery item
39675		prev: function ( direction ) {
39676			var current = F.current;
39677
39678			if (current) {
39679				if (!isString(direction)) {
39680					direction = current.direction.prev;
39681				}
39682
39683				F.jumpto(current.index - 1, direction, 'prev');
39684			}
39685		},
39686
39687		// Navigate to gallery item by index
39688		jumpto: function ( index, direction, router ) {
39689			var current = F.current;
39690
39691			if (!current) {
39692				return;
39693			}
39694
39695			index = getScalar(index);
39696
39697			F.direction = direction || current.direction[ (index >= current.index ? 'next' : 'prev') ];
39698			F.router    = router || 'jumpto';
39699
39700			if (current.loop) {
39701				if (index < 0) {
39702					index = current.group.length + (index % current.group.length);
39703				}
39704
39705				index = index % current.group.length;
39706			}
39707
39708			if (current.group[ index ] !== undefined) {
39709				F.cancel();
39710
39711				F._start(index);
39712			}
39713		},
39714
39715		// Center inside viewport and toggle position type to fixed or absolute if needed
39716		reposition: function (e, onlyAbsolute) {
39717			var current = F.current,
39718				wrap    = current ? current.wrap : null,
39719				pos;
39720
39721			if (wrap) {
39722				pos = F._getPosition(onlyAbsolute);
39723
39724				if (e && e.type === 'scroll') {
39725					delete pos.position;
39726
39727					wrap.stop(true, true).animate(pos, 200);
39728
39729				} else {
39730					wrap.css(pos);
39731
39732					current.pos = $.extend({}, current.dim, pos);
39733				}
39734			}
39735		},
39736
39737		update: function (e) {
39738			var type = (e && e.type),
39739				anyway = !type || type === 'orientationchange';
39740
39741			if (anyway) {
39742				clearTimeout(didUpdate);
39743
39744				didUpdate = null;
39745			}
39746
39747			if (!F.isOpen || didUpdate) {
39748				return;
39749			}
39750
39751			didUpdate = setTimeout(function() {
39752				var current = F.current;
39753
39754				if (!current || F.isClosing) {
39755					return;
39756				}
39757
39758				F.wrap.removeClass('fancybox-tmp');
39759
39760				if (anyway || type === 'load' || (type === 'resize' && current.autoResize)) {
39761					F._setDimension();
39762				}
39763
39764				if (!(type === 'scroll' && current.canShrink)) {
39765					F.reposition(e);
39766				}
39767
39768				F.trigger('onUpdate');
39769
39770				didUpdate = null;
39771
39772			}, (anyway && !isTouch ? 0 : 300));
39773		},
39774
39775		// Shrink content to fit inside viewport or restore if resized
39776		toggle: function ( action ) {
39777			if (F.isOpen) {
39778				F.current.fitToView = $.type(action) === "boolean" ? action : !F.current.fitToView;
39779
39780				// Help browser to restore document dimensions
39781				if (isTouch) {
39782					F.wrap.removeAttr('style').addClass('fancybox-tmp');
39783
39784					F.trigger('onUpdate');
39785				}
39786
39787				F.update();
39788			}
39789		},
39790
39791		hideLoading: function () {
39792			D.unbind('.loading');
39793
39794			$('#fancybox-loading').remove();
39795		},
39796
39797		showLoading: function () {
39798			var el, viewport;
39799
39800			F.hideLoading();
39801
39802			el = $('<div id="fancybox-loading"><div></div></div>').click(F.cancel).appendTo('body');
39803
39804			// If user will press the escape-button, the request will be canceled
39805			D.bind('keydown.loading', function(e) {
39806				if ((e.which || e.keyCode) === 27) {
39807					e.preventDefault();
39808
39809					F.cancel();
39810				}
39811			});
39812
39813			if (!F.defaults.fixed) {
39814				viewport = F.getViewport();
39815
39816				el.css({
39817					position : 'absolute',
39818					top  : (viewport.h * 0.5) + viewport.y,
39819					left : (viewport.w * 0.5) + viewport.x
39820				});
39821			}
39822		},
39823
39824		getViewport: function () {
39825			var locked = (F.current && F.current.locked) || false,
39826				rez    = {
39827					x: W.scrollLeft(),
39828					y: W.scrollTop()
39829				};
39830
39831			if (locked) {
39832				rez.w = locked[0].clientWidth;
39833				rez.h = locked[0].clientHeight;
39834
39835			} else {
39836				// See http://bugs.jquery.com/ticket/6724
39837				rez.w = isTouch && window.innerWidth  ? window.innerWidth  : W.width();
39838				rez.h = isTouch && window.innerHeight ? window.innerHeight : W.height();
39839			}
39840
39841			return rez;
39842		},
39843
39844		// Unbind the keyboard / clicking actions
39845		unbindEvents: function () {
39846			if (F.wrap && isQuery(F.wrap)) {
39847				F.wrap.unbind('.fb');
39848			}
39849
39850			D.unbind('.fb');
39851			W.unbind('.fb');
39852		},
39853
39854		bindEvents: function () {
39855			var current = F.current,
39856				keys;
39857
39858			if (!current) {
39859				return;
39860			}
39861
39862			// Changing document height on iOS devices triggers a 'resize' event,
39863			// that can change document height... repeating infinitely
39864			W.bind('orientationchange.fb' + (isTouch ? '' : ' resize.fb') + (current.autoCenter && !current.locked ? ' scroll.fb' : ''), F.update);
39865
39866			keys = current.keys;
39867
39868			if (keys) {
39869				D.bind('keydown.fb', function (e) {
39870					var code   = e.which || e.keyCode,
39871						target = e.target || e.srcElement;
39872
39873					// Skip esc key if loading, because showLoading will cancel preloading
39874					if (code === 27 && F.coming) {
39875						return false;
39876					}
39877
39878					// Ignore key combinations and key events within form elements
39879					if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && !(target && (target.type || $(target).is('[contenteditable]')))) {
39880						$.each(keys, function(i, val) {
39881							if (current.group.length > 1 && val[ code ] !== undefined) {
39882								F[ i ]( val[ code ] );
39883
39884								e.preventDefault();
39885								return false;
39886							}
39887
39888							if ($.inArray(code, val) > -1) {
39889								F[ i ] ();
39890
39891								e.preventDefault();
39892								return false;
39893							}
39894						});
39895					}
39896				});
39897			}
39898
39899			if ($.fn.mousewheel && current.mouseWheel) {
39900				F.wrap.bind('mousewheel.fb', function (e, delta, deltaX, deltaY) {
39901					var target = e.target || null,
39902						parent = $(target),
39903						canScroll = false;
39904
39905					while (parent.length) {
39906						if (canScroll || parent.is('.fancybox-skin') || parent.is('.fancybox-wrap')) {
39907							break;
39908						}
39909
39910						canScroll = isScrollable( parent[0] );
39911						parent    = $(parent).parent();
39912					}
39913
39914					if (delta !== 0 && !canScroll) {
39915						if (F.group.length > 1 && !current.canShrink) {
39916							if (deltaY > 0 || deltaX > 0) {
39917								F.prev( deltaY > 0 ? 'down' : 'left' );
39918
39919							} else if (deltaY < 0 || deltaX < 0) {
39920								F.next( deltaY < 0 ? 'up' : 'right' );
39921							}
39922
39923							e.preventDefault();
39924						}
39925					}
39926				});
39927			}
39928		},
39929
39930		trigger: function (event, o) {
39931			var ret, obj = o || F.coming || F.current;
39932
39933			if (!obj) {
39934				return;
39935			}
39936
39937			if ($.isFunction( obj[event] )) {
39938				ret = obj[event].apply(obj, Array.prototype.slice.call(arguments, 1));
39939			}
39940
39941			if (ret === false) {
39942				return false;
39943			}
39944
39945			if (obj.helpers) {
39946				$.each(obj.helpers, function (helper, opts) {
39947					if (opts && F.helpers[helper] && $.isFunction(F.helpers[helper][event])) {
39948						F.helpers[helper][event]($.extend(true, {}, F.helpers[helper].defaults, opts), obj);
39949					}
39950				});
vendor: 7,983 bytes, lines 39950-40306
39950
39951			}
39952
39953			D.trigger(event);
39954		},
39955
39956		isImage: function (str) {
39957			return isString(str) && str.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i);
39958		},
39959
39960		isSWF: function (str) {
39961			return isString(str) && str.match(/\.(swf)((\?|#).*)?$/i);
39962		},
39963
39964		_start: function (index) {
39965			var coming = {},
39966				obj,
39967				href,
39968				type,
39969				margin,
39970				padding;
39971
39972			index = getScalar( index );
39973			obj   = F.group[ index ] || null;
39974
39975			if (!obj) {
39976				return false;
39977			}
39978
39979			coming = $.extend(true, {}, F.opts, obj);
39980
39981			// Convert margin and padding properties to array - top, right, bottom, left
39982			margin  = coming.margin;
39983			padding = coming.padding;
39984
39985			if ($.type(margin) === 'number') {
39986				coming.margin = [margin, margin, margin, margin];
39987			}
39988
39989			if ($.type(padding) === 'number') {
39990				coming.padding = [padding, padding, padding, padding];
39991			}
39992
39993			// 'modal' propery is just a shortcut
39994			if (coming.modal) {
39995				$.extend(true, coming, {
39996					closeBtn   : false,
39997					closeClick : false,
39998					nextClick  : false,
39999					arrows     : false,
40000					mouseWheel : false,
40001					keys       : null,
40002					helpers: {
40003						overlay : {
40004							closeClick : false
40005						}
40006					}
40007				});
40008			}
40009
40010			// 'autoSize' property is a shortcut, too
40011			if (coming.autoSize) {
40012				coming.autoWidth = coming.autoHeight = true;
40013			}
40014
40015			if (coming.width === 'auto') {
40016				coming.autoWidth = true;
40017			}
40018
40019			if (coming.height === 'auto') {
40020				coming.autoHeight = true;
40021			}
40022
40023			/*
40024			 * Add reference to the group, so it`s possible to access from callbacks, example:
40025			 * afterLoad : function() {
40026			 *     this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : '');
40027			 * }
40028			 */
40029
40030			coming.group  = F.group;
40031			coming.index  = index;
40032
40033			// Give a chance for callback or helpers to update coming item (type, title, etc)
40034			F.coming = coming;
40035
40036			if (false === F.trigger('beforeLoad')) {
40037				F.coming = null;
40038
40039				return;
40040			}
40041
40042			type = coming.type;
40043			href = coming.href;
40044
40045			if (!type) {
40046				F.coming = null;
40047
40048				//If we can not determine content type then drop silently or display next/prev item if looping through gallery
40049				if (F.current && F.router && F.router !== 'jumpto') {
40050					F.current.index = index;
40051
40052					return F[ F.router ]( F.direction );
40053				}
40054
40055				return false;
40056			}
40057
40058			F.isActive = true;
40059
40060			if (type === 'image' || type === 'swf') {
40061				coming.autoHeight = coming.autoWidth = false;
40062				coming.scrolling  = 'visible';
40063			}
40064
40065			if (type === 'image') {
40066				coming.aspectRatio = true;
40067			}
40068
40069			if (type === 'iframe' && isTouch) {
40070				coming.scrolling = 'scroll';
40071			}
40072
40073			// Build the neccessary markup
40074			coming.wrap = $(coming.tpl.wrap).addClass('fancybox-' + (isTouch ? 'mobile' : 'desktop') + ' fancybox-type-' + type + ' fancybox-tmp ' + coming.wrapCSS).appendTo( coming.parent || 'body' );
40075
40076			$.extend(coming, {
40077				skin  : $('.fancybox-skin',  coming.wrap),
40078				outer : $('.fancybox-outer', coming.wrap),
40079				inner : $('.fancybox-inner', coming.wrap)
40080			});
40081
40082			$.each(["Top", "Right", "Bottom", "Left"], function(i, v) {
40083				coming.skin.css('padding' + v, getValue(coming.padding[ i ]));
40084			});
40085
40086			F.trigger('onReady');
40087
40088			// Check before try to load; 'inline' and 'html' types need content, others - href
40089			if (type === 'inline' || type === 'html') {
40090				if (!coming.content || !coming.content.length) {
40091					return F._error( 'content' );
40092				}
40093
40094			} else if (!href) {
40095				return F._error( 'href' );
40096			}
40097
40098			if (type === 'image') {
40099				F._loadImage();
40100
40101			} else if (type === 'ajax') {
40102				F._loadAjax();
40103
40104			} else if (type === 'iframe') {
40105				F._loadIframe();
40106
40107			} else {
40108				F._afterLoad();
40109			}
40110		},
40111
40112		_error: function ( type ) {
40113			$.extend(F.coming, {
40114				type       : 'html',
40115				autoWidth  : true,
40116				autoHeight : true,
40117				minWidth   : 0,
40118				minHeight  : 0,
40119				scrolling  : 'no',
40120				hasError   : type,
40121				content    : F.coming.tpl.error
40122			});
40123
40124			F._afterLoad();
40125		},
40126
40127		_loadImage: function () {
40128			// Reset preload image so it is later possible to check "complete" property
40129			var img = F.imgPreload = new Image();
40130
40131			img.onload = function () {
40132				this.onload = this.onerror = null;
40133
40134				F.coming.width  = this.width / F.opts.pixelRatio;
40135				F.coming.height = this.height / F.opts.pixelRatio;
40136
40137				F._afterLoad();
40138			};
40139
40140			img.onerror = function () {
40141				this.onload = this.onerror = null;
40142
40143				F._error( 'image' );
40144			};
40145
40146			img.src = F.coming.href;
40147
40148			if (img.complete !== true) {
40149				F.showLoading();
40150			}
40151		},
40152
40153		_loadAjax: function () {
40154			var coming = F.coming;
40155
40156			F.showLoading();
40157
40158			F.ajaxLoad = $.ajax($.extend({}, coming.ajax, {
40159				url: coming.href,
40160				error: function (jqXHR, textStatus) {
40161					if (F.coming && textStatus !== 'abort') {
40162						F._error( 'ajax', jqXHR );
40163
40164					} else {
40165						F.hideLoading();
40166					}
40167				},
40168				success: function (data, textStatus) {
40169					if (textStatus === 'success') {
40170						coming.content = data;
40171
40172						F._afterLoad();
40173					}
40174				}
40175			}));
40176		},
40177
40178		_loadIframe: function() {
40179			var coming = F.coming,
40180				iframe = $(coming.tpl.iframe.replace(/\{rnd\}/g, new Date().getTime()))
40181					.attr('scrolling', isTouch ? 'auto' : coming.iframe.scrolling)
40182					.attr('src', coming.href);
40183
40184			// This helps IE
40185			$(coming.wrap).bind('onReset', function () {
40186				try {
40187					$(this).find('iframe').hide().attr('src', '//about:blank').end().empty();
40188				} catch (e) {}
40189			});
40190
40191			if (coming.iframe.preload) {
40192				F.showLoading();
40193
40194				iframe.one('load', function() {
40195					$(this).data('ready', 1);
40196
40197					// iOS will lose scrolling if we resize
40198					if (!isTouch) {
40199						$(this).bind('load.fb', F.update);
40200					}
40201
40202					// Without this trick:
40203					//   - iframe won't scroll on iOS devices
40204					//   - IE7 sometimes displays empty iframe
40205					$(this).parents('.fancybox-wrap').width('100%').removeClass('fancybox-tmp').show();
40206
40207					F._afterLoad();
40208				});
40209			}
40210
40211			coming.content = iframe.appendTo( coming.inner );
40212
40213			if (!coming.iframe.preload) {
40214				F._afterLoad();
40215			}
40216		},
40217
40218		_preloadImages: function() {
40219			var group   = F.group,
40220				current = F.current,
40221				len     = group.length,
40222				cnt     = current.preload ? Math.min(current.preload, len - 1) : 0,
40223				item,
40224				i;
40225
40226			for (i = 1; i <= cnt; i += 1) {
40227				item = group[ (current.index + i ) % len ];
40228
40229				if (item.type === 'image' && item.href) {
40230					new Image().src = item.href;
40231				}
40232			}
40233		},
40234
40235		_afterLoad: function () {
40236			var coming   = F.coming,
40237				previous = F.current,
40238				placeholder = 'fancybox-placeholder',
40239				current,
40240				content,
40241				type,
40242				scrolling,
40243				href,
40244				embed;
40245
40246			F.hideLoading();
40247
40248			if (!coming || F.isActive === false) {
40249				return;
40250			}
40251
40252			if (false === F.trigger('afterLoad', coming, previous)) {
40253				coming.wrap.stop(true).trigger('onReset').remove();
40254
40255				F.coming = null;
40256
40257				return;
40258			}
40259
40260			if (previous) {
40261				F.trigger('beforeChange', previous);
40262
40263				previous.wrap.stop(true).removeClass('fancybox-opened')
40264					.find('.fancybox-item, .fancybox-nav')
40265					.remove();
40266			}
40267
40268			F.unbindEvents();
40269
40270			current   = coming;
40271			content   = coming.content;
40272			type      = coming.type;
40273			scrolling = coming.scrolling;
40274
40275			$.extend(F, {
40276				wrap  : current.wrap,
40277				skin  : current.skin,
40278				outer : current.outer,
40279				inner : current.inner,
40280				current  : current,
40281				previous : previous
40282			});
40283
40284			href = current.href;
40285
40286			switch (type) {
40287				case 'inline':
40288				case 'ajax':
40289				case 'html':
40290					if (current.selector) {
40291						content = $('<div>').html(content).find(current.selector);
40292
40293					} else if (isQuery(content)) {
40294						if (!content.data(placeholder)) {
40295							content.data(placeholder, $('<div class="' + placeholder + '"></div>').insertAfter( content ).hide() );
40296						}
40297
40298						content = content.show().detach();
40299
40300						current.wrap.bind('onReset', function () {
40301							if ($(this).find(content).length) {
40302								content.hide().replaceAll( content.data(placeholder) ).data(placeholder, false);
40303							}
40304						});
40305					}
40306				
vendor: 14,989 bytes, lines 40306-40878
40306	break;
40307
40308				case 'image':
40309					content = current.tpl.image.replace('{href}', href);
40310					break;
40311
40312				case 'swf':
40313					content = '<object id="fancybox-swf" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="100%"><param name="movie" value="' + href + '"></param>';
40314					embed   = '';
40315
40316					$.each(current.swf, function(name, val) {
40317						content += '<param name="' + name + '" value="' + val + '"></param>';
40318						embed   += ' ' + name + '="' + val + '"';
40319					});
40320
40321					content += '<embed src="' + href + '" type="application/x-shockwave-flash" width="100%" height="100%"' + embed + '></embed></object>';
40322					break;
40323			}
40324
40325			if (!(isQuery(content) && content.parent().is(current.inner))) {
40326				current.inner.append( content );
40327			}
40328
40329			// Give a chance for helpers or callbacks to update elements
40330			F.trigger('beforeShow');
40331
40332			// Set scrolling before calculating dimensions
40333			current.inner.css('overflow', scrolling === 'yes' ? 'scroll' : (scrolling === 'no' ? 'hidden' : scrolling));
40334
40335			// Set initial dimensions and start position
40336			F._setDimension();
40337
40338			F.reposition();
40339
40340			F.isOpen = false;
40341			F.coming = null;
40342
40343			F.bindEvents();
40344
40345			if (!F.isOpened) {
40346				$('.fancybox-wrap').not( current.wrap ).stop(true).trigger('onReset').remove();
40347
40348			} else if (previous.prevMethod) {
40349				F.transitions[ previous.prevMethod ]();
40350			}
40351
40352			F.transitions[ F.isOpened ? current.nextMethod : current.openMethod ]();
40353
40354			F._preloadImages();
40355		},
40356
40357		_setDimension: function () {
40358			var viewport   = F.getViewport(),
40359				steps      = 0,
40360				canShrink  = false,
40361				canExpand  = false,
40362				wrap       = F.wrap,
40363				skin       = F.skin,
40364				inner      = F.inner,
40365				current    = F.current,
40366				width      = current.width,
40367				height     = current.height,
40368				minWidth   = current.minWidth,
40369				minHeight  = current.minHeight,
40370				maxWidth   = current.maxWidth,
40371				maxHeight  = current.maxHeight,
40372				scrolling  = current.scrolling,
40373				scrollOut  = current.scrollOutside ? current.scrollbarWidth : 0,
40374				margin     = current.margin,
40375				wMargin    = getScalar(margin[1] + margin[3]),
40376				hMargin    = getScalar(margin[0] + margin[2]),
40377				wPadding,
40378				hPadding,
40379				wSpace,
40380				hSpace,
40381				origWidth,
40382				origHeight,
40383				origMaxWidth,
40384				origMaxHeight,
40385				ratio,
40386				width_,
40387				height_,
40388				maxWidth_,
40389				maxHeight_,
40390				iframe,
40391				body;
40392
40393			// Reset dimensions so we could re-check actual size
40394			wrap.add(skin).add(inner).width('auto').height('auto').removeClass('fancybox-tmp');
40395
40396			wPadding = getScalar(skin.outerWidth(true)  - skin.width());
40397			hPadding = getScalar(skin.outerHeight(true) - skin.height());
40398
40399			// Any space between content and viewport (margin, padding, border, title)
40400			wSpace = wMargin + wPadding;
40401			hSpace = hMargin + hPadding;
40402
40403			origWidth  = isPercentage(width)  ? (viewport.w - wSpace) * getScalar(width)  / 100 : width;
40404			origHeight = isPercentage(height) ? (viewport.h - hSpace) * getScalar(height) / 100 : height;
40405
40406			if (current.type === 'iframe') {
40407				iframe = current.content;
40408
40409				if (current.autoHeight && iframe.data('ready') === 1) {
40410					try {
40411						if (iframe[0].contentWindow.document.location) {
40412							inner.width( origWidth ).height(9999);
40413
40414							body = iframe.contents().find('body');
40415
40416							if (scrollOut) {
40417								body.css('overflow-x', 'hidden');
40418							}
40419
40420							origHeight = body.outerHeight(true);
40421						}
40422
40423					} catch (e) {}
40424				}
40425
40426			} else if (current.autoWidth || current.autoHeight) {
40427				inner.addClass( 'fancybox-tmp' );
40428
40429				// Set width or height in case we need to calculate only one dimension
40430				if (!current.autoWidth) {
40431					inner.width( origWidth );
40432				}
40433
40434				if (!current.autoHeight) {
40435					inner.height( origHeight );
40436				}
40437
40438				if (current.autoWidth) {
40439					origWidth = inner.width();
40440				}
40441
40442				if (current.autoHeight) {
40443					origHeight = inner.height();
40444				}
40445
40446				inner.removeClass( 'fancybox-tmp' );
40447			}
40448
40449			width  = getScalar( origWidth );
40450			height = getScalar( origHeight );
40451
40452			ratio  = origWidth / origHeight;
40453
40454			// Calculations for the content
40455			minWidth  = getScalar(isPercentage(minWidth) ? getScalar(minWidth, 'w') - wSpace : minWidth);
40456			maxWidth  = getScalar(isPercentage(maxWidth) ? getScalar(maxWidth, 'w') - wSpace : maxWidth);
40457
40458			minHeight = getScalar(isPercentage(minHeight) ? getScalar(minHeight, 'h') - hSpace : minHeight);
40459			maxHeight = getScalar(isPercentage(maxHeight) ? getScalar(maxHeight, 'h') - hSpace : maxHeight);
40460
40461			// These will be used to determine if wrap can fit in the viewport
40462			origMaxWidth  = maxWidth;
40463			origMaxHeight = maxHeight;
40464
40465			if (current.fitToView) {
40466				maxWidth  = Math.min(viewport.w - wSpace, maxWidth);
40467				maxHeight = Math.min(viewport.h - hSpace, maxHeight);
40468			}
40469
40470			maxWidth_  = viewport.w - wMargin;
40471			maxHeight_ = viewport.h - hMargin;
40472
40473			if (current.aspectRatio) {
40474				if (width > maxWidth) {
40475					width  = maxWidth;
40476					height = getScalar(width / ratio);
40477				}
40478
40479				if (height > maxHeight) {
40480					height = maxHeight;
40481					width  = getScalar(height * ratio);
40482				}
40483
40484				if (width < minWidth) {
40485					width  = minWidth;
40486					height = getScalar(width / ratio);
40487				}
40488
40489				if (height < minHeight) {
40490					height = minHeight;
40491					width  = getScalar(height * ratio);
40492				}
40493
40494			} else {
40495				width = Math.max(minWidth, Math.min(width, maxWidth));
40496
40497				if (current.autoHeight && current.type !== 'iframe') {
40498					inner.width( width );
40499
40500					height = inner.height();
40501				}
40502
40503				height = Math.max(minHeight, Math.min(height, maxHeight));
40504			}
40505
40506			// Try to fit inside viewport (including the title)
40507			if (current.fitToView) {
40508				inner.width( width ).height( height );
40509
40510				wrap.width( width + wPadding );
40511
40512				// Real wrap dimensions
40513				width_  = wrap.width();
40514				height_ = wrap.height();
40515
40516				if (current.aspectRatio) {
40517					while ((width_ > maxWidth_ || height_ > maxHeight_) && width > minWidth && height > minHeight) {
40518						if (steps++ > 19) {
40519							break;
40520						}
40521
40522						height = Math.max(minHeight, Math.min(maxHeight, height - 10));
40523						width  = getScalar(height * ratio);
40524
40525						if (width < minWidth) {
40526							width  = minWidth;
40527							height = getScalar(width / ratio);
40528						}
40529
40530						if (width > maxWidth) {
40531							width  = maxWidth;
40532							height = getScalar(width / ratio);
40533						}
40534
40535						inner.width( width ).height( height );
40536
40537						wrap.width( width + wPadding );
40538
40539						width_  = wrap.width();
40540						height_ = wrap.height();
40541					}
40542
40543				} else {
40544					width  = Math.max(minWidth,  Math.min(width,  width  - (width_  - maxWidth_)));
40545					height = Math.max(minHeight, Math.min(height, height - (height_ - maxHeight_)));
40546				}
40547			}
40548
40549			if (scrollOut && scrolling === 'auto' && height < origHeight && (width + wPadding + scrollOut) < maxWidth_) {
40550				width += scrollOut;
40551			}
40552
40553			inner.width( width ).height( height );
40554
40555			wrap.width( width + wPadding );
40556
40557			width_  = wrap.width();
40558			height_ = wrap.height();
40559
40560			canShrink = (width_ > maxWidth_ || height_ > maxHeight_) && width > minWidth && height > minHeight;
40561			canExpand = current.aspectRatio ? (width < origMaxWidth && height < origMaxHeight && width < origWidth && height < origHeight) : ((width < origMaxWidth || height < origMaxHeight) && (width < origWidth || height < origHeight));
40562
40563			$.extend(current, {
40564				dim : {
40565					width	: getValue( width_ ),
40566					height	: getValue( height_ )
40567				},
40568				origWidth  : origWidth,
40569				origHeight : origHeight,
40570				canShrink  : canShrink,
40571				canExpand  : canExpand,
40572				wPadding   : wPadding,
40573				hPadding   : hPadding,
40574				wrapSpace  : height_ - skin.outerHeight(true),
40575				skinSpace  : skin.height() - height
40576			});
40577
40578			if (!iframe && current.autoHeight && height > minHeight && height < maxHeight && !canExpand) {
40579				inner.height('auto');
40580			}
40581		},
40582
40583		_getPosition: function (onlyAbsolute) {
40584			var current  = F.current,
40585				viewport = F.getViewport(),
40586				margin   = current.margin,
40587				width    = F.wrap.width()  + margin[1] + margin[3],
40588				height   = F.wrap.height() + margin[0] + margin[2],
40589				rez      = {
40590					position: 'absolute',
40591					top  : margin[0],
40592					left : margin[3]
40593				};
40594
40595			if (current.autoCenter && current.fixed && !onlyAbsolute && height <= viewport.h && width <= viewport.w) {
40596				rez.position = 'fixed';
40597
40598			} else if (!current.locked) {
40599				rez.top  += viewport.y;
40600				rez.left += viewport.x;
40601			}
40602
40603			rez.top  = getValue(Math.max(rez.top,  rez.top  + ((viewport.h - height) * current.topRatio)));
40604			rez.left = getValue(Math.max(rez.left, rez.left + ((viewport.w - width)  * current.leftRatio)));
40605
40606			return rez;
40607		},
40608
40609		_afterZoomIn: function () {
40610			var current = F.current;
40611
40612			if (!current) {
40613				return;
40614			}
40615
40616			F.isOpen = F.isOpened = true;
40617
40618			F.wrap.css('overflow', 'visible').addClass('fancybox-opened');
40619
40620			F.update();
40621
40622			// Assign a click event
40623			if ( current.closeClick || (current.nextClick && F.group.length > 1) ) {
40624				F.inner.css('cursor', 'pointer').bind('click.fb', function(e) {
40625					if (!$(e.target).is('a') && !$(e.target).parent().is('a')) {
40626						e.preventDefault();
40627
40628						F[ current.closeClick ? 'close' : 'next' ]();
40629					}
40630				});
40631			}
40632
40633			// Create a close button
40634			if (current.closeBtn) {
40635				$(current.tpl.closeBtn).appendTo(F.skin).bind('click.fb', function(e) {
40636					e.preventDefault();
40637
40638					F.close();
40639				});
40640			}
40641
40642			// Create navigation arrows
40643			if (current.arrows && F.group.length > 1) {
40644				if (current.loop || current.index > 0) {
40645					$(current.tpl.prev).appendTo(F.outer).bind('click.fb', F.prev);
40646				}
40647
40648				if (current.loop || current.index < F.group.length - 1) {
40649					$(current.tpl.next).appendTo(F.outer).bind('click.fb', F.next);
40650				}
40651			}
40652
40653			F.trigger('afterShow');
40654
40655			// Stop the slideshow if this is the last item
40656			if (!current.loop && current.index === current.group.length - 1) {
40657				F.play( false );
40658
40659			} else if (F.opts.autoPlay && !F.player.isActive) {
40660				F.opts.autoPlay = false;
40661
40662				F.play();
40663			}
40664		},
40665
40666		_afterZoomOut: function ( obj ) {
40667			obj = obj || F.current;
40668
40669			$('.fancybox-wrap').trigger('onReset').remove();
40670
40671			$.extend(F, {
40672				group  : {},
40673				opts   : {},
40674				router : false,
40675				current   : null,
40676				isActive  : false,
40677				isOpened  : false,
40678				isOpen    : false,
40679				isClosing : false,
40680				wrap   : null,
40681				skin   : null,
40682				outer  : null,
40683				inner  : null
40684			});
40685
40686			F.trigger('afterClose', obj);
40687		}
40688	});
40689
40690	/*
40691	 *	Default transitions
40692	 */
40693
40694	F.transitions = {
40695		getOrigPosition: function () {
40696			var current  = F.current,
40697				element  = current.element,
40698				orig     = current.orig,
40699				pos      = {},
40700				width    = 50,
40701				height   = 50,
40702				hPadding = current.hPadding,
40703				wPadding = current.wPadding,
40704				viewport = F.getViewport();
40705
40706			if (!orig && current.isDom && element.is(':visible')) {
40707				orig = element.find('img:first');
40708
40709				if (!orig.length) {
40710					orig = element;
40711				}
40712			}
40713
40714			if (isQuery(orig)) {
40715				pos = orig.offset();
40716
40717				if (orig.is('img')) {
40718					width  = orig.outerWidth();
40719					height = orig.outerHeight();
40720				}
40721
40722			} else {
40723				pos.top  = viewport.y + (viewport.h - height) * current.topRatio;
40724				pos.left = viewport.x + (viewport.w - width)  * current.leftRatio;
40725			}
40726
40727			if (F.wrap.css('position') === 'fixed' || current.locked) {
40728				pos.top  -= viewport.y;
40729				pos.left -= viewport.x;
40730			}
40731
40732			pos = {
40733				top     : getValue(pos.top  - hPadding * current.topRatio),
40734				left    : getValue(pos.left - wPadding * current.leftRatio),
40735				width   : getValue(width  + wPadding),
40736				height  : getValue(height + hPadding)
40737			};
40738
40739			return pos;
40740		},
40741
40742		step: function (now, fx) {
40743			var ratio,
40744				padding,
40745				value,
40746				prop       = fx.prop,
40747				current    = F.current,
40748				wrapSpace  = current.wrapSpace,
40749				skinSpace  = current.skinSpace;
40750
40751			if (prop === 'width' || prop === 'height') {
40752				ratio = fx.end === fx.start ? 1 : (now - fx.start) / (fx.end - fx.start);
40753
40754				if (F.isClosing) {
40755					ratio = 1 - ratio;
40756				}
40757
40758				padding = prop === 'width' ? current.wPadding : current.hPadding;
40759				value   = now - padding;
40760
40761				F.skin[ prop ](  getScalar( prop === 'width' ?  value : value - (wrapSpace * ratio) ) );
40762				F.inner[ prop ]( getScalar( prop === 'width' ?  value : value - (wrapSpace * ratio) - (skinSpace * ratio) ) );
40763			}
40764		},
40765
40766		zoomIn: function () {
40767			var current  = F.current,
40768				startPos = current.pos,
40769				effect   = current.openEffect,
40770				elastic  = effect === 'elastic',
40771				endPos   = $.extend({opacity : 1}, startPos);
40772
40773			// Remove "position" property that breaks older IE
40774			delete endPos.position;
40775
40776			if (elastic) {
40777				startPos = this.getOrigPosition();
40778
40779				if (current.openOpacity) {
40780					startPos.opacity = 0.1;
40781				}
40782
40783			} else if (effect === 'fade') {
40784				startPos.opacity = 0.1;
40785			}
40786
40787			F.wrap.css(startPos).animate(endPos, {
40788				duration : effect === 'none' ? 0 : current.openSpeed,
40789				easing   : current.openEasing,
40790				step     : elastic ? this.step : null,
40791				complete : F._afterZoomIn
40792			});
40793		},
40794
40795		zoomOut: function () {
40796			var current  = F.current,
40797				effect   = current.closeEffect,
40798				elastic  = effect === 'elastic',
40799				endPos   = {opacity : 0.1};
40800
40801			if (elastic) {
40802				endPos = this.getOrigPosition();
40803
40804				if (current.closeOpacity) {
40805					endPos.opacity = 0.1;
40806				}
40807			}
40808
40809			F.wrap.animate(endPos, {
40810				duration : effect === 'none' ? 0 : current.closeSpeed,
40811				easing   : current.closeEasing,
40812				step     : elastic ? this.step : null,
40813				complete : F._afterZoomOut
40814			});
40815		},
40816
40817		changeIn: function () {
40818			var current   = F.current,
40819				effect    = current.nextEffect,
40820				startPos  = current.pos,
40821				endPos    = { opacity : 1 },
40822				direction = F.direction,
40823				distance  = 200,
40824				field;
40825
40826			startPos.opacity = 0.1;
40827
40828			if (effect === 'elastic') {
40829				field = direction === 'down' || direction === 'up' ? 'top' : 'left';
40830
40831				if (direction === 'down' || direction === 'right') {
40832					startPos[ field ] = getValue(getScalar(startPos[ field ]) - distance);
40833					endPos[ field ]   = '+=' + distance + 'px';
40834
40835				} else {
40836					startPos[ field ] = getValue(getScalar(startPos[ field ]) + distance);
40837					endPos[ field ]   = '-=' + distance + 'px';
40838				}
40839			}
40840
40841			// Workaround for http://bugs.jquery.com/ticket/12273
40842			if (effect === 'none') {
40843				F._afterZoomIn();
40844
40845			} else {
40846				F.wrap.css(startPos).animate(endPos, {
40847					duration : current.nextSpeed,
40848					easing   : current.nextEasing,
40849					complete : F._afterZoomIn
40850				});
40851			}
40852		},
40853
40854		changeOut: function () {
40855			var previous  = F.previous,
40856				effect    = previous.prevEffect,
40857				endPos    = { opacity : 0.1 },
40858				direction = F.direction,
40859				distance  = 200;
40860
40861			if (effect === 'elastic') {
40862				endPos[ direction === 'down' || direction === 'up' ? 'top' : 'left' ] = ( direction === 'up' || direction === 'left' ? '-' : '+' ) + '=' + distance + 'px';
40863			}
40864
40865			previous.wrap.animate(endPos, {
40866				duration : effect === 'none' ? 0 : previous.prevSpeed,
40867				easing   : previous.prevEasing,
40868				complete : function () {
40869					$(this).trigger('onReset').remove();
40870				}
40871			});
40872		}
40873	};
40874
40875	/*
40876	 *	Overlay helper
40877	 */
40878
40879	F.helpers.overlay = {
40880		defaults : {
40881			closeClick : true,      // if true, fancyBox will be closed when user clicks on the overlay
40882			speedOut   : 200,       // duration of fadeOut animation
40883			showEarly  : true,      // indicates if should be opened immediately or wait until the content is ready
40884			css        : {},        // custom CSS properties
40885			locked     : !isTouch,  // if true, the content will be locked into overlay
40886			fixed      : true       // if false, the overlay CSS position property will not be set to "fixed"
40887		},
40888
40889		overlay : null,      // current handle
40890		fixed   : false,     // indicates if the overlay has position "fixed"
40891		el      : $('html'), // element that contains "the lock"
40892
40893		// Public methods
40894		create : function(opts) {
40895			opts = $.extend({}, this.defaults, opts);
40896
40897			if (this.overlay) {
40898				this.close();
40899			}
40900
40901			this.overlay = $('<div class="fancybox-overlay"></div>').appendTo( F.coming ? F.coming.parent : opts.parent );
40902			this.fixed   = false;
40903
40904			if (opts.fixed && F.defaults.fixed) {
40905				this.overlay.addClass('fancybox-overlay-fixed');
40906
40907				this.fixed = true;
40908			}
40909		},
40910
40911		open : function(opts) {
40912			var that = this;
40913
40914			opts = $.extend({}, this.defaults, opts);
40915
40916			if (this.overlay) {
40917				this.overlay.unbind('.overlay').width('auto').height('auto');
40918
40919			} else {
40920				this.create(opts);
40921			}
40922
40923			if (!this.fixed) {
40924				W.bind('resize.overlay', $.proxy( this.update, this) );
40925
40926				this.update();
40927			}
40928
40929			if (opts.closeClick) {
40930				this.overlay.bind('click.overlay', function(e) {
40931					if ($(e.target).hasClass('fancybox-overlay')) {
40932						if (F.isActive) {
40933							F.close();
40934						} else {
40935							that.close();
40936						}
40937
40938						return false;
40939					}
40940				});
40941			}
40942
40943			this.overlay.css( opts.css ).show();
40944		},
40945
40946		close : function() {
40947			var scrollV, scrollH;
40948
40949			W.unbind('resize.overlay');
40950
40951			if (this.el.hasClass('fancybox-lock')) {
40952				$('.fancybox-margin').removeClass('fancybox-margin');
40953
40954				scrollV = W.scrollTop();
40955				scrollH = W.scrollLeft();
40956
40957				this.el.removeClass('fancybox-lock');
40958
40959				W.scrollTop( scrollV ).scrollLeft( scrollH );
40960			}
40961
40962			$('.fancybox-overlay').remove().hide();
40963
40964			$.extend(this, {
40965				overlay : null,
40966				fixed   : false
40967			});
40968		},
40969
40970		// Private, callbacks
40971
40972		update : function () {
40973			var width = '100%', offsetWidth;
40974
40975			// Reset width/height so it will not mess
40976			this.overlay.width(width).height('100%');
40977
40978			// jQuery does not return reliable result for IE
40979			if (IE) {
40980				offsetWidth = Math.max(document.documentElement.offsetWidth, document.body.offsetWidth);
40981
40982				if (D.width() > offsetWidth) {
40983					width = D.width();
40984				}
40985
40986			} else if (D.width() > W.width()) {
40987				width = D.width();
40988			}
40989
40990			this.overlay.width(width).height(D.height());
40991		},
40992
40993		// This is where we can manipulate DOM, because later it would cause iframes to reload
40994		onReady : function (opts, obj) {
40995			var overlay = this.overlay;
40996
40997			$('.fancybox-overlay').stop(true, true);
40998
40999			if (!overlay) {
41000				this.create(opts);
41001			}
41002
41003			if (opts.locked && this.fixed && obj.fixed) {
41004				if (!overlay) {
41005					this.margin = D.height() > W.height() ? $('html').css('margin-right').replace("px", "") : false;
41006				}
41007
41008				obj.locked = this.overlay.append( obj.wrap );
41009				obj.fixed  = false;
41010			}
41011
41012			if (opts.showEarly === true) {
41013				this.beforeShow.apply(this, arguments);
41014			}
41015		},
41016
41017		beforeShow : function(opts, obj) {
41018			var scrollV, scrollH;
41019
41020			if (obj.locked) {
41021				if (this.margin !== false) {
41022					$('*').filter(function(){
41023						return ($(this).css('position') === 'fixed' && !$(this).hasClass("fancybox-overlay") && !$(this).hasClass("fancybox-wrap") );
41024					}).addClass('fancybox-margin');
41025
41026					this.el.addClass('fancybox-margin');
41027				}
41028
41029				scrollV = W.scrollTop();
41030				scrollH = W.scrollLeft();
41031
41032				this.el.addClass('fancybox-lock');
41033
41034				W.scrollTop( scrollV ).scrollLeft( scrollH );
41035			}
41036
41037			this.open(opts);
41038		},
41039
41040		onUpdate : function() {
41041			if (!this.fixed) {
41042				this.update();
41043			}
41044		},
41045
41046		afterClose: function (opts) {
41047			// Remove overlay if exists and fancyBox is not opening
41048			// (e.g., it is not being open using afterClose callback)
41049			//if (this.overlay && !F.isActive) {
41050			if (this.overlay && !F.coming) {
41051				this.overlay.fadeOut(opts.speedOut, $.proxy( this.close, this ));
41052			}
41053		}
41054	};
41055
41056	/*
41057	 *	Title helper
41058	 */
41059
41060	F.helpers.title = {
41061		defaults : {
41062			type     : 'float', // 'float', 'inside', 'outside' or 'over',
41063			position : 'bottom' // 'top' or 'bottom'
41064		},
41065
41066		beforeShow: function (opts) {
41067			var current = F.current,
41068				text    = current.title,
41069				type    = opts.type,
41070				title,
41071				target;
41072
41073			if ($.isFunction(text)) {
41074				text = text.call(current.element, current);
41075			}
41076
41077			if (!isString(text) || $.trim(text) === '') {
41078				return;
41079			}
41080
41081			title = $('<div class="fancybox-title fancybox-title-' + type + '-wrap">
41081' + text + '</div>');
41082
41083			switch (type) {
41084				case 'inside':
41085					target = F.skin;
41086					break;
41087
41088				case 'outside':
41089					target = F.wrap;
41090					break;
41091
41092				case 'over':
41093					target = F.inner;
41094					break;
41095
41096				default: // 'float'
41097					target = F.skin;
41098
41099					title.appendTo('body');
41100
41101					if (IE) {
41102						title.width( title.width() );
41103					}
41104
41105					title.wrapInner('<span class="child"></span>');
41106
41107					//Increase bottom margin so this title will also fit into viewport
41108					F.current.margin[2] += Math.abs( getScalar(title.css('margin-bottom')) );
41109					break;
41110			}
41111
41112			title[ (opts.position === 'top' ? 'prependTo'  : 'appendTo') ](target);
41113		}
41114	};
41115
41116	// jQuery plugin initialization
41117	$.fn.fancybox = function (options) {
41118		var index,
41119			that     = $(this),
41120			selector = this.selector || '',
41121			run      = function(e) {
41122				var what = $(this).blur(), idx = index, relType, relVal;
41123
41124				if (!(e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) && !what.is('.fancybox-wrap')) {
41125					relType = options.groupAttr || 'data-fancybox-group';
41126					relVal  = what.attr(relType);
41127
41128					if (!relVal) {
41129						relType = 'rel';
41130						relVal  = what.get(0)[ relType ];
41131					}
41132
41133					if (relVal && relVal !== '' && relVal !== 'nofollow') {
41134						what = selector.length ? $(selector) : that;
41135						what = what.filter('[' + relType + '="' + relVal + '"]');
41136						idx  = what.index(this);
41137					}
41138
41139					options.index = idx;
41140
41141					// Stop an event from bubbling if everything is fine
41142					if (F.open(what, options) !== false) {
41143						e.preventDefault();
41144					}
41145				}
41146			};
41147
41148		options = options || {};
41149		index   = options.index || 0;
41150
41151		if (!selector || options.live === false) {
41152			that.unbind('click.fb-start').bind('click.fb-start', run);
41153
41154		} else {
41155			D.undelegate(selector, 'click.fb-start').delegate(selector + ":not('.fancybox-item, .fancybox-nav')", 'click.fb-start', run);
41156		}
41157
41158		this.filter('[data-fancybox-start=1]').trigger('click');
41159
41160		return this;
41161	};
41162
41163	// Tests that need a body at doc ready
41164	D.ready(function() {
41165		var w1, w2;
41166
41167		if ( $.scrollbarWidth === undefined ) {
41168			// http://benalman.com/projects/jquery-misc-plugins/#scrollbarwidth
41169			$.scrollbarWidth = function() {
41170				var parent = $('<div style="width:50px;height:50px;overflow:auto"><div/></div>').appendTo('body'),
41171					child  = parent.children(),
41172					width  = child.innerWidth() - child.height( 99 ).innerWidth();
41173
41174				parent.remove();
41175
41176				return width;
41177			};
41178		}
41179
41180		if ( $.support.fixedPosition === undefined ) {
41181			$.support.fixedPosition = (function() {
41182				var elem  = $('<div style="position:fixed;top:20px;"></div>').appendTo('body'),
41183					fixed = ( elem[0].offsetTop === 20 || elem[0].offsetTop === 15 );
41184
41185				elem.remove();
41186
41187				return fixed;
41188			}());
41189		}
41190
41191		$.extend(F.defaults, {
41192			scrollbarWidth : $.scrollbarWidth(),
41193			fixed  : $.support.fixedPosition,
41194			parent : $('body')
41195		});
41196
41197		//Get real width of page scroll-bar
41198		w1 = $(window).width();
41199
41200		H.addClass('fancybox-lock-test');
41201
41202		w2 = $(window).width();
41203
41204		H.removeClass('fancybox-lock-test');
41205
41206		$("<style type='text/css'>.fancybox-margin{margin-right:" + (w2 - w1) + "px;}</style>").appendTo("head");
41207	});
41208
41209}(window, document, jQuery));
41210(function() {
41211  var base,
41212    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
41213    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
41214    hasProp = {}.hasOwnProperty;
41215
41216  if ((base = App.Pages).Blog == null) {
41217    base.Blog = {};
41218  }
41219
41220  App.Pages.Blog.PostsShow = (function(superClass) {
41221    extend(PostsShow, superClass);
41222
41223    function PostsShow() {
41224      this.onNextQuestionClick = bind(this.onNextQuestionClick, this);
41225      this.onAnswerClick = bind(this.onAnswerClick, this);
41226      return PostsShow.__super__.constructor.apply(this, arguments);
41227    }
41228
41229    PostsShow.prototype.widgets = ['comments'];
41230
41231    PostsShow.prototype.initialize = function() {
41232      this.initPlugins();
41233      if ($('.quiz').length !== 0) {
41234        return this.initQuiz();
41235      }
41236    };
41237
41238    PostsShow.prototype.initPlugins = function() {
41239      return $('img.with-preview').fancybox({
41240        autoScale: true,
41241        padding: 0,
41242        closeClick: true,
41243        beforeLoad: function() {
41244          var url;
41245          url = $(this.element).data('target');
41246          return this.href = url;
41247        },
41248        afterClose: function() {
41249          return $(this.element).show();
41250        }
41251      });
41252    };
41253
41254    PostsShow.prototype.initQuiz = function() {
41255      var baseUrl;
41256      this.currentQuestion = 0;
41257      this.answers = {};
41258      this.answerCounter = {
41259        correct: 0,
41260        failed: 0
41261      };
41262      this.quizId = $('.quiz').data('id');
41263      baseUrl = "https://" + location.host;
41264      $("a[href^='http:']:not([href*='" + baseUrl + "']), a[href^='https:']:not([href*='" + baseUrl + "']), a[href$='.pdf']:not([href*='" + baseUrl + "']), a[href$='.pdf']").on('click', this.onLinkClick);
41265      $('.js-quiz-trigger').on('click', this.onQuizClick);
41266      $('.js-quiz-answer').on('click', this.onAnswerClick);
41267      $('.js-quiz-continue').on('click', this.onNextQuestionClick);
41268      return $('.quiz-entry.is-init').imagesLoaded(function() {
41269        return $('.quiz-entry__actions').css('top', $('.quiz-entry.is-init').height() / 2);
41270      });
41271    };
41272
41273    PostsShow.prototype.answerCorrect = function(answer) {
41274      return gon.quiz[this.currentQuestion].answer === answer;
41275    };
41276
41277    PostsShow.prototype.onQuizClick = function(e) {
41278      $('.quiz').fadeIn();
41279      $('body').addClass('is-quiz');
41280      App.events.publish('app:element:focus', $('.quiz-entry.is-init'));
41281      $('.quiz-entry.is-init').addClass('is-active').removeClass('is-init');
41282      return e.preventDefault();
41283    };
41284
41285    PostsShow.prototype.onAnswerClick = function(e) {
41286      var $el, $text, answer;
41287      $el = $(e.currentTarget);
41288      answer = parseInt($el.data('answer'), 10);
41289      $text = $('.quiz-entry.is-active .quiz-entry__text');
41290      if (this.answerCorrect(answer)) {
41291        $text.html("<b class='quiz-entry__success'><i>" + (this.currentQuestion + 1) + ".</i> 110%!</b> " + gon.quiz[this.currentQuestion].success_text).addClass('is-success');
41292        this.answers[this.currentQuestion] = 1;
41293        this.answerCounter.correct++;
41294      } else {
41295        $text.html("<b class='quiz-entry__fail'><i>" + (this.currentQuestion + 1) + ".</i> Gotcha!</b> " + gon.quiz[this.currentQuestion].fail_text).addClass('is-fail');
41296        this.answers[this.currentQuestion] = 0;
41297        this.answerCounter.failed++;
41298      }
41299      $('.js-answer-buttons').fadeOut().promise().done(function() {
41300        var offset;
41301        offset = $('.quiz-entry.is-active').height() >= $(window).height() ? $('.quiz-entry.is-active').height() - $(window).height() : 0;
41302        $('html, body').animate({
41303          scrollTop: $('.quiz-entry.is-active').offset().top + $text.height() + offset + $('.quiz-actions').outerHeight() * 2
41304        }, 600);
41305        return $('.js-quiz-continue').hide().removeClass('is-hidden').fadeIn();
41306      });
41307      return e.preventDefault();
41308    };
41309
41310    PostsShow.prototype.onNextQuestionClick = function(e) {
41311      this.currentQuestion++;
41312      $('.js-quiz-continue').fadeOut().promise().done((function(_this) {
41313        return function() {
41314          var $current, $next, $summary;
41315          $current = $('.quiz-entry.is-active');
41316          $next = $current.next();
41317          if ($next.length > 0) {
41318            $current.removeClass('is-active').addClass('is-answered');
41319            $next.addClass('is-active');
41320            if ($next.next().length === 0) {
41321              $('.js-quiz-continue').text('View quiz results');
41322            }
41323            $('.js-answer-buttons').fadeIn();
41324            return App.events.publish('app:element:focus', $next);
41325          } else {
41326            $summary = $('.quiz-summary');
41327            $summary.find('.js-quiz-correct').text(_this.answerCounter.correct);
41328            $summary.find('.js-quiz-total').text(_this.answerCounter.correct + _this.answerCounter.failed);
41329            return $summary.hide().removeClass('is-hidden').fadeIn().promise().done(function() {
41330              App.Mediator.publish('app:element:focus', $summary);
41331              return $('body').removeClass('is-quiz');
41332            });
41333          }
41334        };
41335      })(this));
41336      return e.preventDefault();
41337    };
41338
41339    PostsShow.prototype.onLinkClick = function(e) {
41340      return $(this).attr('target', '_blank');
41341    };
41342
41343    return PostsShow;
41344
41345  })(App.Core.Page);
41346
41347}).call(this);
41348/*
41349 * jQuery EasyTabs plugin 3.2.0
41350 *
41351 * Copyright (c) 2010-2011 Steve Schwartz (JangoSteve)
41352 *
41353 * Dual licensed under the MIT and GPL licenses:
41354 *   http://www.opensource.org/licenses/mit-license.php
41355 *   http://www.gnu.org/licenses/gpl.html
41356 *
41357 * Date: Thu May 09 17:30:00 2013 -0500
41358 */
41359
41360( function($) {
41361
41362  $.easytabs = function(container, options) {
41363
41364        // Attach to plugin anything that should be available via
41365        // the $container.data('easytabs') object
41366    var plugin = this,
41367        $container = $(container),
41368
41369        defaults = {
41370          animate: true,
41371          panelActiveClass: "active",
41372          tabActiveClass: "active",
41373          defaultTab: "li:first-child",
41374          animationSpeed: "normal",
41375          tabs: "> ul > li",
41376          updateHash: true,
41377          cycle: false,
41378          collapsible: false,
41379          collapsedClass: "collapsed",
41380          collapsedByDefault: true,
41381          uiTabs: false,
41382          transitionIn: 'fadeIn',
41383          transitionOut: 'fadeOut',
41384          transitionInEasing: 'swing',
41385          transitionOutEasing: 'swing',
41386          transitionCollapse: 'slideUp',
41387          transitionUncollapse: 'slideDown',
41388          transitionCollapseEasing: 'swing',
41389          transitionUncollapseEasing: 'swing',
41390          containerClass: "",
41391          tabsClass: "",
41392          tabClass: "",
41393          panelClass: "",
41394          cache: true,
41395          event: 'click',
41396          panelContext: $container
41397        },
41398
41399        // Internal instance variables
41400        // (not available via easytabs object)
41401        $defaultTab,
41402        $defaultTabLink,
41403        transitions,
41404        lastHash,
41405        skipUpdateToHash,
41406        animationSpeeds = {
41407          fast: 200,
41408          normal: 400,
41409          slow: 600
41410        },
41411
41412        // Shorthand variable so that we don't need to call
41413        // plugin.settings throughout the plugin code
41414        settings;
41415
41416    // =============================================================
41417    // Functions available via easytabs object
41418    // =============================================================
41419
41420    plugin.init = function() {
41421
41422      plugin.settings = settings = $.extend({}, defaults, options);
41423      settings.bind_str = settings.event+".easytabs";
41424
41425      // Add jQuery UI's crazy class names to markup,
41426      // so that markup will match theme CSS
41427      if ( settings.uiTabs ) {
41428        settings.tabActiveClass = 'ui-tabs-selected';
41429        settings.containerClass = 'ui-tabs ui-widget ui-widget-content ui-corner-all';
41430        settings.tabsClass = 'ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all';
41431        settings.tabClass = 'ui-state-default ui-corner-top';
41432        settings.panelClass = 'ui-tabs-panel ui-widget-content ui-corner-bottom';
41433      }
41434
41435      // If collapsible is true and defaultTab specified, assume user wants defaultTab showing (not collapsed)
41436      if ( settings.collapsible && options.defaultTab !== undefined && options.collpasedByDefault === undefined ) {
41437        settings.collapsedByDefault = false;
41438      }
41439
41440      // Convert 'normal', 'fast', and 'slow' animation speed settings to their respective speed in milliseconds
41441      if ( typeof(settings.animationSpeed) === 'string' ) {
41442        settings.animationSpeed = animationSpeeds[settings.animationSpeed];
41443      }
41444
41445      $('a.anchor').remove().prependTo('body');
41446
41447      // Store easytabs object on container so we can easily set
41448      // properties throughout
41449      $container.data('easytabs', {});
41450
41451      plugin.setTransitions();
41452
41453      plugin.getTabs();
41454
41455      addClasses();
41456
41457      setDefaultTab();
41458
41459      bindToTabClicks();
41460
41461      initHashChange();
41462
41463      initCycle();
41464
41465      // Append data-easytabs HTML attribute to make easy to query for
41466      // easytabs instances via CSS pseudo-selector
41467      $container.attr('data-easytabs', true);
41468    };
41469
41470    // Set transitions for switching between tabs based on options.
41471    // Could be used to update transitions if settings are changes.
41472    plugin.setTransitions = function() {
41473      transitions = ( settings.animate ) ? {
41474          show: settings.transitionIn,
41475          hide: settings.transitionOut,
41476          speed: settings.animationSpeed,
41477          collapse: settings.transitionCollapse,
41478          uncollapse: settings.transitionUncollapse,
41479          halfSpeed: settings.animationSpeed / 2
41480        } :
41481        {
41482          show: "show",
41483          hide: "hide",
41484          speed: 0,
41485          collapse: "hide",
41486          uncollapse: "show",
41487          halfSpeed: 0
41488        };
41489    };
41490
41491    // Find and instantiate tabs and panels.
41492    // Could be used to reset tab and panel collection if markup is
41493    // modified.
41494    plugin.getTabs = function() {
41495      var $matchingPanel;
41496
41497      // Find the initial set of elements matching the setting.tabs
41498      // CSS selector within the container
41499      plugin.tabs = $container.find(settings.tabs),
41500
41501      // Instantiate panels as empty jquery object
41502      plugin.panels = $(),
41503
41504      plugin.tabs.each(function(){
41505        var $tab = $(this),
41506            $a = $tab.children('a'),
41507
41508            // targetId is the ID of the panel, which is either the
41509            // `href` attribute for non-ajax tabs, or in the
41510            // `data-target` attribute for ajax tabs since the `href` is
41511            // the ajax URL
41512            targetId = $tab.children('a').data('target');
41513
41514        $tab.data('easytabs', {});
41515
41516        // If the tab has a `data-target` attribute, and is thus an ajax tab
41517        if ( targetId !== undefined && targetId !== null ) {
41518          $tab.data('easytabs').ajax = $a.attr('href');
41519        } else {
41520          targetId = $a.attr('href');
41521        }
41522        targetId = targetId.match(/#([^\?]+)/)[1];
41523
41524        $matchingPanel = settings.panelContext.find("#" + targetId);
41525
41526        // If tab has a matching panel, add it to panels
41527        if ( $matchingPanel.length ) {
41528
41529          // Store panel height before hiding
41530          $matchingPanel.data('easytabs', {
41531            position: $matchingPanel.css('position'),
41532            visibility: $matchingPanel.css('visibility')
41533          });
41534
41535          // Don't hide panel if it's active (allows `getTabs` to be called manually to re-instantiate tab collection)
41536          $matchingPanel.not(settings.panelActiveClass).hide();
41537
41538          plugin.panels = plugin.panels.add($matchingPanel);
41539
41540          $tab.data('easytabs').panel = $matchingPanel;
41541
41542        // Otherwise, remove tab from tabs collection
41543        } else {
41544          plugin.tabs = plugin.tabs.not($tab);
41545          if ('console' in window) {
41546            console.warn('Warning: tab without matching panel for selector \'#' + targetId +'\' removed from set');
41547          }
41548        }
41549      });
41550    };
41551
41552    // Select tab and fire callback
41553    plugin.selectTab = function($clicked, callback) {
41554      var url = window.location,
41555          hash = url.hash.match(/^[^\?]*/)[0],
41556          $targetPanel = $clicked.parent().data('easytabs').panel,
41557          ajaxUrl = $clicked.parent().data('easytabs').ajax;
41558
41559      // Tab is collapsible and active => toggle collapsed state
41560      if( settings.collapsible && ! skipUpdateToHash && ($clicked.hasClass(settings.tabActiveClass) || $clicked.hasClass(settings.collapsedClass)) ) {
41561        plugin.toggleTabCollapse($clicked, $targetPanel, ajaxUrl, callback);
41562
41563      // Tab is not active and panel is not active => select tab
41564      } else if( ! $clicked.hasClass(settings.tabActiveClass) || ! $targetPanel.hasClass(settings.panelActiveClass) ){
41565        activateTab($clicked, $targetPanel, ajaxUrl, callback);
41566
41567      // Cache is disabled => reload (e.g reload an ajax tab).
41568      } else if ( ! settings.cache ){
41569        activateTab($clicked, $targetPanel, ajaxUrl, callback);
41570      }
41571
41572    };
41573
41574    // Toggle tab collapsed state and fire callback
41575    plugin.toggleTabCollapse = function($clicked, $targetPanel, ajaxUrl, callback) {
41576      plugin.panels.stop(true,true);
41577
41578      if( fire($container,"easytabs:before", [$clicked, $targetPanel, settings]) ){
41579        plugin.tabs.filter("." + settings.tabActiveClass).removeClass(settings.tabActiveClass).children().removeClass(settings.tabActiveClass);
41580
41581        // If panel is collapsed, uncollapse it
41582        if( $clicked.hasClass(settings.collapsedClass) ){
41583
41584          // If ajax panel and not already cached
41585          if( ajaxUrl && (!settings.cache || !$clicked.parent().data('easytabs').cached) ) {
41586            $container.trigger('easytabs:ajax:beforeSend', [$clicked, $targetPanel]);
41587
41588            $targetPanel.load(ajaxUrl, function(response, status, xhr){
41589              $clicked.parent().data('easytabs').cached = true;
41590              $container.trigger('easytabs:ajax:complete', [$clicked, $targetPanel, response, status, xhr]);
41591            });
41592          }
41593
41594          // Update CSS classes of tab and panel
41595          $clicked.parent()
41596            .removeClass(settings.collapsedClass)
41597            .addClass(settings.tabActiveClass)
41598            .children()
41599              .removeClass(settings.collapsedClass)
41600              .addClass(settings.tabActiveClass);
41601
41602          $targetPanel
41603            .addClass(settings.panelActiveClass)
41604            [transitions.uncollapse](transitions.speed, settings.transitionUncollapseEasing, function(){
41605              $container.trigger('easytabs:midTransition', [$clicked, $targetPanel, settings]);
41606              if(typeof callback == 'function') callback();
41607            });
41608
41609        // Otherwise, collapse it
41610        } else {
41611
41612          // Update CSS classes of tab and panel
41613          $clicked.addClass(settings.collapsedClass)
41614            .parent()
41615              .addClass(settings.collapsedClass);
41616
41617          $targetPanel
41618            .removeClass(settings.panelActiveClass)
41619            [transitions.collapse](transitions.speed, settings.transitionCollapseEasing, function(){
41620              $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, settings]);
41621              if(typeof callback == 'function') callback();
41622            });
41623        }
41624      }
41625    };
41626
41627
41628    // Find tab with target panel matching value
41629    plugin.matchTab = function(hash) {
41630      return plugin.tabs.find("[href='" + hash + "'],[data-target='" + hash + "']").first();
41631    };
41632
41633    // Find panel with `id` matching value
41634    plugin.matchInPanel = function(hash) {
41635      return ( hash && plugin.validId(hash) ? plugin.panels.filter(':has(' + hash + ')').first() : [] );
41636    };
41637
41638    // Make sure hash is a valid id value (admittedly strict in that HTML5 allows almost anything without a space)
41639    // but jQuery has issues with such id values anyway, so we can afford to be strict here.
41640    plugin.validId = function(id) {
41641      return id.substr(1).match(/^[A-Za-z][A-Za-z0-9\-_:\.]*$/);
41642    };
41643
41644    // Select matching tab when URL hash changes
41645    plugin.selectTabFromHashChange = function() {
41646      var hash = window.location.hash.match(/^[^\?]*/)[0],
41647          $tab = plugin.matchTab(hash),
41648          $panel;
41649
41650      if ( settings.updateHash ) {
41651
41652        // If hash directly matches tab
41653        if( $tab.length ){
41654          skipUpdateToHash = true;
41655          plugin.selectTab( $tab );
41656
41657        } else {
41658          $panel = plugin.matchInPanel(hash);
41659
41660          // If panel contains element matching hash
41661          if ( $panel.length ) {
41662            hash = '#' + $panel.attr('id');
41663            $tab = plugin.matchTab(hash);
41664            skipUpdateToHash = true;
41665            plugin.selectTab( $tab );
41666
41667          // If default tab is not active...
41668          } else if ( ! $defaultTab.hasClass(settings.tabActiveClass) && ! settings.cycle ) {
41669
41670            // ...and hash is blank or matches a parent of the tab container or
41671            // if the last tab (before the hash updated) was one of the other tabs in this container.
41672            if ( hash === '' || plugin.matchTab(lastHash).length || $container.closest(hash).length ) {
41673              skipUpdateToHash = true;
41674              plugin.selectTab( $defaultTabLink );
41675            }
41676          }
41677        }
41678      }
41679    };
41680
41681    // Cycle through tabs
41682    plugin.cycleTabs = function(tabNumber){
41683      if(settings.cycle){
41684        tabNumber = tabNumber % plugin.tabs.length;
41685        $tab = $( plugin.tabs[tabNumber] ).children("a").first();
41686        skipUpdateToHash = true;
41687        plugin.selectTab( $tab, function() {
41688          setTimeout(function(){ plugin.cycleTabs(tabNumber + 1); }, settings.cycle);
41689        });
41690      }
41691    };
41692
41693    // Convenient public methods
41694    plugin.publicMethods = {
41695      select: function(tabSelector){
41696        var $tab;
41697
41698        // Find tab container that matches selector (like 'li#tab-one' which contains tab link)
41699        if ( ($tab = plugin.tabs.filter(tabSelector)).length === 0 ) {
41700
41701          // Find direct tab link that matches href (like 'a[href="#panel-1"]')
41702          if ( ($tab = plugin.tabs.find("a[href='" + tabSelector + "']")).length === 0 ) {
41703
41704            // Find direct tab link that matches selector (like 'a#tab-1')
41705            if ( ($tab = plugin.tabs.find("a" + tabSelector)).length === 0 ) {
41706
41707              // Find direct tab link that matches data-target (lik 'a[data-target="#panel-1"]')
41708              if ( ($tab = plugin.tabs.find("[data-target='" + tabSelector + "']")).length === 0 ) {
41709
41710                // Find direct tab link that ends in the matching href (like 'a[href$="#panel-1"]', which would also match http://example.com/currentpage/#panel-1)
41711                if ( ($tab = plugin.tabs.find("a[href$='" + tabSelector + "']")).length === 0 ) {
41712
41713                  $.error('Tab \'' + tabSelector + '\' does not exist in tab set');
41714                }
41715              }
41716            }
41717          }
41718        } else {
41719          // Select the child tab link, since the first option finds the tab container (like <li>)
41720          $tab = $tab.children("a").first();
41721        }
41722        plugin.selectTab($tab);
41723      }
41724    };
41725
41726    // =============================================================
41727    // Private functions
41728    // =============================================================
41729
41730    // Triggers an event on an element and returns the event result
41731    var fire = function(obj, name, data) {
41732      var event = $.Event(name);
41733      obj.trigger(event, data);
41734      return event.result !== false;
41735    }
41736
41737    // Add CSS classes to markup (if specified), called by init
41738    var addClasses = function() {
41739      $container.addClass(settings.containerClass);
41740      plugin.tabs.parent().addClass(settings.tabsClass);
41741      plugin.tabs.addClass(settings.tabClass);
41742      plugin.panels.addClass(settings.panelClass);
41743    };
41744
41745    // Set the default tab, whether from hash (bookmarked) or option,
41746    // called by init
41747    var setDefaultTab = function(){
41748      var hash = window.location.hash.match(/^[^\?]*/)[0],
41749          $selectedTab = plugin.matchTab(hash).parent(),
41750          $panel;
41751
41752      // If hash directly matches one of the tabs, active on page-load
41753      if( $selectedTab.length === 1 ){
41754        $defaultTab = $selectedTab;
41755        settings.cycle = false;
41756
41757      } else {
41758        $panel = plugin.matchInPanel(hash);
41759
41760        // If one of the panels contains the element matching the hash,
41761        // make it active on page-load
41762        if ( $panel.length ) {
41763          hash = '#' + $panel.attr('id');
41764          $defaultTab = plugin.matchTab(hash).parent();
41765
41766        // Otherwise, make the default tab the one that's active on page-load
41767        } else {
41768          $defaultTab = plugin.tabs.parent().find(settings.defaultTab);
41769          if ( $defaultTab.length === 0 ) {
41770            $.error("The specified default tab ('" + settings.defaultTab + "') could not be found in the tab set ('" + settings.tabs + "') out of " + plugin.tabs.length + " tabs.");
41771          }
41772        }
41773      }
41774
41775      $defaultTabLink = $defaultTab.children("a").first();
41776
41777      activateDefaultTab($selectedTab);
41778    };
41779
41780    // Activate defaultTab (or collapse by default), called by setDefaultTab
41781    var activateDefaultTab = function($selectedTab) {
41782      var defaultPanel,
41783          defaultAjaxUrl;
41784
41785      if ( settings.collapsible && $selectedTab.length === 0 && settings.collapsedByDefault ) {
41786        $defaultTab
41787          .addClass(settings.collapsedClass)
41788          .children()
41789            .addClass(settings.collapsedClass);
41790
41791      } else {
41792
41793        defaultPanel = $( $defaultTab.data('easytabs').panel );
41794        defaultAjaxUrl = $defaultTab.data('easytabs').ajax;
41795
41796        if ( defaultAjaxUrl && (!settings.cache || !$defaultTab.data('easytabs').cached) ) {
41797          $container.trigger('easytabs:ajax:beforeSend', [$defaultTabLink, defaultPanel]);
41798          defaultPanel.load(defaultAjaxUrl, function(response, status, xhr){
41799            $defaultTab.data('easytabs').cached = true;
41800            $container.trigger('easytabs:ajax:complete', [$defaultTabLink, defaultPanel, response, status, xhr]);
41801          });
41802        }
41803
41804        $defaultTab.data('easytabs').panel
41805          .show()
41806          .addClass(settings.panelActiveClass);
41807
41808        $defaultTab
41809          .addClass(settings.tabActiveClass)
41810          .children()
41811            .addClass(settings.tabActiveClass);
41812      }
41813
41814      // Fire event when the plugin is initialised
41815      $container.trigger("easytabs:initialised", [$defaultTabLink, defaultPanel]);
41816    };
41817
41818    // Bind tab-select funtionality to namespaced click event, called by
41819    // init
41820    var bindToTabClicks = function() {
41821      plugin.tabs.children("a").bind(settings.bind_str, function(e) {
41822
41823        // Stop cycling when a tab is clicked
41824        settings.cycle = false;
41825
41826        // Hash will be updated when tab is clicked,
41827        // don't cause tab to re-select when hash-change event is fired
41828        skipUpdateToHash = false;
41829
41830        // Select the panel for the clicked tab
41831        plugin.selectTab( $(this) );
41832
41833        // Don't follow the link to the anchor
41834        e.preventDefault ? e.preventDefault() : e.returnValue = false;
41835      });
41836    };
41837
41838    // Activate a given tab/panel, called from plugin.selectTab:
41839    //
41840    //   * fire `easytabs:before` hook
41841    //   * get ajax if new tab is an uncached ajax tab
41842    //   * animate out previously-active panel
41843    //   * fire `easytabs:midTransition` hook
41844    //   * update URL hash
41845    //   * animate in newly-active panel
41846    //   * update CSS classes for inactive and active tabs/panels
41847    //
41848    // TODO: This could probably be broken out into many more modular
41849    // functions
41850    var activateTab = function($clicked, $targetPanel, ajaxUrl, callback) {
41851      plugin.panels.stop(true,true);
41852
41853      if( fire($container,"easytabs:before", [$clicked, $targetPanel, settings]) ){
41854        var $visiblePanel = plugin.panels.filter(":visible"),
41855            $panelContainer = $targetPanel.parent(),
41856            targetHeight,
41857            visibleHeight,
41858            heightDifference,
41859            showPanel,
41860            hash = window.location.hash.match(/^[^\?]*/)[0];
41861
41862        if (settings.animate) {
41863          targetHeight = getHeightForHidden($targetPanel);
41864          visibleHeight = $visiblePanel.length ? setAndReturnHeight($visiblePanel) : 0;
41865          heightDifference = targetHeight - visibleHeight;
41866        }
41867
41868        // Set lastHash to help indicate if defaultTab should be
41869        // activated across multiple tab instances.
41870        lastHash = hash;
41871
41872        // TODO: Move this function elsewhere
41873        showPanel = function() {
41874          // At this point, the previous panel is hidden, and the new one will be selected
41875          $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, settings]);
41876
41877          // Gracefully animate between panels of differing heights, start height change animation *after* panel change if panel needs to contract,
41878          // so that there is no chance of making the visible panel overflowing the height of the target panel
41879          if (settings.animate && settings.transitionIn == 'fadeIn') {
41880            if (heightDifference < 0)
41881              $panelContainer.animate({
41882                height: $panelContainer.height() + heightDifference
41883              }, transitions.halfSpeed ).css({ 'min-height': '' });
41884          }
41885
41886          if ( settings.updateHash && ! skipUpdateToHash ) {
41887            //window.location = url.toString().replace((url.pathname + hash), (url.pathname + $clicked.attr("href")));
41888            // Not sure why this behaves so differently, but it's more straight forward and seems to have less side-effects
41889            window.location.hash = '#' + $targetPanel.attr('id');
41890          } else {
41891            skipUpdateToHash = false;
41892          }
41893
41894          $targetPanel
41895            [transitions.show](transitions.speed, settings.transitionInEasing, function(){
41896              $panelContainer.css({height: '', 'min-height': ''}); // After the transition, unset the height
41897              $container.trigger("easytabs:after", [$clicked, $targetPanel, settings]);
41898              // callback only gets called if selectTab actually does something, since it's inside the if block
41899              if(typeof callback == 'function'){
41900                callback();
41901              }
41902          });
41903        };
41904
41905        if ( ajaxUrl && (!settings.cache || !$clicked.parent().data('easytabs').cached) ) {
41906          $container.trigger('easytabs:ajax:beforeSend', [$clicked, $targetPanel]);
41907          $targetPanel.load(ajaxUrl, function(response, status, xhr){
41908            $clicked.parent().data('easytabs').cached = true;
41909            $container.trigger('easytabs:ajax:complete', [$clicked, $targetPanel, response, status, xhr]);
41910          });
41911        }
41912
41913        // Gracefully animate between panels of differing heights, start height change animation *before* panel change if panel needs to expand,
41914        // so that there is no chance of making the target panel overflowing the height of the visible panel
41915        if( settings.animate && settings.transitionOut == 'fadeOut' ) {
41916          if( heightDifference > 0 ) {
41917            $panelContainer.animate({
41918              height: ( $panelContainer.height() + heightDifference )
41919            }, transitions.halfSpeed );
41920          } else {
41921            // Prevent height jumping before height transition is triggered at midTransition
41922            $panelContainer.css({ 'min-height': $panelContainer.height() });
41923          }
41924        }
41925
41926        // Change the active tab *first* to provide immediate feedback when the user clicks
41927        plugin.tabs.filter("." + settings.tabActiveClass).removeClass(settings.tabActiveClass).children().removeClass(settings.tabActiveClass);
41928        plugin.tabs.filter("." + settings.collapsedClass).removeClass(settings.collapsedClass).children().removeClass(settings.collapsedClass);
41929        $clicked.parent().addClass(settings.tabActiveClass).children().addClass(settings.tabActiveClass);
41930
41931        plugin.panels.filter("." + settings.panelActiveClass).removeClass(settings.panelActiveClass);
41932        $targetPanel.addClass(settings.panelActiveClass);
41933
41934        if( $visiblePanel.length ) {
41935          $visiblePanel
41936            [transitions.hide](transitions.speed, settings.transitionOutEasing, showPanel);
41937        } else {
41938          $targetPanel
41939            [transitions.uncollapse](transitions.speed, settings.transitionUncollapseEasing, showPanel);
41940        }
41941      }
41942    };
41943
41944    // Get heights of panels to enable animation between panels of
41945    // differing heights, called by activateTab
41946    var getHeightForHidden = function($targetPanel){
41947
41948      if ( $targetPanel.data('easytabs') && $targetPanel.data('easytabs').lastHeight ) {
41949        return $targetPanel.data('easytabs').lastHeight;
41950      }
41951
41952      // this is the only property easytabs changes, so we need to grab its value on each tab change
41953      var display = $targetPanel.css('display'),
41954          outerCloak,
41955          height;
41956
41957      // Workaround with wrapping height, because firefox returns wrong
41958      // height if element itself has absolute positioning.
41959      // but try/catch block needed for IE7 and IE8 because they throw
41960      // an "Unspecified error" when trying to create an element
41961      // with the css position set.
41962      try {
41963        outerCloak = $('<div></div>', {'position': 'absolute', 'visibility': 'hidden', 'overflow': 'hidden'});
41964      } catch (e) {
41965        outerCloak = $('<div></div>', {'visibility': 'hidden', 'overflow': 'hidden'});
41966      }
41967      height = $targetPanel
41968        .wrap(outerCloak)
41969        .css({'position':'relative','visibility':'hidden','display':'block'})
41970        .outerHeight();
41971
41972      $targetPanel.unwrap();
41973
41974      // Return element to previous state
41975      $targetPanel.css({
41976        position: $targetPanel.data('easytabs').position,
41977        visibility: $targetPanel.data('easytabs').visibility,
41978        display: display
41979      });
41980
41981      // Cache height
41982      $targetPanel.data('easytabs').lastHeight = height;
41983
41984      return height;
41985    };
41986
41987    // Since the height of the visible panel may have been manipulated due to interaction,
41988    // we want to re-cache the visible height on each tab change, called
41989    // by activateTab
41990    var setAndReturnHeight = function($visiblePanel) {
41991      var height = $visiblePanel.outerHeight();
41992
41993      if( $visiblePanel.data('easytabs') ) {
41994        $visiblePanel.data('easytabs').lastHeight = height;
41995      } else {
41996        $visiblePanel.data('easytabs', {lastHeight: height});
41997      }
41998      return height;
41999    };
42000
42001    // Setup hash-change callback for forward- and back-button
42002    // functionality, called by init
42003    var initHashChange = function(){
42004
42005      // enabling back-button with jquery.hashchange plugin
42006      // http://benalman.com/projects/jquery-hashchange-plugin/
42007      if(typeof $(window).hashchange === 'function'){
42008        $(window).hashchange( function(){
42009          plugin.selectTabFromHashChange();
42010        });
42011      } else if ($.address && typeof $.address.change === 'function') { // back-button with jquery.address plugin http://www.asual.com/jquery/address/docs/
42012        $.address.change( function(){
42013          plugin.selectTabFromHashChange();
42014        });
42015      }
42016    };
42017
42018    // Begin cycling if set in options, called by init
42019    var initCycle = function(){
42020      var tabNumber;
42021      if (settings.cycle) {
42022        tabNumber = plugin.tabs.index($defaultTab);
42023        setTimeout( function(){ plugin.cycleTabs(tabNumber + 1); }, settings.cycle);
42024      }
42025    };
42026
42027
42028    plugin.init();
42029
42030  };
42031
42032  $.fn.easytabs = function(options) {
42033    var args = arguments;
42034
42035    return this.each(function() {
42036      var $this = $(this),
42037          plugin = $this.data('easytabs');
42038
42039      // Initialization was called with $(el).easytabs( { options } );
42040      if (undefined === plugin) {
42041        plugin = new $.easytabs(this, options);
42042        $this.data('easytabs', plugin);
42043      }
42044
42045      // User called public method
42046      if ( plugin.publicMethods[options] ){
42047        return plugin.publicMethods[options](Array.prototype.slice.call( args, 1 ));
42048      }
42049    });
42050  };
42051
42052})(jQuery);
42053(function() {
42054  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
42055    hasProp = {}.hasOwnProperty;
42056
42057  App.Pages.BloggingContestIndex = (function(superClass) {
42058    extend(BloggingContestIndex, superClass);
42059
42060    function BloggingContestIndex() {
42061      return BloggingContestIndex.__super__.constructor.apply(this, arguments);
42062    }
42063
42064    BloggingContestIndex.prototype.initialize = function() {
42065      this.initTabs();
42066      this.$submissionForm = $('#blogging-contest-form');
42067      $('.js-scroll-to-form').on('click', this.scrollToSubmissionForm);
42068      return this.initValidation();
42069    };
42070
42071    BloggingContestIndex.prototype.initTabs = function() {
42072      return $('.js-blogging-contest-easytabs').easytabs({
42073        animate: true,
42074        animationSpeed: 250,
42075        panelActiveClass: 'is-active',
42076        panelContext: $('.js-blogging-contest-sections'),
42077        tabActiveClass: 'is-active'
42078      });
42079    };
42080
42081    BloggingContestIndex.prototype.scrollToSubmissionForm = function() {
42082      $('.js-blogging-contest-easytabs').easytabs('select', '#section-submission');
42083      return setTimeout(function() {
42084        return $('body').scrollTo('#section-submission');
42085      }, 250);
42086    };
42087
42088    BloggingContestIndex.prototype.validationSettings = function() {
42089      return {
42090        rules: {
42091          'blogging_contest_application[name]': {
42092            required: true
42093          },
42094          'blogging_contest_application[surname]': {
42095            required: true
42096          },
42097          'blogging_contest_application[email]': {
42098            required: true,
42099            email: true
42100          },
42101          'blogging_contest_application[country]': {
42102            required: true
42103          },
42104          'blogging_contest_application[post_link]': {
42105            required: true
42106          }
42107        }
42108      };
42109    };
42110
42111    BloggingContestIndex.prototype.initValidation = function() {
42112      return this.$submissionForm.validate(this.validationSettings());
42113    };
42114
42115    return BloggingContestIndex;
42116
42117  })(App.Core.Page);
42118
42119}).call(this);
42120/*! sly 1.6.1 - 8th Aug 2015 | https://github.com/darsain/sly */
42121
42122!function(a,b,c){"use strict";function d(b,p,q){function K(c){var d=0,e=Gb.length;if(yb.old=a.extend({},yb),wb=tb?0:ub[rb.horizontal?"width":"height"](),Bb=zb[rb.horizontal?"width":"height"](),xb=tb?b:vb[rb.horizontal?"outerWidth":"outerHeight"](),Gb.length=0,yb.start=0,yb.end=H(xb-wb,0),Rb){d=Ib.length,Hb=vb.children(rb.itemSelector),Ib.length=0;var f,g=j(vb,rb.horizontal?"paddingLeft":"paddingTop"),h=j(vb,rb.horizontal?"paddingRight":"paddingBottom"),i="border-box"===a(Hb).css("boxSizing"),l="none"!==Hb.css("float"),m=0,n=Hb.length-1;xb=0,Hb.each(function(b,c){var d=a(c),e=c.getBoundingClientRect(),i=G(rb.horizontal?e.width||e.right-e.left:e.height||e.bottom-e.top),k=j(d,rb.horizontal?"marginLeft":"marginTop"),o=j(d,rb.horizontal?"marginRight":"marginBottom"),p=i+k+o,q=!k||!o,r={};r.el=c,r.size=q?i:p,r.half=r.size/2,r.start=xb+(q?k:0),r.center=r.start-G(wb/2-r.size/2),r.end=r.start-wb+r.size,b||(xb+=g),xb+=p,rb.horizontal||l||o&&k&&b>0&&(xb-=I(k,o)),b===n&&(r.end+=h,xb+=h,m=q?o:0),Ib.push(r),f=r}),vb[0].style[rb.horizontal?"width":"height"]=(i?xb:xb-g-h)+"px",xb-=m,Ib.length?(yb.start=Ib[0][Pb?"center":"start"],yb.end=Pb?f.center:xb>wb?f.end:yb.start):yb.start=yb.end=0}if(yb.center=G(yb.end/2+yb.start/2),V(),Ab.length&&Bb>0&&(rb.dynamicHandle?(Cb=yb.start===yb.end?Bb:G(Bb*wb/xb),Cb=k(Cb,rb.minHandleSize,Bb),Ab[0].style[rb.horizontal?"width":"height"]=Cb+"px"):Cb=Ab[rb.horizontal?"outerWidth":"outerHeight"](),Db.end=Bb-Cb,ec||N()),!tb&&wb>0){var o=yb.start,p="";if(Rb)a.each(Ib,function(a,b){Pb?Gb.push(b.center):b.start+b.size>o&&o<=yb.end&&(o=b.start,Gb.push(o),o+=wb,o>yb.end&&o<yb.end+wb&&Gb.push(yb.end))});else for(;o-wb<yb.end;)Gb.push(o),o+=wb;if(Eb[0]&&e!==Gb.length){for(var q=0;q<Gb.length;q++)p+=rb.pageBuilder.call(sb,q);Fb=Eb.html(p).children(),Fb.eq(Jb.activePage).addClass(rb.activeClass)}}if(Jb.slideeSize=xb,Jb.frameSize=wb,Jb.sbSize=Bb,Jb.handleSize=Cb,Rb){c&&null!=rb.startAt&&(T(rb.startAt),sb[Qb?"toCenter":"toStart"](rb.startAt));var r=Ib[Jb.activeItem];L(Qb&&r?r.center:k(yb.dest,yb.start,yb.end))}else c?null!=rb.startAt&&L(rb.startAt,1):L(k(yb.dest,yb.start,yb.end));ob("load")}function L(a,b,c){if(Rb&&cc.released&&!c){var d=U(a),e=a>yb.start&&a<yb.end;Qb?(e&&(a=Ib[d.centerItem].center),Pb&&rb.activateMiddle&&T(d.centerItem)):e&&(a=Ib[d.firstItem].start)}cc.init&&cc.slidee&&rb.elasticBounds?a>yb.end?a=yb.end+(a-yb.end)/6:a<yb.start&&(a=yb.start+(a-yb.start)/6):a=k(a,yb.start,yb.end),ac.start=+new Date,ac.time=0,ac.from=yb.cur,ac.to=a,ac.delta=a-yb.cur,ac.tweesing=cc.tweese||cc.init&&!cc.slidee,ac.immediate=!ac.tweesing&&(b||cc.init&&cc.slidee||!rb.speed),cc.tweese=0,a!==yb.dest&&(yb.dest=a,ob("change"),ec||M()),Z(),V(),W(),O()}function M(){if(sb.initialized){if(!ec)return ec=t(M),void(cc.released&&ob("moveStart"));ac.immediate?yb.cur=ac.to:ac.tweesing?(ac.tweeseDelta=ac.to-yb.cur,D(ac.tweeseDelta)<.1?yb.cur=ac.to:yb.cur+=ac.tweeseDelta*(cc.released?rb.swingSpeed:rb.syncSpeed)):(ac.time=I(+new Date-ac.start,rb.speed),yb.cur=ac.from+ac.delta*a.easing[rb.easing](ac.time/rb.speed,ac.time,0,1,rb.speed)),ac.to===yb.cur?(yb.cur=ac.to,cc.tweese=ec=0):ec=t(M),ob("move"),tb||(m?vb[0].style[m]=n+(rb.horizontal?"translateX":"translateY")+"("+-yb.cur+"px)":vb[0].style[rb.horizontal?"left":"top"]=-G(yb.cur)+"px"),!ec&&cc.released&&ob("moveEnd"),N()}}function N(){Ab.length&&(Db.cur=yb.start===yb.end?0:((cc.init&&!cc.slidee?yb.dest:yb.cur)-yb.start)/(yb.end-yb.start)*Db.end,Db.cur=k(G(Db.cur),Db.start,Db.end),_b.hPos!==Db.cur&&(_b.hPos=Db.cur,m?Ab[0].style[m]=n+(rb.horizontal?"translateX":"translateY")+"("+Db.cur+"px)":Ab[0].style[rb.horizontal?"left":"top"]=Db.cur+"px"))}function O(){Fb[0]&&_b.page!==Jb.activePage&&(_b.page=Jb.activePage,Fb.removeClass(rb.activeClass).eq(Jb.activePage).addClass(rb.activeClass),ob("activePage",_b.page))}function P(){bc.speed&&yb.cur!==(bc.speed>0?yb.end:yb.start)||sb.stop(),hc=cc.init?t(P):0,bc.now=+new Date,bc.pos=yb.cur+(bc.now-bc.lastTime)/1e3*bc.speed,L(cc.init?bc.pos:G(bc.pos)),cc.init||yb.cur!==yb.dest||ob("moveEnd"),bc.lastTime=bc.now}function Q(a,b,d){if("boolean"===e(b)&&(d=b,b=c),b===c)L(yb[a],d);else{if(Qb&&"center"!==a)return;var f=sb.getPos(b);f&&L(f[a],d,!Qb)}}function R(a){return null!=a?i(a)?a>=0&&a<Ib.length?a:-1:Hb.index(a):-1}function S(a){return R(i(a)&&0>a?a+Ib.length:a)}function T(a,b){var c=R(a);return!Rb||0>c?!1:((_b.active!==c||b)&&(Hb.eq(Jb.activeItem).removeClass(rb.activeClass),Hb.eq(c).addClass(rb.activeClass),_b.active=Jb.activeItem=c,W(),ob("active",c)),c)}
42122function U(a){a=k(i(a)?a:yb.dest,yb.start,yb.end);var b={},c=Pb?0:wb/2;if(!tb)for(var d=0,e=Gb.length;e>d;d++){if(a>=yb.end||d===Gb.length-1){b.activePage=Gb.length-1;break}if(a<=Gb[d]+c){b.activePage=d;break}}if(Rb){for(var f=!1,g=!1,h=!1,j=0,l=Ib.length;l>j;j++)if(f===!1&&a<=Ib[j].start+Ib[j].half&&(f=j),h===!1&&a<=Ib[j].center+Ib[j].half&&(h=j),j===l-1||a<=Ib[j].end+Ib[j].half){g=j;break}b.firstItem=i(f)?f:0,b.centerItem=i(h)?h:b.firstItem,b.lastItem=i(g)?g:b.centerItem}return b}function V(b){a.extend(Jb,U(b))}function W(){var a=yb.dest<=yb.start,b=yb.dest>=yb.end,c=(a?1:0)|(b?2:0);if(_b.slideePosState!==c&&(_b.slideePosState=c,Yb.is("button,input")&&Yb.prop("disabled",a),Zb.is("button,input")&&Zb.prop("disabled",b),Yb.add(Vb)[a?"addClass":"removeClass"](rb.disabledClass),Zb.add(Ub)[b?"addClass":"removeClass"](rb.disabledClass)),_b.fwdbwdState!==c&&cc.released&&(_b.fwdbwdState=c,Vb.is("button,input")&&Vb.prop("disabled",a),Ub.is("button,input")&&Ub.prop("disabled",b)),Rb&&null!=Jb.activeItem){var d=0===Jb.activeItem,e=Jb.activeItem>=Ib.length-1,f=(d?1:0)|(e?2:0);_b.itemsButtonState!==f&&(_b.itemsButtonState=f,Wb.is("button,input")&&Wb.prop("disabled",d),Xb.is("button,input")&&Xb.prop("disabled",e),Wb[d?"addClass":"removeClass"](rb.disabledClass),Xb[e?"addClass":"removeClass"](rb.disabledClass))}}function X(a,b,c){if(a=S(a),b=S(b),a>-1&&b>-1&&a!==b&&(!c||b!==a-1)&&(c||b!==a+1)){Hb.eq(a)[c?"insertAfter":"insertBefore"](Ib[b].el);var d=b>a?a:c?b:b-1,e=a>b?a:c?b+1:b,f=a>b;null!=Jb.activeItem&&(a===Jb.activeItem?_b.active=Jb.activeItem=c?f?b+1:b:f?b:b-1:Jb.activeItem>d&&Jb.activeItem<e&&(_b.active=Jb.activeItem+=f?1:-1)),K()}}function Y(a,b){for(var c=0,d=$b[a].length;d>c;c++)if($b[a][c]===b)return c;return-1}function Z(){cc.released&&!sb.isPaused&&sb.resume()}function $(a){return G(k(a,Db.start,Db.end)/Db.end*(yb.end-yb.start))+yb.start}function _(){cc.history[0]=cc.history[1],cc.history[1]=cc.history[2],cc.history[2]=cc.history[3],cc.history[3]=cc.delta}function ab(a){cc.released=0,cc.source=a,cc.slidee="slidee"===a}function bb(b){var c="touchstart"===b.type,d=b.data.source,e="slidee"===d;cc.init||!c&&eb(b.target)||("handle"!==d||rb.dragHandle&&Db.start!==Db.end)&&(!e||(c?rb.touchDragging:rb.mouseDragging&&b.which<2))&&(c||f(b),ab(d),cc.init=0,cc.$source=a(b.target),cc.touch=c,cc.pointer=c?b.originalEvent.touches[0]:b,cc.initX=cc.pointer.pageX,cc.initY=cc.pointer.pageY,cc.initPos=e?yb.cur:Db.cur,cc.start=+new Date,cc.time=0,cc.path=0,cc.delta=0,cc.locked=0,cc.history=[0,0,0,0],cc.pathToLock=e?c?30:10:0,u.on(c?x:w,cb),sb.pause(1),(e?vb:Ab).addClass(rb.draggedClass),ob("moveStart"),e&&(fc=setInterval(_,10)))}function cb(a){if(cc.released="mouseup"===a.type||"touchend"===a.type,cc.pointer=cc.touch?a.originalEvent[cc.released?"changedTouches":"touches"][0]:a,cc.pathX=cc.pointer.pageX-cc.initX,cc.pathY=cc.pointer.pageY-cc.initY,cc.path=E(F(cc.pathX,2)+F(cc.pathY,2)),cc.delta=rb.horizontal?cc.pathX:cc.pathY,cc.released||!(cc.path<1)){if
42122(!cc.init){if(cc.path<rb.dragThreshold)return cc.released?db():c;if(!(rb.horizontal?D(cc.pathX)>D(cc.pathY):D(cc.pathX)<D(cc.pathY)))return db();cc.init=1}f(a),!cc.locked&&cc.path>cc.pathToLock&&cc.slidee&&(cc.locked=1,cc.$source.on(z,g)),cc.released&&(db(),rb.releaseSwing&&cc.slidee&&(cc.swing=(cc.delta-cc.history[0])/40*300,cc.delta+=cc.swing,cc.tweese=D(cc.swing)>10)),L(cc.slidee?G(cc.initPos-cc.delta):$(cc.initPos+cc.delta))}}function db(){clearInterval(fc),cc.released=!0,u.off(cc.touch?x:w,cb),(cc.slidee?vb:Ab).removeClass(rb.draggedClass),setTimeout(function(){cc.$source.off(z,g)}),yb.cur===yb.dest&&cc.init&&ob("moveEnd"),sb.resume(1),cc.init=0}function eb(b){return~a.inArray(b.nodeName,B)||a(b).is(rb.interactive)}function fb(){sb.stop(),u.off("mouseup",fb)}function gb(a){switch(f(a),this){case Ub[0]:case Vb[0]:sb.moveBy(Ub.is(this)?rb.moveBy:-rb.moveBy),u.on("mouseup",fb);break;case Wb[0]:sb.prev();break;case Xb[0]:sb.next();break;case Yb[0]:sb.prevPage();break;case Zb[0]:sb.nextPage()}}function hb(a){return dc.curDelta=(rb.horizontal?a.deltaY||a.deltaX:a.deltaY)||-a.wheelDelta,dc.curDelta/=1===a.deltaMode?3:100,Rb?(o=+new Date,dc.last<o-dc.resetTime&&(dc.delta=0),dc.last=o,dc.delta+=dc.curDelta,D(dc.delta)<1?dc.finalDelta=0:(dc.finalDelta=G(dc.delta/1),dc.delta%=1),dc.finalDelta):dc.curDelta}function ib(a){a.originalEvent[r]=sb;var b=+new Date;if(J+rb.scrollHijack>b&&Sb[0]!==document&&Sb[0]!==window)return void(J=b);if(rb.scrollBy&&yb.start!==yb.end){var c=hb(a.originalEvent);(rb.scrollTrap||c>0&&yb.dest<yb.end||0>c&&yb.dest>yb.start)&&f(a,1),sb.slideBy(rb.scrollBy*c)}}function jb(a){rb.clickBar&&a.target===zb[0]&&(f(a),L($((rb.horizontal?a.pageX-zb.offset().left:a.pageY-zb.offset().top)-Cb/2)))}function kb(a){if(rb.keyboardNavBy)switch(a.which){case rb.horizontal?37:38:f(a),sb["pages"===rb.keyboardNavBy?"prevPage":"prev"]();break;case rb.horizontal?39:40:f(a),sb["pages"===rb.keyboardNavBy?"nextPage":"next"]()}}function lb(a){return eb(this)?void(a.originalEvent[r+"ignore"]=!0):void(this.parentNode!==vb[0]||a.originalEvent[r+"ignore"]||sb.activate(this))}function mb(){this.parentNode===Eb[0]&&sb.activatePage(Fb.index(this))}function nb(a){rb.pauseOnHover&&sb["mouseenter"===a.type?"pause":"resume"](2)}function ob(a,b){if($b[a]){for(qb=$b[a].length,C.length=0,pb=0;qb>pb;pb++)C.push($b[a][pb]);for(pb=0;qb>pb;pb++)C[pb].call(sb,a,b)}}if(!(this instanceof d))return new d(b,p,q);var pb,qb,rb=a.extend({},d.defaults,p),sb=this,tb=i(b),ub=a(b),vb=rb.slidee?a(rb.slidee).eq(0):ub.children().eq(0),wb=0,xb=0,yb={start:0,center:0,end:0,cur:0,dest:0},zb=a(rb.scrollBar).eq(0),Ab=zb.children().eq(0),Bb=0,Cb=0,Db={start:0,end:0,cur:0},Eb=a(rb.pagesBar),Fb=0,Gb=[],Hb=0,Ib=[],Jb={firstItem:0,lastItem:0,centerItem:0,activeItem:null,activePage:0},Kb=new l(ub[0]),Lb=new l(vb[0]),Mb=new l(zb[0]),Nb=new l(Ab[0]),Ob="basic"===rb.itemNav,Pb="forceCentered"===rb.itemNav,Qb="centered"===rb.itemNav||Pb,Rb=!tb&&(Ob||Qb||Pb),Sb=rb.scrollSource?a(rb.scrollSource):ub,Tb=rb.dragSource?a(rb.dragSource):ub,Ub=a(rb.forward),Vb=a(rb.backward),Wb=a(rb.prev),Xb=a(rb.next),Yb=a(rb.prevPage),Zb=a(rb.nextPage),$b={},_b={},ac={},bc={},cc={released:1},dc={last:0,delta:0,resetTime:200},ec=0,fc=0,gc=0,hc=0;tb||(b=ub[0]),sb.initialized=0,sb.frame=b,sb.slidee=vb[0],sb.pos=yb,sb.rel=Jb,sb.items=Ib,sb.pages=Gb,sb.isPaused=0,sb.options=rb,sb.dragging=cc,sb.reload=function(){K()},sb.getPos=function(a){if(Rb){var b=R(a);return-1!==b?Ib[b]:!1}var c=vb.find(a).eq(0);if(c[0]){var d=rb.horizontal?c.offset().left-vb.offset().left:c.offset().top-vb.offset().top,e=c[rb.horizontal?"outerWidth":"outerHeight"]();return{start:d,center:d-wb/2+e/2,end:d-wb+e,size:e}}return!1},sb.moveBy=function(a){bc.speed=a,!cc.init&&bc.speed&&yb.cur!==(bc.speed>0?yb.end:yb.start)&&(bc.lastTime=+new Date,bc.startPos=yb.cur,ab("button"),cc.init=1,ob("moveStart"),s(hc),P())},sb.stop=function(){"button"===cc.source&&(cc.init=0,cc.released=1)},sb.prev=function(){sb.activate(null==Jb.activeItem?0:Jb.activeItem-1)},sb.next=function(){sb.activate(null==Jb.activeItem?0:Jb.activeItem+1)},sb.prevPage=function(){sb.activatePage(Jb.activePage-1)},sb.nextPage=function(){sb.activatePage(Jb.activePage+1)},sb.slideBy=function(a,b){a&&(Rb?sb[Qb?"toCenter":"toStart"](k((Qb?Jb.centerItem:Jb.firstItem)+rb.scrollBy*a,0,Ib.length)):L(yb.dest+a,b))},sb.slideTo=function(a,b){L(a,b)},sb.toStart=function(a,b){Q("start",a,b)},sb.toEnd=function(a,b){Q("end",a,b)},sb.toCenter=function(a,b){Q("center",a,b)},sb.getIndex=R,sb.activate=function(a,b){var c=T(a);rb.smart&&c!==!1&&(Qb?sb.toCenter(c,b):c>=Jb.lastItem?sb.toStart(c,b):c<=Jb.firstItem?sb.toEnd(c,b):Z())},sb.activatePage=function(a,b){i(a)&&L(Gb[k(a,0,Gb.length-1)],b)},sb.resume=function(a){rb.cycleBy&&rb.cycleInterval&&("items"!==rb.cycleBy||Ib[0]&&null!=Jb.activeItem)&&!(a<sb.isPau
42122sed)&&(sb.isPaused=0,gc?gc=clearTimeout(gc):ob("resume"),gc=setTimeout(function(){switch(ob("cycle"),rb.cycleBy){case"items":sb.activate(Jb.activeItem>=Ib.length-1?0:Jb.activeItem+1);break;case"pages":sb.activatePage(Jb.activePage>=Gb.length-1?0:Jb.activePage+1)}},rb.cycleInterval))},sb.pause=function(a){a<sb.isPaused||(sb.isPaused=a||100,gc&&(gc=clearTimeout(gc),ob("pause")))},sb.toggle=function(){sb[gc?"pause":"resume"]()},sb.set=function(b,c){a.isPlainObject(b)?a.extend(rb,b):rb.hasOwnProperty(b)&&(rb[b]=c)},sb.add=function(b,c){var d=a(b);Rb?(null==c||!Ib[0]||c>=Ib.length?d.appendTo(vb):Ib.length&&d.insertBefore(Ib[c].el),null!=Jb.activeItem&&c<=Jb.activeItem&&(_b.active=Jb.activeItem+=d.length)):vb.append(d),K()},sb.remove=function(b){if(Rb){var c=S(b);if(c>-1){Hb.eq(c).remove();var d=c===Jb.activeItem;null!=Jb.activeItem&&c<Jb.activeItem&&(_b.active=--Jb.activeItem),K(),d&&(_b.active=null,sb.activate(Jb.activeItem))}}else a(b).remove(),K()},sb.moveAfter=function(a,b){X(a,b,1)},sb.moveBefore=function(a,b){X(a,b)},sb.on=function(a,b){if("object"===e(a))for(var c in a)a.hasOwnProperty(c)&&sb.on(c,a[c]);else if("function"===e(b))for(var d=a.split(" "),f=0,g=d.length;g>f;f++)$b[d[f]]=$b[d[f]]||[],-1===Y(d[f],b)&&$b[d[f]].push(b);else if("array"===e(b))for(var h=0,i=b.length;i>h;h++)sb.on(a,b[h])},sb.one=function(a,b){function c(){b.apply(sb,arguments),sb.off(a,c)}sb.on(a,c)},sb.off=function(a,b){if(b instanceof Array)for(var c=0,d=b.length;d>c;c++)sb.off(a,b[c]);else for(var e=a.split(" "),f=0,g=e.length;g>f;f++)if($b[e[f]]=$b[e[f]]||[],null==b)$b[e[f]].length=0;else{var h=Y(e[f],b);-1!==h&&$b[e[f]].splice(h,1)}}
42122,sb.destroy=function(){return d.removeInstance(b),Sb.add(Ab).add(zb).add(Eb).add(Ub).add(Vb).add(Wb).add(Xb).add(Yb).add(Zb).off("."+r),u.off("keydown",kb),Wb.add(Xb).add(Yb).add(Zb).removeClass(rb.disabledClass),Hb&&null!=Jb.activeItem&&Hb.eq(Jb.activeItem).removeClass(rb.activeClass),Eb.empty(),tb||(ub.off("."+r),Kb.restore(),Lb.restore(),Mb.restore(),Nb.restore(),a.removeData(b,r)),Ib.length=Gb.length=0,_b={},sb.initialized=0,sb},sb.init=function(){if(!sb.initialized){if(d.getInstance(b))throw new Error("There is already a Sly instance on this element");d.storeInstance(b,sb),sb.on(q);var a=["overflow","position"],c=["position","webkitTransform","msTransform","transform","left","top","width","height"];Kb.save.apply(Kb,a),Mb.save.apply(Mb,a),Lb.save.apply(Lb,c),Nb.save.apply(Nb,c);var e=Ab;return tb||(e=e.add(vb),ub.css("overflow","hidden"),m||"static"!==ub.css("position")||ub.css("position","relative")),m?n&&e.css(m,n):("static"===zb.css("position")&&zb.css("position","relative"),e.css({position:"absolute"})),rb.forward&&Ub.on(A,gb),rb.backward&&Vb.on(A,gb),rb.prev&&Wb.on(z,gb),rb.next&&Xb.on(z,gb),rb.prevPage&&Yb.on(z,gb),rb.nextPage&&Zb.on(z,gb),Sb.on(y,ib),zb[0]&&zb.on(z,jb),Rb&&rb.activateOn&&ub.on(rb.activateOn+"."+r,"*",lb),Eb[0]&&rb.activatePageOn&&Eb.on(rb.activatePageOn+"."+r,"*",mb),Tb.on(v,{source:"slidee"},bb),Ab&&Ab.on(v,{source:"handle"},bb),u.on("keydown",kb),tb||(ub.on("mouseenter."+r+" mouseleave."+r,nb),ub.on("scroll."+r,h)),sb.initialized=1,K(!0),rb.cycleBy&&!tb&&sb[rb.startPaused?"pause":"resume"](),sb}}}function e(a){return null==a?String(a):"object"==typeof a||"function"==typeof a?Object.prototype.toString.call(a).match(/\s([a-z]+)/i)[1].toLowerCase()||"object":typeof a}function f(a,b){a.preventDefault(),b&&a.stopPropagation()}function g(b){f(b,1),a(this).off(b.type,g)}function h(){this.scrollLeft=0,this.scrollTop=0}function i(a){return!isNaN(parseFloat(a))&&isFinite(a)}function j(a,b){return 0|G(String(a.css(b)).replace(/[^\-0-9.]/g,""))}function k(a,b,c){return b>a?b:a>c?c:a}function l(a){var b={};return b.style={},b.save=function(){if(a&&a.nodeType){for(var c=0;c<arguments.length;c++)b.style[arguments[c]]=a.style[arguments[c]];return b}},b.restore=function(){if(a&&a.nodeType){for(var c in b.style)b.style.hasOwnProperty(c)&&(a.style[c]=b.style[c]);return b}},b}var m,n,o,p="sly",q="Sly",r=p,s=b.cancelAnimationFrame||b.cancelRequestAnimationFrame,t=b.requestAnimationFrame,u=a(document),v="touchstart."+r+" mousedown."+r,w="mousemove."+r+" mouseup."+r,x="touchmove."+r+" touchend."+r,y=(document.implementation.hasFeature("Event.wheel","3.0")?"wheel.":"mousewheel.")+r,z="click."+r,A="mousedown."+r,B=["INPUT","SELECT","BUTTON","TEXTAREA"],C=[],D=Math.abs,E=Math.sqrt,F=Math.pow,G=Math.round,H=Math.max,I=Math.min,J=0;u.on(y,function(a){var b=a.originalEvent[r],c=+new Date;(!b||b.options.scrollHijack<c-J)&&(J=c)}),d.getInstance=function(b){return a.data(b,r)},d.storeInstance=function(b,c){return a.data(b,r,c)},d.removeInstance=function(b){return a.removeData(b,r)},function(a){function b(a){var b=(new Date).getTime(),d=Math.max(0,16-(b-c)),e=setTimeout(a,d);return c=b,e}t=a.requestAnimationFrame||a.webkitRequestAnimationFrame||b;var c=(new Date).getTime(),d=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||a.clearTimeout;s=function(b){d.call(a,b)}}(window),function(){function a(a){for(var d=0,e=b.length;e>d;d++){var f=b[d]?b[d]+a.charAt(0).toUpperCase()+a.slice(1):a;if(null!=c.style[f])return f}}var b=["","Webkit","Moz","ms","O"],c=document.createElement("div");m=a("transform"),n=a("perspective")?"translateZ(0) ":""}(),b[q]=d,a.fn[p]=function(b,c){var f,g;return a.isPlainObject(b)||(("string"===e(b)||b===!1)&&(f=b===!1?"destroy":b,g=Array.prototype.slice.call(arguments,1)),b={}),this.each(function(a,e){var h=d.getInstance(e);h||f?h&&f&&h[f]&&h[f].apply(h,g):h=new d(e,b,c).init()})},d.defaults={slidee:null,horizontal:!1,itemNav:null,itemSelector:null,smart:!1,activateOn:null,activateMiddle:!1,scrollSource:null,scrollBy:0,scrollHijack:300,scrollTrap:!1,dragSource:null,mouseDragging:!1,touchDragging:!1,releaseSwing:!1,swingSpeed:.2,elasticBounds:!1,dragThreshold:3,interactive:null,scrollBar:null,dragHandle:!1,dynamicHandle:!1,minHandleSize:50,clickBar:!1,syncSpeed:.5,pagesBar:null,activatePageOn:null,pageBuilder:function(a){return"<li>"+(a+1)+"</li>"},forward:null,backward:null,prev:null,next:null,prevPage:null,nextPage:null,cycleBy:null,cycleInterval:5e3,pauseOnHover:!1,startPaused:!1,moveBy:300,speed:0,easing:"swing",startAt:null,keyboardNavBy:null,draggedClass:"dragged",activeClass:"active",disabledClass:"disabled"}}(jQuery,window);
42123(function() {
42124  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
42125    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
42126    hasProp = {}.hasOwnProperty;
42127
42128  App.Pages.BuyersLandingPageIndex = (function(superClass) {
42129    extend(BuyersLandingPageIndex, superClass);
42130
42131    function BuyersLandingPageIndex() {
42132      this.resizeSly = bind(this.resizeSly, this);
42133      return BuyersLandingPageIndex.__super__.constructor.apply(this, arguments);
42134    }
42135
42136    BuyersLandingPageIndex.prototype.widgets = ['loyaltyLevelProgressInfoTooltip', 'downloads', 'categoriesContainerTracking'];
42137
42138    BuyersLandingPageIndex.prototype.initialize = function() {
42139      this.initializeTooltips();
42140      this.initCartItemsList();
42141      this.bindEvents();
42142      this.bindGTMEvents();
42143      return App.events.subscribe('app:cart:updated', this.onCartUpdated);
42144    };
42145
42146    BuyersLandingPageIndex.prototype.initCartItemsList = function() {
42147      this.cartSly = new Sly('.js-cart-list', {
42148        horizontal: true,
42149        itemNav: 'basic',
42150        smart: true,
42151        itemSelector: '.js-cart-list .grid__col',
42152        startAt: 0,
42153        mouseDragging: true,
42154        releaseSwing: true,
42155        touchDragging: false,
42156        swingSpeed: 0.1,
42157        nextPage: $('.js-cart-preview-next'),
42158        prevPage: $('.js-cart-preview-prev'),
42159        disabledClass: 'is-disabled',
42160        draggedClass: 'is-dragged'
42161      });
42162      this.cartSly.on('load', (function(_this) {
42163        return function() {
42164          if (_this.cartSly.pos.start === _this.cartSly.pos.end) {
42165            return $('.grid__col').addClass('is-short-grid');
42166          } else {
42167            return $('.grid__col').removeClass('is-short-grid');
42168          }
42169        };
42170      })(this));
42171      if ($('.grid__col').length !== 0) {
42172        return this.cartSly.init();
42173      }
42174    };
42175
42176    BuyersLandingPageIndex.prototype.bindEvents = function() {
42177      return $(window).on('resize', this.resizeSly);
42178    };
42179
42180    BuyersLandingPageIndex.prototype.resizeSly = function() {
42181      return this.cartSly.reload();
42182    };
42183
42184    BuyersLandingPageIndex.prototype.onCartUpdated = function(cart) {
42185      return $('.js-store-credits-left').text(numeral(cart.meta.storeCreditsLeft).format('$0.00'));
42186    };
42187
42188    BuyersLandingPageIndex.prototype.initializeTooltips = function() {
42189      var $bonusCreditsTooltip, $cgtCreditsInfoTooltip, $proTipTooltip, $saleCampaignTooltip;
42190      $bonusCreditsTooltip = $('.js-bonus-credits-tooltip');
42191      $cgtCreditsInfoTooltip = $('.js-cgtrader-credits-info-tooltip');
42192      $proTipTooltip = $('.js-pro-tip-tooltip');
42193      $saleCampaignTooltip = $('.js-sale-campaign-tooltip');
42194      if ($saleCampaignTooltip.length > 0) {
42195        this.initTooltip(I18n.translate('tooltips.buyers_sign_in_page.sale_campaign_tooltip_html'), $saleCampaignTooltip, 'Tooltip in important message (sale off)');
42196      }
42197      if ($proTipTooltip.length > 0) {
42198        this.initTooltip(I18n.translate('tooltips.buyers_sign_in_page.pro_tip_tooltip_html'), $proTipTooltip, 'Tooltip in pro tip');
42199      }
42200      if ($cgtCreditsInfoTooltip.length > 0) {
42201        this.initTooltip(I18n.translate('tooltips.buyers_sign_in_page.cgtrader_credits_tooltip_html'), $cgtCreditsInfoTooltip, 'Tooltip in credit count');
42202      }
42203      if ($bonusCreditsTooltip.length > 0) {
42204        return this.initTooltip(I18n.translate('tooltips.buyers_sign_in_page.bonus_credits_tooltip_html'), $bonusCreditsTooltip, 'Tooltip in important message (bonus credits)');
42205      }
42206    };
42207
42208    BuyersLandingPageIndex.prototype.initTooltip = function(content, $tooltip, eventLabel) {
42209      return $tooltip.tooltipster({
42210        theme: 'tooltip--light tooltip--popover u-text-center',
42211        animation: 'grow',
42212        contentAsHTML: true,
42213        content: content,
42214        interactive: true,
42215        maxWidth: 460
42216      });
42217    };
42218
42219    BuyersLandingPageIndex.prototype.bindGTMEvents = function() {
42220      this.$cartSection = $('.js-cart-section');
42221      this.$purchasesList = $('.js-purchases-list');
42222      this.$noPurchasesView = $('.js-no-purchases-view');
42223      this.$publishedJobInfo = $('.js-published-job-info');
42224      this.$noJobsView = $('.js-no-jobs-view');
42225      if (this.$cartSection.length !== 0) {
42226        this.trackEvent('Cart items', 'Impressions');
42227      }
42228      if (this.$purchasesList.length !== 0) {
42229        this.trackEvent('Purchases', 'One + purchases');
42230      }
42231      if (this.$noPurchasesView.length !== 0) {
42232        this.trackEvent('Purchases', 'No purchases');
42233      }
42234      if (this.$publishedJobInfo.length !== 0) {
42235        this.trackEvent('3D Jobs', 'One + jobs published');
42236      }
42237      if (this.$noJobsView.length !== 0) {
42238        return this.trackEvent('3D Jobs', 'No jobs published');
42239      }
42240    };
42241
42242    return BuyersLandingPageIndex;
42243
42244  })(App.Core.Page);
42245
42246}).call(this);
42247(function() {
42248  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
42249    hasProp = {}.hasOwnProperty;
42250
42251  App.Pages.CategoriesIndex = (function(superClass) {
42252    extend(CategoriesIndex, superClass);
42253
42254    function CategoriesIndex() {
42255      return CategoriesIndex.__super__.constructor.apply(this, arguments);
42256    }
42257
42258    CategoriesIndex.prototype.initialize = function() {
42259      return this.bindEvents();
42260    };
42261
42262    CategoriesIndex.prototype.bindEvents = function() {
42263      return $('.js-category-image').on('click', this.onThumbnailClick);
42264    };
42265
42266    CategoriesIndex.prototype.onThumbnailClick = function(e) {
42267      var $target;
42268      e.stopPropagation();
42269      $target = $(e.currentTarget).parents('.category').find('.category__title a');
42270      return window.location = $target.attr('href');
42271    };
42272
42273    return CategoriesIndex;
42274
42275  })(App.Core.Page);
42276
42277}).call(this);
42278var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};!function(e,t){"object"===("undefined"==typeof exports?"undefined":_typeof(exports))&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.LazyLoad=t()}(this,function(){"use strict";var e={elements_selector:"img",container:window,threshold:300,throttle:150,data_src:"src",data_srcset:"srcset",class_loading:"loading",class_loaded:"loaded",class_error:"error",class_initial:"initial",skip_invisible:!0,callback_load:null,callback_error:null,callback_set:null,callback_processed:null,callback_enter:null},t=!("onscroll"in window)||/glebot/.test(navigator.userAgent),n=function(e,t){e&&e(t)}
42278,o=function(e){return e.getBoundingClientRect().top+window.pageYOffset-e.ownerDocument.documentElement.clientTop},i=function(e,t,n){return(t===window?window.innerHeight+window.pageYOffset:o(t)+t.offsetHeight)<=o(e)-n},s=function(e){return e.getBoundingClientRect().left+window.pageXOffset-e.ownerDocument.documentElement.clientLeft},r=function(e,t,n){var o=window.innerWidth;return(t===window?o+window.pageXOffset:s(t)+o)<=s(e)-n},l=function(e,t,n){return(t===window?window.pageYOffset:o(t))>=o(e)+n+e.offsetHeight},a=function(e,t,n){return(t===window?window.pageXOffset:s(t))>=s(e)+n+e.offsetWidth},c=function(e,t,n){return!(i(e,t,n)||l(e,t,n)||r(e,t,n)||a(e,t,n))},u=function(e,t){var n,o=new e(t);try{n=new CustomEvent("LazyLoad::Initialized",{detail:{instance:o}})}catch(e){(n=document.createEvent("CustomEvent")).initCustomEvent("LazyLoad::Initialized",!1,!1,{instance:o})}window.dispatchEvent(n)},d=function(e,t){return e.getAttribute("data-"+t)},h=function(e,t,n){return e.setAttribute("data-"+t,n)},_=function(e,t){var n=e.parentNode;if("PICTURE"===n.tagName)for(var o=0;o<n.children.length;o++){var i=n.children[o];if("SOURCE"===i.tagName){var s=d(i,t);s&&i.setAttribute("srcset",s)}}},f=function(e,t,n){var o=e.tagName,i=d(e,n);if("IMG"===o){_(e,t);var s=d(e,t);return s&&e.setAttribute("srcset",s),void(i&&e.setAttribute("src",i))}"IFRAME"!==o?i&&(e.style.backgroundImage='url("'+i+'")'):i&&e.setAttribute("src",i)},p="classList"in document.createElement("p"),m=function(e,t){p?e.classList.add(t):e.className+=(e.className?" ":"")+t},g=function(e,t){p?e.classList.remove(t):e.className=e.className.replace(new RegExp("(^|\\s+)"+t+"(\\s+|$)")," ").replace(/^\s+/,"").replace(/\s+$/,"")},v=function(t){this._settings=_extends({},e,t),this._queryOriginNode=this._settings.container===window?document:this._settings.container,this._previousLoopTime=0,this._loopTimeout=null,this._boundHandleScroll=this.handleScroll.bind(this),this._isFirstLoop=!0,window.addEventListener("resize",this._boundHandleScroll),this.update()};v.prototype={_reveal:function(e){var t=this._settings,o=function o(){t&&(e.removeEventListener("load",i),e.removeEventListener("error",o),g(e,t.class_loading),m(e,t.class_error),n(t.callback_error,e))},i=function i(){t&&(g(e,t.class_loading),m(e,t.class_loaded),e.removeEventListener("load",i),e.removeEventListener("error",o),n(t.callback_load,e))};n(t.callback_enter,e),"IMG"!==e.tagName&&"IFRAME"!==e.tagName||(e.addEventListener("load",i),e.addEventListener("error",o),m(e,t.class_loading)),f(e,t.data_srcset,t.data_src),n(t.callback_set,e)},_loopThroughElements:function(){var e=this._settings,o=this._elements,i=o?o.length:0,s=void 0,r=[],l=this._isFirstLoop;for(s=0;s<i;s++){var a=o[s];e.skip_invisible&&null===a.offsetParent||(t||c(a,e.container,e.threshold))&&(l&&m(a,e.class_initial),this._reveal(a),r.push(s),h(a,"was-processed",!0))}for(;r.length;)o.splice(r.pop(),1),n(e.callback_processed,o.length);0===i&&this._stopScrollHandler(),l&&(this._isFirstLoop=!1)},_purgeElements:function(){var e=this._elements,t=e.length,n=void 0,o=[];for(n=0;n<t;n++){var i=e[n];d(i,"was-processed")&&o.push(n)}for(;o.length>0;)e.splice(o.pop(),1)},_startScrollHandler:function(){this._isHandlingScroll||(this._isHandlingScroll=!0,this._settings.container.addEventListener("scroll",this._boundHandleScroll))},_stopScrollHandler:function(){this._isHandlingScroll&&(this._isHandlingScroll=!1,this._settings.container.removeEventListener("scroll",this._boundHandleScroll))},handleScroll:function(){var e=this._settings.throttle;if(0!==e){var t=Date.now(),n=e-(t-this._previousLoopTime);n<=0||n>e?(this._loopTimeout&&(clearTimeout(this._loopTimeout),this._loopTimeout=null),this._previousLoopTime=t,this._loopThroughElements()):this._loopTimeout||(this._loopTimeout=setTimeout(function(){this._previousLoopTime=Date.now(),this._loopTimeout=null,this._loopThroughElements()}.bind(this),n))}else this._loopThroughElements()},update:function(){this._elements=Array.prototype.slice.call(this._queryOriginNode.querySelectorAll(this._settings.elements_selector)),this._purgeElements(),this._loopThroughElements(),this._startScrollHandler()},destroy:function(){window.removeEventListener("resize",this._boundHandleScroll),this._loopTimeout&&(clearTimeout(this._loopTimeout),this._loopTimeout=null),this._stopScrollHandler(),this._elements=null,this._queryOriginNode=null,this._settings=null}};var w=window.lazyLoadOptions;return w&&function(e,t){var n=t.length;if(n)for(var o=0;o<n;o++)u(e,t[o]);else u(e,t)}(v,w),v});
42279(function() {
42280  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
42281    hasProp = {}.hasOwnProperty;
42282
42283  App.Pages.ChallengesCorona = (function(superClass) {
42284    extend(ChallengesCorona, superClass);
42285
42286    function ChallengesCorona() {
42287      return ChallengesCorona.__super__.constructor.apply(this, arguments);
42288    }
42289
42290    ChallengesCorona.prototype.initialize = function() {
42291      this.bindPerPageSelectors();
42292      return new LazyLoad({
42293        elements_selector: '.js-lazy-img'
42294      });
42295    };
42296
42297    ChallengesCorona.prototype.bindPerPageSelectors = function() {
42298      return $(document).on('click', '.js-per-page', (function(_this) {
42299        return function(event) {
42300          var $perPageLink, params;
42301          mute(event);
42302          $perPageLink = $(event.currentTarget);
42303          $perPageLink.parents('.product-filters-footer__page-size').find('.is-active').removeClass('is-active');
42304          $perPageLink.parent().addClass('is-active');
42305          Cookies.set(_this.paginationCookieKey(), $perPageLink.text());
42306          Cookies.set(gon.cookies_map.per_page_changed, true);
42307          params = new URLSearchParams(location.search);
42308          params.set('page', '1');
42309          return window.location.search = params.toString();
42310        };
42311      })(this));
42312    };
42313
42314    ChallengesCorona.prototype.paginationCookieKey = function() {
42315      if (_.isEmpty($.deparam((window.location.search || "").replace(/^\?/, '')).keywords)) {
42316        return gon.cookies_map.items_listing_per_page;
42317      } else {
42318        return gon.cookies_map.items_search_per_page;
42319      }
42320    };
42321
42322    return ChallengesCorona;
42323
42324  })(App.Core.Page);
42325
42326}).call(this);
42327(function() {
42328  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
42329    hasProp = {}.hasOwnProperty;
42330
42331  App.Pages.ChallengesIndex = (function(superClass) {
42332    extend(ChallengesIndex, superClass);
42333
42334    function ChallengesIndex() {
42335      return ChallengesIndex.__super__.constructor.apply(this, arguments);
42336    }
42337
42338    ChallengesIndex.prototype.widgets = ['linkFaker'];
42339
42340    return ChallengesIndex;
42341
42342  })(App.Core.Page);
42343
42344}).call(this);
42345(function() {
42346  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
42347    hasProp = {}.hasOwnProperty;
42348
42349  App.Pages.ChallengesShow = (function(superClass) {
42350    extend(ChallengesShow, superClass);
42351
42352    function ChallengesShow() {
42353      return ChallengesShow.__super__.constructor.apply(this, arguments);
42354    }
42355
42356    ChallengesShow.prototype.widgets = ['countdown', 'linkFaker'];
42357
42358    ChallengesShow.prototype.initialize = function() {
42359      return this.initTabs();
42360    };
42361
42362    ChallengesShow.prototype.initTabs = function() {
42363      var defaultTab, entryTab;
42364      entryTab = $('.challenge-tabs > ul > li > a[href="#section-entries"]');
42365      defaultTab = entryTab.length ? entryTab.first().parent() : $('.challenge-tabs > ul > li').first();
42366      return $('.challenge-tabs').easytabs({
42367        animate: true,
42368        panelActiveClass: 'is-active',
42369        panelContext: $('.challenge-sections'),
42370        tabActiveClass: 'is-active',
42371        defaultTab: defaultTab
42372      });
42373    };
42374
42375    return ChallengesShow;
42376
42377  })(App.Core.Page);
42378
42379}).call(this);
42380(function() {
42381  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
42382    hasProp = {}.hasOwnProperty;
42383
42384  App.Pages.CheckoutOrderProcessingScreen = (function(superClass) {
42385    extend(CheckoutOrderProcessingScreen, superClass);
42386
42387    function CheckoutOrderProcessingScreen() {
42388      return CheckoutOrderProcessingScreen.__super__.constructor.apply(this, arguments);
42389    }
42390
42391    CheckoutOrderProcessingScreen.prototype.initialize = function() {
42392      return this.startPolling();
42393    };
42394
42395    CheckoutOrderProcessingScreen.prototype.startPolling = function() {
42396      var orderID;
42397      orderID = $('.js-loader-container').data('id');
42398      return setInterval((function(_this) {
42399        return function() {
42400          return _this.checkStatus(orderID);
42401        };
42402      })(this), 3000);
42403    };
42404
42405    CheckoutOrderProcessingScreen.prototype.checkStatus = function(id) {
42406      var that;
42407      that = this;
42408      return $.ajax({
42409        url: '/cg/checkout/check-order-status/' + id,
42410        success: function(response) {
42411          if (response.success) {
42412            return window.location = response.return_url;
42413          }
42414        }
42415      });
42416    };
42417
42418    return CheckoutOrderProcessingScreen;
42419
42420  })(App.Core.Page);
42421
42422}).call(this);
42423// Generated by CoffeeScript 1.7.1
42424(function() {
42425  var cardFromNumber, cardFromType, cards, defaultFormat, formatBackCardNumber, formatBackExpiry, formatCardNumber, formatExpiry, formatForwardExpiry, formatForwardSlashAndSpace, hasTextSelected, luhnCheck, reFormatCVC, reFormatCardNumber, reFormatExpiry, reFormatNumeric, restrictCVC, restrictCardNumber, restrictExpiry, restrictNumeric, setCardType,
42426    __slice = [].slice,
42427    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
42428
42429  $.payment = {};
42430
42431  $.payment.fn = {};
42432
42433  $.fn.payment = function() {
42434    var args, method;
42435    method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
42436    return $.payment.fn[method].apply(this, args);
42437  };
42438
42439  defaultFormat = /(\d{1,4})/g;
42440
42441  $.payment.cards = cards = [
42442    {
42443      type: 'visaelectron',
42444      pattern: /^4(026|17500|405|508|844|91[37])/,
42445      format: defaultFormat,
42446      length: [16],
42447      cvcLength: [3],
42448      luhn: true
42449    }, {
42450      type: 'maestro',
42451      pattern: /^(5(018|0[23]|[68])|6(39|7))/,
42452      format: defaultFormat,
42453      length: [12, 13, 14, 15, 16, 17, 18, 19],
42454      cvcLength: [3],
42455      luhn: true
42456    }, {
42457      type: 'forbrugsforeningen',
42458      pattern: /^600/,
42459      format: defaultFormat,
42460      length: [16],
42461      cvcLength: [3],
42462      luhn: true
42463    }, {
42464      type: 'dankort',
42465      pattern: /^5019/,
42466      format: defaultFormat,
42467      length: [16],
42468      cvcLength: [3],
42469      luhn: true
42470    }, {
42471      type: 'visa',
42472      pattern: /^4/,
42473      format: defaultFormat,
42474      length: [13, 16],
42475      cvcLength: [3],
42476      luhn: true
42477    }, {
42478      type: 'mastercard',
42479      pattern: /^(5[0-5]|2[2-7])/,
42480      format: defaultFormat,
42481      length: [16],
42482      cvcLength: [3],
42483      luhn: true
42484    }, {
42485      type: 'amex',
42486      pattern: /^3[47]/,
42487      format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/,
42488      length: [15],
42489      cvcLength: [3, 4],
42490      luhn: true
42491    }, {
42492      type: 'dinersclub',
42493      pattern: /^3[0689]/,
42494      format: /(\d{1,4})(\d{1,6})?(\d{1,4})?/,
42495      length: [14],
42496      cvcLength: [3],
42497      luhn: true
42498    }, {
42499      type: 'discover',
42500      pattern: /^6([045]|22)/,
42501      format: defaultFormat,
42502      length: [16],
42503      cvcLength: [3],
42504      luhn: true
42505    }, {
42506      type: 'unionpay',
42507      pattern: /^(62|88)/,
42508      format: defaultFormat,
42509      length: [16, 17, 18, 19],
42510      cvcLength: [3],
42511      luhn: false
42512    }, {
42513      type: 'jcb',
42514      pattern: /^35/,
42515      format: defaultFormat,
42516      length: [16],
42517      cvcLength: [3],
42518      luhn: true
42519    }
42520  ];
42521
42522  cardFromNumber = function(num) {
42523    var card, _i, _len;
42524    num = (num + '').replace(/\D/g, '');
42525    for (_i = 0, _len = cards.length; _i < _len; _i++) {
42526      card = cards[_i];
42527      if (card.pattern.test(num)) {
42528        return card;
42529      }
42530    }
42531  };
42532
42533  cardFromType = function(type) {
42534    var card, _i, _len;
42535    for (_i = 0, _len = cards.length; _i < _len; _i++) {
42536      card = cards[_i];
42537      if (card.type === type) {
42538        return card;
42539      }
42540    }
42541  };
42542
42543  luhnCheck = function(num) {
42544    var digit, digits, odd, sum, _i, _len;
42545    odd = true;
42546    sum = 0;
42547    digits = (num + '').split('').reverse();
42548    for (_i = 0, _len = digits.length; _i < _len; _i++) {
42549      digit = digits[_i];
42550      digit = parseInt(digit, 10);
42551      if ((odd = !odd)) {
42552        digit *= 2;
42553      }
42554      if (digit > 9) {
42555        digit -= 9;
42556      }
42557      sum += digit;
42558    }
42559    return sum % 10 === 0;
42560  };
42561
42562  hasTextSelected = function($target) {
42563    var _ref;
42564    if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== $target.prop('selectionEnd')) {
42565      return true;
42566    }
42567    if ((typeof document !== "undefined" && document !== null ? (_ref = document.selection) != null ? _ref.createRange : void 0 : void 0) != null) {
42568      if (document.selection.createRange().text) {
42569        return true;
42570      }
42571    }
42572    return false;
42573  };
42574
42575  reFormatNumeric = function(e) {
42576    return setTimeout(function() {
42577      var $target, value;
42578      $target = $(e.currentTarget);
42579      value = $target.val();
42580      value = value.replace(/\D/g, '');
42581      return $target.val(value);
42582    });
42583  };
42584
42585  reFormatCardNumber = function(e) {
42586    return setTimeout(function() {
42587      var $target, value;
42588      $target = $(e.currentTarget);
42589      value = $target.val();
42590      value = $.payment.formatCardNumber(value);
42591      return $target.val(value);
42592    });
42593  };
42594
42595  formatCardNumber = function(e) {
42596    var $target, card, digit, length, re, upperLength, value;
42597    digit = String.fromCharCode(e.which);
42598    if (!/^\d+$/.test(digit)) {
42599      return;
42600    }
42601    $target = $(e.currentTarget);
42602    value = $target.val();
42603    card = cardFromNumber(value + digit);
42604    length = (value.replace(/\D/g, '') + digit).length;
42605    upperLength = 16;
42606    if (card) {
42607      upperLength = card.length[card.length.length - 1];
42608    }
42609    if (length >= upperLength) {
42610      return;
42611    }
42612    if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
42613      return;
42614    }
42615    if (card && card.type === 'amex') {
42616      re = /^(\d{4}|\d{4}\s\d{6})$/;
42617    } else {
42618      re = /(?:^|\s)(\d{4})$/;
42619    }
42620    if (re.test(value)) {
42621      e.preventDefault();
42622      return setTimeout(function() {
42623        return $target.val(value + ' ' + digit);
42624      });
42625    } else if (re.test(value + digit)) {
42626      e.preventDefault();
42627      return setTimeout(function() {
42628        return $target.val(value + digit + ' ');
42629      });
42630    }
42631  };
42632
42633  formatBackCardNumber = function(e) {
42634    var $target, value;
42635    $target = $(e.currentTarget);
42636    value = $target.val();
42637    if (e.which !== 8) {
42638      return;
42639    }
42640    if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
42641      return;
42642    }
42643    if (/\d\s$/.test(value)) {
42644      e.preventDefault();
42645      return setTimeout(function() {
42646        return $target.val(value.replace(/\d\s$/, ''));
42647      });
42648    } else if (/\s\d?$/.test(value)) {
42649      e.preventDefault();
42650      return setTimeout(function() {
42651        return $target.val(value.replace(/\d$/, ''));
42652      });
42653    }
42654  };
42655
42656  reFormatExpiry = function(e) {
42657    return setTimeout(function() {
42658      var $target, value;
42659      $target = $(e.currentTarget);
42660      value = $target.val();
42661      value = $.payment.formatExpiry(value);
42662      return $target.val(value);
42663    });
42664  };
42665
42666  formatExpiry = function(e) {
42667    var $target, digit, val;
42668    digit = String.fromCharCode(e.which);
42669    if (!/^\d+$/.test(digit)) {
42670      return;
42671    }
42672    $target = $(e.currentTarget);
42673    val = $target.val() + digit;
42674    if (/^\d$/.test(val) && (val !== '0' && val !== '1')) {
42675      e.preventDefault();
42676      return setTimeout(function() {
42677        return $target.val("0" + val + " / ");
42678      });
42679    } else if (/^\d\d$/.test(val)) {
42680      e.preventDefault();
42681      return setTimeout(function() {
42682        return $target.val("" + val + " / ");
42683      });
42684    }
42685  };
42686
42687  formatForwardExpiry = function(e) {
42688    var $target, digit, val;
42689    digit = String.fromCharCode(e.which);
42690    if (!/^\d+$/.test(digit)) {
42691      return;
42692    }
42693    $target = $(e.currentTarget);
42694    val = $target.val();
42695    if (/^\d\d$/.test(val)) {
42696      return $target.val("" + val + " / ");
42697    }
42698  };
42699
42700  formatForwardSlashAndSpace = function(e) {
42701    var $target, val, which;
42702    which = String.fromCharCode(e.which);
42703    if (!(which === '/' || which === ' ')) {
42704      return;
42705    }
42706    $target = $(e.currentTarget);
42707    val = $target.val();
42708    if (/^\d$/.test(val) && val !== '0') {
42709      return $target.val("0" + val + " / ");
42710    }
42711  };
42712
42713  formatBackExpiry = function(e) {
42714    var $target, value;
42715    $target = $(e.currentTarget);
42716    value = $target.val();
42717    if (e.which !== 8) {
42718      return;
42719    }
42720    if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
42721      return;
42722    }
42723    if (/\d\s\/\s$/.test(value)) {
42724      e.preventDefault();
42725      return setTimeout(function() {
42726        return $target.val(value.replace(/\d\s\/\s$/, ''));
42727      });
42728    }
42729  };
42730
42731  reFormatCVC = function(e) {
42732    return setTimeout(function() {
42733      var $target, value;
42734      $target = $(e.currentTarget);
42735      value = $target.val();
42736      value = value.replace(/\D/g, '').slice(0, 4);
42737      return $target.val(value);
42738    });
42739  };
42740
42741  restrictNumeric = function(e) {
42742    var input;
42743    if (e.metaKey || e.ctrlKey) {
42744      return true;
42745    }
42746    if (e.which === 32) {
42747      return false;
42748    }
42749    if (e.which === 0) {
42750      return true;
42751    }
42752    if (e.which < 33) {
42753      return true;
42754    }
42755    input = String.fromCharCode(e.which);
42756    return !!/[\d\s]/.test(input);
42757  };
42758
42759  restrictCardNumber = function(e) {
42760    var $target, card, digit, value;
42761    $target = $(e.currentTarget);
42762    digit = String.fromCharCode(e.which);
42763    if (!/^\d+$/.test(digit)) {
42764      return;
42765    }
42766    if (hasTextSelected($target)) {
42767      return;
42768    }
42769    value = ($target.val() + digit).replace(/\D/g, '');
42770    card = cardFromNumber(value);
42771    if (card) {
42772      return value.length <= card.length[card.length.length - 1];
42773    } else {
42774      return value.length <= 16;
42775    }
42776  };
42777
42778  restrictExpiry = function(e) {
42779    var $target, digit, value;
42780    $target = $(e.currentTarget);
42781    digit = String.fromCharCode(e.which);
42782    if (!/^\d+$/.test(digit)) {
42783      return;
42784    }
42785    if (hasTextSelected($target)) {
42786      return;
42787    }
42788    value = $target.val() + digit;
42789    value = value.replace(/\D/g, '');
42790    if (value.length > 6) {
42791      return false;
42792    }
42793  };
42794
42795  restrictCVC = function(e) {
42796    var $target, digit, val;
42797    $target = $(e.currentTarget);
42798    digit = String.fromCharCode(e.which);
42799    if (!/^\d+$/.test(digit)) {
42800      return;
42801    }
42802    if (hasTextSelected($target)) {
42803      return;
42804    }
42805    val = $target.val() + digit;
42806    return val.length <= 4;
42807  };
42808
42809  setCardType = function(e) {
42810    var $target, allTypes, card, cardType, val;
42811    $target = $(e.currentTarget);
42812    val = $target.val();
42813    cardType = $.payment.cardType(val) || 'unknown';
42814    if (!$target.hasClass(cardType)) {
42815      allTypes = (function() {
42816        var _i, _len, _results;
42817        _results = [];
42818        for (_i = 0, _len = cards.length; _i < _len; _i++) {
42819          card = cards[_i];
42820          _results.push(card.type);
42821        }
42822        return _results;
42823      })();
42824      $target.removeClass('unknown');
42825      $target.removeClass(allTypes.join(' '));
42826      $target.addClass(cardType);
42827      $target.toggleClass('identified', cardType !== 'unknown');
42828      return $target.trigger('payment.cardType', cardType);
42829    }
42830  };
42831
42832  $.payment.fn.formatCardCVC = function() {
42833    this.on('keypress', restrictNumeric);
42834    this.on('keypress', restrictCVC);
42835    this.on('paste', reFormatCVC);
42836    this.on('change', reFormatCVC);
42837    this.on('input', reFormatCVC);
42838    return this;
42839  };
42840
42841  $.payment.fn.formatCardExpiry = function() {
42842    this.on('keypress', restrictNumeric);
42843    this.on('keypress', restrictExpiry);
42844    this.on('keypress', formatExpiry);
42845    this.on('keypress', formatForwardSlashAndSpace);
42846    this.on('keypress', formatForwardExpiry);
42847    this.on('keydown', formatBackExpiry);
42848    this.on('change', reFormatExpiry);
42849    this.on('input', reFormatExpiry);
42850    return this;
42851  };
42852
42853  $.payment.fn.formatCardNumber = function() {
42854    this.on('keypress', restrictNumeric);
42855    this.on('keypress', restrictCardNumber);
42856    this.on('keypress', formatCardNumber);
42857    this.on('keydown', formatBackCardNumber);
42858    this.on('keyup', setCardType);
42859    this.on('paste', reFormatCardNumber);
42860    this.on('change', reFormatCardNumber);
42861    this.on('input', reFormatCardNumber);
42862    this.on('input', setCardType);
42863    return this;
42864  };
42865
42866  $.payment.fn.restrictNumeric = function() {
42867    this.on('keypress', restrictNumeric);
42868    this.on('paste', reFormatNumeric);
42869    this.on('change', reFormatNumeric);
42870    this.on('input', reFormatNumeric);
42871    return this;
42872  };
42873
42874  $.payment.fn.cardExpiryVal = function() {
42875    return $.payment.cardExpiryVal($(this).val());
42876  };
42877
42878  $.payment.cardExpiryVal = function(value) {
42879    var month, prefix, year, _ref;
42880    value = value.replace(/\s/g, '');
42881    _ref = value.split('/', 2), month = _ref[0], year = _ref[1];
42882    if ((year != null ? year.length : void 0) === 2 && /^\d+$/.test(year)) {
42883      prefix = (new Date).getFullYear();
42884      prefix = prefix.toString().slice(0, 2);
42885      year = prefix + year;
42886    }
42887    month = parseInt(month, 10);
42888    year = parseInt(year, 10);
42889    return {
42890      month: month,
42891      year: year
42892    };
42893  };
42894
42895  $.payment.validateCardNumber = function(num) {
42896    var card, _ref;
42897    num = (num + '').replace(/\s+|-/g, '');
42898    if (!/^\d+$/.test(num)) {
42899      return false;
42900    }
42901    card = cardFromNumber(num);
42902    if (!card) {
42903      return false;
42904    }
42905    return (_ref = num.length, __indexOf.call(card.length, _ref) >= 0) && (card.luhn === false || luhnCheck(num));
42906  };
42907
42908  $.payment.validateCardExpiry = function(month, year) {
42909    var currentTime, expiry, _ref;
42910    if (typeof month === 'object' && 'month' in month) {
42911      _ref = month, month = _ref.month, year = _ref.year;
42912    }
42913    if (!(month && year)) {
42914      return false;
42915    }
42916    month = $.trim(month);
42917    year = $.trim(year);
42918    if (!/^\d+$/.test(month)) {
42919      return false;
42920    }
42921    if (!/^\d+$/.test(year)) {
42922      return false;
42923    }
42924    if (!((1 <= month && month <= 12))) {
42925      return false;
42926    }
42927    if (year.length === 2) {
42928      if (year < 70) {
42929        year = "20" + year;
42930      } else {
42931        year = "19" + year;
42932      }
42933    }
42934    if (year.length !== 4) {
42935      return false;
42936    }
42937    expiry = new Date(year, month);
42938    currentTime = new Date;
42939    expiry.setMonth(expiry.getMonth() - 1);
42940    expiry.setMonth(expiry.getMonth() + 1, 1);
42941    return expiry > currentTime;
42942  };
42943
42944  $.payment.validateCardCVC = function(cvc, type) {
42945    var card, _ref;
42946    cvc = $.trim(cvc);
42947    if (!/^\d+$/.test(cvc)) {
42948      return false;
42949    }
42950    card = cardFromType(type);
42951    if (card != null) {
42952      return _ref = cvc.length, __indexOf.call(card.cvcLength, _ref) >= 0;
42953    } else {
42954      return cvc.length >= 3 && cvc.length <= 4;
42955    }
42956  };
42957
42958  $.payment.cardType = function(num) {
42959    var _ref;
42960    if (!num) {
42961      return null;
42962    }
42963    return ((_ref = cardFromNumber(num)) != null ? _ref.type : void 0) || null;
42964  };
42965
42966  $.payment.formatCardNumber = function(num) {
42967    var card, groups, upperLength, _ref;
42968    num = num.replace(/\D/g, '');
42969    card = cardFromNumber(num);
42970    if (!card) {
42971      return num;
42972    }
42973    upperLength = card.length[card.length.length - 1];
42974    num = num.slice(0, upperLength);
42975    if (card.format.global) {
42976      return (_ref = num.match(card.format)) != null ? _ref.join(' ') : void 0;
42977    } else {
42978      groups = card.format.exec(num);
42979      if (groups == null) {
42980        return;
42981      }
42982      groups.shift();
42983      groups = $.grep(groups, function(n) {
42984        return n;
42985      });
42986      return groups.join(' ');
42987    }
42988  };
42989
42990  $.payment.formatExpiry = function(expiry) {
42991    var mon, parts, sep, year;
42992    parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/);
42993    if (!parts) {
42994      return '';
42995    }
42996    mon = parts[1] || '';
42997    sep = parts[2] || '';
42998    year = parts[3] || '';
42999    if (year.length > 0) {
43000      sep = ' / ';
43001    } else if (sep === ' /') {
43002      mon = mon.substring(0, 1);
43003      sep = '';
43004    } else if (mon.length === 2 || sep.length > 0) {
43005      sep = ' / ';
43006    } else if (mon.length === 1 && (mon !== '0' && mon !== '1')) {
43007      mon = "0" + mon;
43008      sep = ' / ';
43009    }
43010    return mon + sep + year;
43011  };
43012
43013}).call(this);
43014(function() {
43015  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
43016    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
43017    hasProp = {}.hasOwnProperty;
43018
43019  App.Pages.CheckoutPayment = (function(superClass) {
43020    var prepareTrackViewItemList;
43021
43022    extend(CheckoutPayment, superClass);
43023
43024    function CheckoutPayment() {
43025      this.checkFormValidation = bind(this.checkFormValidation, this);
43026      this.openRequiredTab = bind(this.openRequiredTab, this);
43027      this.openGuestModal = bind(this.openGuestModal, this);
43028      this.switchTab = bind(this.switchTab, this);
43029      this.onCountrySelect = bind(this.onCountrySelect, this);
43030      this.onV2CompanyToggle = bind(this.onV2CompanyToggle, this);
43031      this.onCompanyToggle = bind(this.onCompanyToggle, this);
43032      this.onDiscountTriggerV3 = bind(this.onDiscountTriggerV3, this);
43033      this.onDiscountTriggerV2 = bind(this.onDiscountTriggerV2, this);
43034      this.onDiscountTrigger = bind(this.onDiscountTrigger, this);
43035      this.trackCartRemove = bind(this.trackCartRemove, this);
43036      this.handleRequestInvoiceError = bind(this.handleRequestInvoiceError, this);
43037      this.handleRequestInvoiceSuccess = bind(this.handleRequestInvoiceSuccess, this);
43038      this.handleCartShareError = bind(this.handleCartShareError, this);
43039      this.handleCartShareSuccess = bind(this.handleCartShareSuccess, this);
43040      this.bindRequestInvoiceFormEvents = bind(this.bindRequestInvoiceFormEvents, this);
43041      this.bindShareableCartFormEvents = bind(this.bindShareableCartFormEvents, this);
43042      this.initRequestInvoiceValidation = bind(this.initRequestInvoiceValidation, this);
43043      this.initShareCartValidation = bind(this.initShareCartValidation, this);
43044      this.openRequestInvoiceModal = bind(this.openRequestInvoiceModal, this);
43045      this.openShareableCartModal = bind(this.openShareableCartModal, this);
43046      this.bindShareableCartModal = bind(this.bindShareableCartModal, this);
43047      this.bindRequestInvoiceModal = bind(this.bindRequestInvoiceModal, this);
43048      return CheckoutPayment.__super__.constructor.apply(this, arguments);
43049    }
43050
43051    CheckoutPayment.prototype.widgets = ['storeCreditsInfoTooltip', 'vatTaxTooltip', 'creditsUsedInfoTooltip', 'postalCodeTooltip', 'CVCTooltip', 'V2WishLists'];
43052
43053    CheckoutPayment.prototype.initialize = function() {
43054      this.cacheDom();
43055      this.$discountForm = $('.checkout-discount-form').find('form');
43056      this.initEditCartTrigger();
43057      this.bindShareableCartModal();
43058      this.bindRequestInvoiceModal();
43059      this.bindSpinnerForItemRemove();
43060      this.bindCheckoutClick();
43061      this.disableItemOnRemove();
43062      this.bindSpinnerForPaypalPurchase();
43063      this.handleCouponPurchase();
43064      this.initDiscountTrigger();
43065      this.initDiscountTriggerV2();
43066      this.initDiscountTriggerV3();
43067      this.initValidation();
43068      this.initBuyerType();
43069      this.initCountrySelection();
43070      if (gon.edit_cart) {
43071        this.initEditCartMode();
43072      }
43073      this.bindApplyCompanyInformation();
43074      this.initShareCartValidation();
43075      this.initRequestInvoiceValidation();
43076      this.bindTabClick();
43077      this.bindGuestModalOpen();
43078      this.openRequiredTab();
43079      return this.initStreetAddressCounter();
43080    };
43081
43082    CheckoutPayment.prototype.cacheDom = function() {
43083      this.$shareCartModal = $("#modal-share-cart");
43084      return this.$requestInvoiceModal = $("#modal-request-invoice");
43085    };
43086
43087    CheckoutPayment.prototype.bindRequestInvoiceModal = function() {
43088      if (!(this.$requestInvoiceModal.length > 0)) {
43089        return;
43090      }
43091      this.$requestForm = this.$requestInvoiceModal.find('#request-invoice-form');
43092      return $('.js-checkout-request-invoice').on('click', this.openRequestInvoiceModal);
43093    };
43094
43095    CheckoutPayment.prototype.bindShareableCartModal = function() {
43096      if (!(this.$shareCartModal.length > 0)) {
43097        return;
43098      }
43099      this.$shareForm = this.$shareCartModal.find('#share-cart-form');
43100      return $('.js-checkout-cart-share').on('click', this.openShareableCartModal);
43101    };
43102
43103    CheckoutPayment.prototype.openShareableCartModal = function() {
43104      this.$shareCartModal.makeMeModal();
43105      return this.bindShareableCartFormEvents();
43106    };
43107
43108    CheckoutPayment.prototype.openRequestInvoiceModal = function() {
43109      this.$requestInvoiceModal.makeMeModal();
43110      return this.bindRequestInvoiceFormEvents();
43111    };
43112
43113    CheckoutPayment.prototype.shareCartValidationSettings = function() {
43114      return {
43115        focusInvalid: false,
43116        rules: {
43117          email: {
43118            required: true,
43119            email: true
43120          }
43121        }
43122      };
43123    };
43124
43125    CheckoutPayment.prototype.requestInvoiceValidationSettings = function() {
43126      return {
43127        focusInvalid: false,
43128        rules: {
43129          email: {
43130            required: true,
43131            email: true
43132          },
43133          message: {
43134            required: true
43135          }
43136        }
43137      };
43138    };
43139
43140    CheckoutPayment.prototype.initShareCartValidation = function() {
43141      if (this.$shareForm) {
43142        return this.$shareForm.validate(this.shareCartValidationSettings());
43143      }
43144    };
43145
43146    CheckoutPayment.prototype.initRequestInvoiceValidation = function() {
43147      if (this.$requestForm) {
43148        return this.$requestForm.validate(this.requestInvoiceValidationSettings());
43149      }
43150    };
43151
43152    CheckoutPayment.prototype.bindShareableCartFormEvents = function() {
43153      this.$shareForm.off('ajax:success').on('ajax:success', this.handleCartShareSuccess);
43154      return this.$shareForm.off('ajax:error').on('ajax:error', this.handleCartShareError);
43155    };
43156
43157    CheckoutPayment.prototype.bindRequestInvoiceFormEvents = function() {
43158      this.$requestForm.off('ajax:success').on('ajax:success', this.handleRequestInvoiceSuccess);
43159      return this.$requestForm.off('ajax:error').on('ajax:error', this.handleRequestInvoiceError);
43160    };
43161
43162    CheckoutPayment.prototype.handleCartShareSuccess = function(e, data, status, xhr) {
43163      if (data.success) {
43164        App.events.publish('app:notification:flash:success', data.message);
43165      } else {
43166        App.events.publish('app:notification:flash:error', data.message);
43167      }
43168      return this.$shareCartModal.makeMeModal('close');
43169    };
43170
43171    CheckoutPayment.prototype.handleCartShareError = function() {
43172      App.events.publish('app:notification:flash:error', 'Oops. Something went wrong.');
43173      return this.$shareCartModal.makeMeModal('close');
43174    };
43175
43176    CheckoutPayment.prototype.handleRequestInvoiceSuccess = function(e, data, status, xhr) {
43177      if (data.success) {
43178        App.events.publish('app:notification:flash:success', data.message);
43179      } else {
43180        App.events.publish('app:notification:flash:error', data.message);
43181      }
43182      return this.$requestInvoiceModal.makeMeModal('close');
43183    };
43184
43185    CheckoutPayment.prototype.handleRequestInvoiceError = function() {
43186      App.events.publish('app:notification:flash:error', 'Oops. Something went wrong.');
43187      return this.$requestInvoiceModal.makeMeModal('close');
43188    };
43189
43190    CheckoutPayment.prototype.bindApplyCompanyInformation = function() {
43191      return $('.js-apply-company-information').on('click', (function(_this) {
43192        return function() {
43193          $('.js-company-details').addClass('is-updating-with-spinner');
43194          return $('.js-apply-company-information-hidden-field').val(true);
43195        };
43196      })(this));
43197    };
43198
43199    CheckoutPayment.prototype.bindCheckoutClick = function() {
43200      var element;
43201      element = $('.js-checkout-click');
43202      element.on("auxclick", function(event) {
43203        var data, list;
43204        if (event.button === 1) {
43205          list = [];
43206          for (data in gon.cart_items) {
43207            list.push(prepareTrackViewItemList(gon.cart_items[data]));
43208            continue;
43209          }
43210          if (window.dataLayer && list.length > 0) {
43211            return window.dataLayer.push({
43212              event: 'begin_checkout',
43213              ecommerce: {
43214                currency: 'USD',
43215                value: gon.cart.value,
43216                items: list
43217              }
43218            });
43219          }
43220        }
43221      });
43222      return element.on('click', function(e) {
43223        var data, list;
43224        list = [];
43225        for (data in gon.cart_items) {
43226          list.push(prepareTrackViewItemList(gon.cart_items[data]));
43227          continue;
43228        }
43229        if (window.dataLayer && list.length > 0) {
43230          return window.dataLayer.push({
43231            event: 'begin_checkout',
43232            ecommerce: {
43233              currency: 'USD',
43234              value: gon.cart.value,
43235              items: list
43236            }
43237          });
43238        }
43239      });
43240    };
43241
43242    CheckoutPayment.prototype.bindSpinnerForItemRemove = function() {
43243      return $('.js-remove-from-cart').on('click', function(e) {
43244        return $(e.currentTarget).closest('.product-list__item').addClass('is-updating-with-spinner spinner--horizontal');
43245      });
43246    };
43247
43248    CheckoutPayment.prototype.trackCartRemove = function(e) {
43249      var items, obj, targetObj;
43250      if (window.dataLayer) {
43251        targetObj = ((function() {
43252          var i, len, ref, results;
43253          ref = gon.cart_items;
43254          results = [];
43255          for (i = 0, len = ref.length; i < len; i++) {
43256            obj = ref[i];
43257            if (obj.id === $(e.currentTarget).data('itemId')) {
43258              results.push(obj);
43259            }
43260          }
43261          return results;
43262        })())[0];
43263        items = prepareTrackViewItemList(targetObj);
43264        return window.dataLayer.push({
43265          event: 'remove_from_cart',
43266          user_id: App.config.userId || '',
43267          ecommerce: {
43268            currency: 'USD',
43269            value: items.price,
43270            items: items
43271          },
43272          _clear: true
43273        });
43274      }
43275    };
43276
43277    CheckoutPayment.prototype.disableItemOnRemove = function() {
43278      var that;
43279      that = this;
43280      return $('.js-v2-remove-from-cart').on('click', function(e) {
43281        that.trackCartRemove(e);
43282        $(e.currentTarget).closest('.cgt-item-card').addClass('is-updating');
43283        return App.events.publish("app:notification:flash:success", 'Item was removed from cart');
43284      });
43285    };
43286
43287    CheckoutPayment.prototype.bindSpinnerForPaypalPurchase = function() {
43288      var that;
43289      that = this;
43290      return $('.js-payment-confirmation').on('click', function() {
43291        that.trackOtpPayClick('paypal');
43292        return $('.js-payment-methods').addClass('is-updating-with-spinner');
43293      });
43294    };
43295
43296    CheckoutPayment.prototype.handleCouponPurchase = function() {
43297      var that;
43298      that = this;
43299      return $('.js-payment-confirmation-v2').on('click', function() {
43300        that.trackOtpPayClick('coupon');
43301        return $('.js-payment-coupon-form-v2 form').submit();
43302      });
43303    };
43304
43305    CheckoutPayment.prototype.initEditCartMode = function() {
43306      $('.js-checkout-cart-edit').remove();
43307      return $('.js-remove-from-cart').show();
43308    };
43309
43310    CheckoutPayment.prototype.initEditCartTrigger = function() {
43311      return $('.js-checkout-cart-edit').on('click', this.onEditCartTrigger);
43312    };
43313
43314    CheckoutPayment.prototype.initDiscountTrigger = function() {
43315      return $('.checkout-content').on('click', '.checkout-discount-form-trigger', this.onDiscountTrigger);
43316    };
43317
43318    CheckoutPayment.prototype.initDiscountTriggerV2 = function() {
43319      $('.js-checkout-apply-enterprise-coupon').on('change', this.onDiscountTriggerV2);
43320      return $('.js-coupon-form-toggle').on('click', this.toggleCouponForm);
43321    };
43322
43323    CheckoutPayment.prototype.initDiscountTriggerV3 = function() {
43324      return $('.js-business-account-apply').on('change', this.onDiscountTriggerV3);
43325    };
43326
43327    CheckoutPayment.prototype.validationSettings = function() {
43328      return {
43329        focusInvalid: false,
43330        rules: {
43331          coupon_code: {
43332            required: true
43333          }
43334        },
43335        messages: {
43336          coupon_code: {
43337            required: "Discount code can't be blank"
43338          }
43339        },
43340        errorPlacement: function(error, element) {
43341          if (element.attr('name') === 'coupon_code') {
43342            return error.insertAfter('.js-checkout-payment-apply-coupon');
43343          } else {
43344            return error.insertAfter(element);
43345          }
43346        }
43347      };
43348    };
43349
43350    CheckoutPayment.prototype.initValidation = function() {
43351      return this.$discountForm.validate(this.validationSettings());
43352    };
43353
43354    CheckoutPayment.prototype.initBuyerType = function() {
43355      $('.js-checkout-vat-company-details-toggle').on('change', this.onCompanyToggle);
43356      $('.js-company-details-toggle').on('change', this.onV2CompanyToggle);
43357      this.$buyerType = $('#buyer_type');
43358      this.$companyForm = $('.billing-address-container');
43359      this.$v2CompanyForm = $('.js-company-details-card');
43360      this.$companyAlert = $('.js-company-alert');
43361      this.$v2PaymentForm = $('.js-adyen-form-container, .js-braintree-form-container');
43362      if ($('.js-checkout-vat-company-details-toggle').is(':checked')) {
43363        this.$companyForm.hide().removeClass('is-hidden').slideDown();
43364      }
43365      if ($('.js-company-details-toggle').is(':checked')) {
43366        this.$v2CompanyForm.removeClass('is-collapsed');
43367        if (this.$companyAlert.data('errors')) {
43368          return this.$v2PaymentForm.addClass('is-disabled');
43369        }
43370      }
43371    };
43372
43373    CheckoutPayment.prototype.initCountrySelection = function() {
43374      this.$countrySelectField = $('#billing_address_country');
43375      this.$usStateSelectField = $('.us-state-selector');
43376      this.$canadaStateSelectField = $('.ca-state-selector');
43377      this.$indiaStateSelectField = $('.in-state-selector');
43378      if (this.$countrySelectField.val() !== 'us') {
43379        this.$usStateSelectField.addClass('is-hidden');
43380      }
43381      if (this.$countrySelectField.val() !== 'ca') {
43382        this.$canadaStateSelectField.addClass('is-hidden');
43383      }
43384      if (this.$countrySelectField.val() !== 'in') {
43385        this.$indiaStateSelectField.addClass('is-hidden');
43386      }
43387      return this.$countrySelectField.on('change', this.onCountrySelect);
43388    };
43389
43390    CheckoutPayment.prototype.onEditCartTrigger = function() {
43391      $('.js-checkout-cart-edit').remove();
43392      return $('.js-remove-from-cart').show();
43393    };
43394
43395    CheckoutPayment.prototype.onDiscountTrigger = function(e) {
43396      var $el;
43397      $el = $(e.currentTarget);
43398      return $el.fadeOut().promise().done(function() {
43399        return $('.checkout-discount-form').fadeIn();
43400      });
43401    };
43402
43403    CheckoutPayment.prototype.onDiscountTriggerV2 = function(e) {
43404      if (e.currentTarget.checked) {
43405        return $('.js-checkout-enterprise-coupon-form').submit();
43406      }
43407    };
43408
43409    CheckoutPayment.prototype.onDiscountTriggerV3 = function(e) {
43410      if (e.currentTarget.checked) {
43411        return $('.js-checkout-business-account-form').submit();
43412      }
43413    };
43414
43415    CheckoutPayment.prototype.toggleCouponForm = function() {
43416      return $('.js-coupon-form').toggleClass('is-collapsed');
43417    };
43418
43419    CheckoutPayment.prototype.onCompanyToggle = function() {
43420      if (this.$companyForm.hasClass('is-hidden')) {
43421        return this.buyerIsCompany();
43422      } else {
43423        $('.js-company-details').addClass('is-updating-with-spinner');
43424        return this.buyerIsPerson();
43425      }
43426    };
43427
43428    CheckoutPayment.prototype.onV2CompanyToggle = function() {
43429      if (this.$v2CompanyForm.hasClass('is-collapsed')) {
43430        return this.buyerIsCompany();
43431      } else {
43432        return this.buyerIsPerson();
43433      }
43434    };
43435
43436    CheckoutPayment.prototype.onCountrySelect = function() {
43437      this.$usStateSelectField.addClass('is-hidden');
43438      this.$canadaStateSelectField.addClass('is-hidden');
43439      this.$indiaStateSelectField.addClass('is-hidden');
43440      if (this.$countrySelectField.val() === 'us') {
43441        this.$usStateSelectField.removeClass('is-hidden');
43442      }
43443      if (this.$countrySelectField.val() === 'ca') {
43444        this.$canadaStateSelectField.removeClass('is-hidden');
43445      }
43446      if (this.$countrySelectField.val() === 'in') {
43447        return this.$indiaStateSelectField.removeClass('is-hidden');
43448      }
43449    };
43450
43451    CheckoutPayment.prototype.buyerIsCompany = function() {
43452      var value;
43453      this.$buyerType.val('business');
43454      this.$companyForm.hide().removeClass('is-hidden').slideDown();
43455      this.$v2CompanyForm.removeClass('is-collapsed');
43456      value = $('.js-apply-company-information-hidden-field').val();
43457      if (!!value) {
43458        this.checkFormValidation();
43459      }
43460      if (this.$companyAlert.data('errors')) {
43461        return this.$v2PaymentForm.addClass('is-disabled');
43462      }
43463    };
43464
43465    CheckoutPayment.prototype.buyerIsPerson = function() {
43466      this.$buyerType.val('person');
43467      $('.js-apply-company-information').click();
43468      this.$v2CompanyForm.addClass('is-collapsed');
43469      this.$companyAlert.addClass('is-alert-row-hidden');
43470      return this.$v2PaymentForm.removeClass('is-disabled');
43471    };
43472
43473    CheckoutPayment.prototype.bindTabClick = function() {
43474      return $('.js-tab').on('click', this.switchTab);
43475    };
43476
43477    CheckoutPayment.prototype.bindGuestModalOpen = function() {
43478      return $('.js-guest-checkout-click').on('click', this.openGuestModal);
43479    };
43480
43481    CheckoutPayment.prototype.switchTab = function(e) {
43482      e.preventDefault();
43483      e.stopPropagation();
43484      this.$tab = $(e.currentTarget);
43485      if (this.$tab.hasClass('cgt-tab--active')) {
43486
43487      } else {
43488        this.hideTabs();
43489        this.displayTab(this.$tab.data("tab"));
43490        if (e.target.dataset['tab'] === 'payment') {
43491          return this.trackCartToPayment();
43492        }
43493      }
43494    };
43495
43496    CheckoutPayment.prototype.openGuestModal = function(e) {
43497      e.preventDefault();
43498      e.stopPropagation();
43499      return App.events.publish('app:guest:modal:open', e);
43500    };
43501
43502    CheckoutPayment.prototype.trackCartToPayment = function() {
43503      return $.ajax({
43504        url: '/cg/checkout/track-cart-to-payment-step',
43505        type: 'PUT'
43506      });
43507    };
43508
43509    CheckoutPayment.prototype.trackCartToLogin = function() {
43510      return $.ajax({
43511        url: '/cg/checkout/track-cart-to-login-step',
43512        type: 'PUT'
43513      });
43514    };
43515
43516    CheckoutPayment.prototype.trackPaymentVisit = function() {
43517      return $.ajax({
43518        url: '/cg/checkout/track-payment-visit',
43519        type: 'PUT'
43520      });
43521    };
43522
43523    CheckoutPayment.prototype.trackCartVisit = function() {
43524      var data, list;
43525      if (gon.cart_items) {
43526        list = [];
43527        for (data in gon.cart_items) {
43528          list.push(prepareTrackViewItemList(gon.cart_items[data]));
43529          continue;
43530        }
43531        if (window.dataLayer && list.length > 0) {
43532          return window.dataLayer.push({
43533            event: 'view_cart',
43534            ecommerce: {
43535              currency: 'USD',
43536              value: gon.cart.value,
43537              items: list
43538            }
43539          });
43540        }
43541      }
43542    };
43543
43544    CheckoutPayment.prototype.trackOtpCheckoutViewed = function() {
43545      var checkoutSource, eventPayload;
43546      checkoutSource = gon.is_buy_now ? 'buy_now' : 'cart';
43547      eventPayload = {
43548        name: 'one_time_purchase_checkout_viewed',
43549        eventData: {
43550          event_source: checkoutSource
43551        }
43552      };
43553      if (window.customEventTracker) {
43554        return window.customEventTracker(eventPayload);
43555      } else {
43556        return App.events.subscribe('app:tracking:load', function() {
43557          return window.customEventTracker(eventPayload);
43558        });
43559      }
43560    };
43561
43562    CheckoutPayment.prototype.trackOtpPayClick = function(paymentMethod) {
43563      var checkoutSource, eventPayload;
43564      checkoutSource = gon.is_buy_now ? 'buy_now' : 'cart';
43565      eventPayload = {
43566        name: 'one_time_purchase_pay_click',
43567        eventData: {
43568          event_source: checkoutSource,
43569          payment_method: paymentMethod
43570        }
43571      };
43572      if (window.customEventTracker) {
43573        return window.customEventTracker(eventPayload);
43574      }
43575    };
43576
43577    CheckoutPayment.prototype.openRequiredTab = function() {
43578      var error, requestedTab;
43579      requestedTab = App.config.viewerId ? window.location.hash === '#cart' ? '#cart' : '#payment' : window.location.hash === '#cart' ? '#cart' : ($('body').addClass('show-payment'), App.events.publish('app:guest:modal:required'), '#cart');
43580      try {
43581        if (!App.config.viewerId) {
43582          this.removeReactModalClass();
43583        }
43584      } catch (error1) {
43585        error = error1;
43586        window.Sentry.captureException(error);
43587      }
43588      try {
43589        return this.displayTab(requestedTab.replace('#', ''));
43590      } catch (error1) {
43591        error = error1;
43592        return window.Sentry.captureException(error);
43593      }
43594    };
43595
43596    CheckoutPayment.prototype.hideTabs = function() {
43597      $('.cgt-tab-content.is-active').removeClass('is-active');
43598      return $('.cgt-tab.cgt-tab--active').removeClass('cgt-tab--active');
43599    };
43600
43601    CheckoutPayment.prototype.displayTab = function(tab) {
43602      if (!tab) {
43603        window.Sentry.captureException(new Error('No tab value provided to "displayTab" function!'));
43604      }
43605      if (tab === 'payment') {
43606        this.trackPaymentVisit();
43607        this.trackOtpCheckoutViewed();
43608      }
43609      if (tab === 'cart') {
43610        this.trackCartVisit();
43611      }
43612      window.location.hash = tab;
43613      $('.js-loader-container').hide();
43614      $('.cgt-tab[data-tab=' + tab + ']').addClass('cgt-tab--active');
43615      return $('#' + tab).addClass('is-active');
43616    };
43617
43618    CheckoutPayment.prototype.removeReactModalClass = function() {
43619      var removeClass;
43620      return removeClass = setInterval(function() {
43621        if ($('body').hasClass('ReactModal__Body--open')) {
43622          $('body').removeClass('ReactModal__Body--open');
43623          return clearInterval(removeClass);
43624        }
43625      }, 500);
43626    };
43627
43628    CheckoutPayment.prototype.checkFormValidation = function() {
43629      this.$v2PaymentForm.addClass('is-disabled');
43630      return this.$companyAlert.removeClass('is-alert-row-hidden');
43631    };
43632
43633    CheckoutPayment.prototype.initStreetAddressCounter = function() {
43634      var $count, $counter, $input, maxLength, updateCounter, warningThreshold;
43635      $input = $('.js-street-address-input');
43636      $counter = $('.js-street-address-counter');
43637      $count = $('.js-street-address-count');
43638      maxLength = 200;
43639      warningThreshold = 180;
43640      updateCounter = function() {
43641        var length, ref;
43642        length = ((ref = $input.val()) != null ? ref.length : void 0) || 0;
43643        $count.text(length);
43644        $counter.removeClass('street-address-counter--warning street-address-counter--danger');
43645        if (length >= maxLength) {
43646          return $counter.addClass('street-address-counter--danger');
43647        } else if (length >= warningThreshold) {
43648          return $counter.addClass('street-address-counter--warning');
43649        }
43650      };
43651      $input.on('input', updateCounter);
43652      return updateCounter();
43653    };
43654
43655    prepareTrackViewItemList = function(attributes) {
43656      var searchQuery, trackable_attributes;
43657      searchQuery = attributes.searchQuery;
43658      trackable_attributes = {
43659        item_id: String(attributes.id),
43660        item_name: attributes.title,
43661        affiliation: attributes.affiliation,
43662        currency: 'USD',
43663        index: parseInt(attributes.index) || null,
43664        item_brand: attributes.authorUsername,
43665        item_category: attributes.category,
43666        item_category2: attributes.subcategory,
43667        item_category3: attributes.itemCategory3,
43668        item_category4: attributes.itemCategory4,
43669        item_category5: attributes.itemCategory5,
43670        item_list_id: searchQuery,
43671        item_list_name: searchQuery,
43672        item_variant: attributes.productType,
43673        location_id: attributes.locationId && attributes.locationId.join('_'),
43674        price: parseFloat(attributes.price),
43675        quantity: 1,
43676        discount: parseFloat(attributes.discount) || 0
43677      };
43678      return trackable_attributes;
43679    };
43680
43681    return CheckoutPayment;
43682
43683  })(App.Core.Page);
43684
43685}).call(this);
43686(function() {
43687  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
43688    hasProp = {}.hasOwnProperty;
43689
43690  App.Pages.CheckoutPlaceOrder = (function(superClass) {
43691    extend(CheckoutPlaceOrder, superClass);
43692
43693    function CheckoutPlaceOrder() {
43694      return CheckoutPlaceOrder.__super__.constructor.apply(this, arguments);
43695    }
43696
43697    return CheckoutPlaceOrder;
43698
43699  })(App.Pages.CheckoutPayment);
43700
43701}).call(this);
43702(function() {
43703  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
43704    hasProp = {}.hasOwnProperty;
43705
43706  App.Pages.CommunityIndex = (function(superClass) {
43707    extend(CommunityIndex, superClass);
43708
43709    function CommunityIndex() {
43710      return CommunityIndex.__super__.constructor.apply(this, arguments);
43711    }
43712
43713    CommunityIndex.prototype.widgets = ['linkFaker'];
43714
43715    CommunityIndex.prototype.initialize = function() {
43716      this.bindEvents();
43717      return new LazyLoad({
43718        elements_selector: '.js-lazy-img'
43719      });
43720    };
43721
43722    CommunityIndex.prototype.bindEvents = function() {
43723      return $('.js-header-block').hover(this.onHeaderBlockHover, this.onHeaderBlockHoverOut);
43724    };
43725
43726    CommunityIndex.prototype.onHeaderBlockHover = function(e) {
43727      var $target;
43728      $target = $($(e.currentTarget).data('target'));
43729      return $target.addClass('is-hover');
43730    };
43731
43732    CommunityIndex.prototype.onHeaderBlockHoverOut = function(e) {
43733      var $target;
43734      $target = $($(e.currentTarget).data('target'));
43735      return $target.removeClass('is-hover');
43736    };
43737
43738    return CommunityIndex;
43739
43740  })(App.Core.Page);
43741
43742}).call(this);
43743/*!
43744 * hoverIntent r7 // 2013.03.11 // jQuery 1.9.1+
43745 * http://cherne.net/brian/resources/jquery.hoverIntent.html
43746 *
43747 * You may use hoverIntent under the terms of the MIT license. Basically that
43748 * means you are free to use hoverIntent as long as this header is left intact.
43749 * Copyright 2007, 2013 Brian Cherne
43750 */
43751 
43752/* hoverIntent is similar to jQuery's built-in "hover" method except that
43753 * instead of firing the handlerIn function immediately, hoverIntent checks
43754 * to see if the user's mouse has slowed down (beneath the sensitivity
43755 * threshold) before firing the event. The handlerOut function is only
43756 * called after a matching handlerIn.
43757 *
43758 * // basic usage ... just like .hover()
43759 * .hoverIntent( handlerIn, handlerOut )
43760 * .hoverIntent( handlerInOut )
43761 *
43762 * // basic usage ... with event delegation!
43763 * .hoverIntent( handlerIn, handlerOut, selector )
43764 * .hoverIntent( handlerInOut, selector )
43765 *
43766 * // using a basic configuration object
43767 * .hoverIntent( config )
43768 *
43769 * @param  handlerIn   function OR configuration object
43770 * @param  handlerOut  function OR selector for delegation OR undefined
43771 * @param  selector    selector OR undefined
43772 * @author Brian Cherne <brian(at)cherne(dot)net>
43773 */
43774
43775(function($) {
43776    $.fn.hoverIntent = function(handlerIn,handlerOut,selector) {
43777
43778        // default configuration values
43779        var cfg = {
43780            interval: 100,
43781            sensitivity: 7,
43782            timeout: 0
43783        };
43784
43785        if ( typeof handlerIn === "object" ) {
43786            cfg = $.extend(cfg, handlerIn );
43787        } else if ($.isFunction(handlerOut)) {
43788            cfg = $.extend(cfg, { over: handlerIn, out: handlerOut, selector: selector } );
43789        } else {
43790            cfg = $.extend(cfg, { over: handlerIn, out: handlerIn, selector: handlerOut } );
43791        }
43792
43793        // instantiate variables
43794        // cX, cY = current X and Y position of mouse, updated by mousemove event
43795        // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
43796        var cX, cY, pX, pY;
43797
43798        // A private function for getting mouse position
43799        var track = function(ev) {
43800            cX = ev.pageX;
43801            cY = ev.pageY;
43802        };
43803
43804        // A private function for comparing current and previous mouse position
43805        var compare = function(ev,ob) {
43806            ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
43807            // compare mouse positions to see if they've crossed the threshold
43808            if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
43809                $(ob).off("mousemove.hoverIntent",track);
43810                // set hoverIntent state to true (so mouseOut can be called)
43811                ob.hoverIntent_s = 1;
43812                return cfg.over.apply(ob,[ev]);
43813            } else {
43814                // set previous coordinates for next time
43815                pX = cX; pY = cY;
43816                // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
43817                ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
43818            }
43819        };
43820
43821        // A private function for delaying the mouseOut function
43822        var delay = function(ev,ob) {
43823            ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
43824            ob.hoverIntent_s = 0;
43825            return cfg.out.apply(ob,[ev]);
43826        };
43827
43828        // A private function for handling mouse 'hovering'
43829        var handleHover = function(e) {
43830            // copy objects to be passed into t (required for event object to be passed in IE)
43831            var ev = jQuery.extend({},e);
43832            var ob = this;
43833
43834            // cancel hoverIntent timer if it exists
43835            if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
43836
43837            // if e.type == "mouseenter"
43838            if (e.type == "mouseenter") {
43839                // set "previous" X and Y position based on initial entry point
43840                pX = ev.pageX; pY = ev.pageY;
43841                // update "current" X and Y position based on mousemove
43842                $(ob).on("mousemove.hoverIntent",track);
43843                // start polling interval (self-calling timeout) to compare mouse coordinates over time
43844                if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
43845
43846                // else e.type == "mouseleave"
43847            } else {
43848                // unbind expensive mousemove event
43849                $(ob).off("mousemove.hoverIntent",track);
43850                // if hoverIntent state is true, then call the mouseOut function after the specified delay
43851                if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
43852            }
43853        };
43854
43855        // listen for mouseenter and mouseleave
43856        return this.on({'mouseenter.hoverIntent':handleHover,'mouseleave.hoverIntent':handleHover}, cfg.selector);
43857    };
43858})(jQuery);
43859
43860/*
43861	By Osvaldas Valutis, www.osvaldas.info
43862	Available for use under the MIT License
43863*/
43864
43865
43866;( function( $, window, document, undefined )
43867{
43868	'use strict';
43869
43870	var cssTransitionSupport = function()
43871		{
43872			var s = document.body || document.documentElement, s = s.style;
43873			if( s.WebkitTransition == '' ) return '-webkit-';
43874			if( s.MozTransition == '' ) return '-moz-';
43875			if( s.OTransition == '' ) return '-o-';
43876			if( s.transition == '' ) return '';
43877			return false;
43878		},
43879
43880		isCssTransitionSupport = cssTransitionSupport() === false ? false : true,
43881
43882		cssTransitionTranslateX = function( element, positionX, speed )
43883		{
43884			var options = {}, prefix = cssTransitionSupport();
43885			options[ prefix + 'transform' ]	 = 'translateX(' + positionX + ')';
43886			options[ prefix + 'transition' ] = prefix + 'transform ' + speed + 's linear';
43887			element.css( options );
43888		},
43889
43890		hasTouch	= ( 'ontouchstart' in window ),
43891		hasPointers = window.navigator.pointerEnabled || window.navigator.msPointerEnabled,
43892		wasTouched	= function( event )
43893		{
43894			if( hasTouch )
43895				return true;
43896
43897			if( !hasPointers || typeof event === 'undefined' || typeof event.pointerType === 'undefined' )
43898				return false;
43899
43900			if( typeof event.MSPOINTER_TYPE_MOUSE !== 'undefined' )
43901			{
43902				if( event.MSPOINTER_TYPE_MOUSE != event.pointerType )
43903					return true;
43904			}
43905			else
43906				if( event.pointerType != 'mouse' )
43907					return true;
43908
43909			return false;
43910		};
43911
43912	$.fn.imageLightbox = function( options )
43913	{
43914		var options	   = $.extend(
43915						 {
43916						 	selector:		'id="imagelightbox"',
43917						 	allowedTypes:	'png|jpg|jpeg|gif',
43918						 	animationSpeed:	250,
43919						 	preloadNext:	true,
43920						 	enableKeyboard:	true,
43921						 	quitOnEnd:		false,
43922						 	quitOnImgClick: false,
43923						 	quitOnDocClick: true,
43924						 	onStart:		false,
43925						 	onEnd:			false,
43926						 	onLoadStart:	false,
43927						 	onLoadEnd:		false
43928						 },
43929						 options ),
43930
43931			targets		= $([]),
43932			target		= $(),
43933			image		= $(),
43934			imageWidth	= 0,
43935			imageHeight = 0,
43936			swipeDiff	= 0,
43937			inProgress	= false,
43938
43939			isTargetValid = function( element )
43940			{
43941				return $( element ).prop( 'tagName' ).toLowerCase() == 'a' && ( new RegExp( '\.(' + options.allowedTypes + ')$', 'i' ) ).test( $( element ).attr( 'href' ) );
43942			},
43943
43944			setImage = function()
43945			{
43946				if( !image.length ) return false;
43947
43948				var screenWidth	 = $( window ).width() * 0.8,
43949					screenHeight = $( window ).height() * 0.9,
43950					tmpImage 	 = new Image();
43951
43952				tmpImage.src	= image.attr( 'src' );
43953				tmpImage.onload = function()
43954				{
43955					imageWidth	 = tmpImage.width;
43956					imageHeight	 = tmpImage.height;
43957
43958					if( imageWidth > screenWidth || imageHeight > screenHeight )
43959					{
43960						var ratio	 = imageWidth / imageHeight > screenWidth / screenHeight ? imageWidth / screenWidth : imageHeight / screenHeight;
43961						imageWidth	/= ratio;
43962						imageHeight	/= ratio;
43963					}
43964
43965					image.css(
43966					{
43967						'width':  imageWidth + 'px',
43968						'height': imageHeight + 'px',
43969						'top':    ( $( window ).height() - imageHeight ) / 2 + 'px',
43970						'left':   ( $( window ).width() - imageWidth ) / 2 + 'px'
43971					});
43972				};
43973			},
43974
43975			loadImage = function( direction )
43976			{
43977				if( inProgress ) return false;
43978
43979				direction = typeof direction === 'undefined' ? false : direction == 'left' ? 1 : -1;
43980
43981				if( image.length )
43982				{
43983					if( direction !== false && ( targets.length < 2 || ( options.quitOnEnd === true && ( ( direction === -1 && targets.index( target ) == 0 ) || ( direction === 1 && targets.index( target ) == targets.length - 1 ) ) ) ) )
43984					{
43985						quitLightbox();
43986						return false;
43987					}
43988					var params = { 'opacity': 0 };
43989					if( isCssTransitionSupport ) cssTransitionTranslateX( image, ( 100 * direction ) - swipeDiff + 'px', options.animationSpeed / 1000 );
43990					else params.left = parseInt( image.css( 'left' ) ) + 100 * direction + 'px';
43991					image.animate( params, options.animationSpeed, function(){ removeImage(); });
43992					swipeDiff = 0;
43993				}
43994
43995				inProgress = true;
43996				if( options.onLoadStart !== false ) options.onLoadStart();
43997
43998				setTimeout( function()
43999				{
44000					image = $( '<img ' + options.selector + ' />' )
44001					.attr( 'src', target.attr( 'href' ) )
44002					.load( function()
44003					{
44004						image.appendTo( 'body' );
44005						setImage();
44006
44007						var params = { 'opacity': 1 };
44008
44009						image.css( 'opacity', 0 );
44010						if( isCssTransitionSupport )
44011						{
44012							cssTransitionTranslateX( image, -100 * direction + 'px', 0 );
44013							setTimeout( function(){ cssTransitionTranslateX( image, 0 + 'px', options.animationSpeed / 1000 ) }, 50 );
44014						}
44015						else
44016						{
44017							var imagePosLeft = parseInt( image.css( 'left' ) );
44018							params.left = imagePosLeft + 'px';
44019							image.css( 'left', imagePosLeft - 100 * direction + 'px' );
44020						}
44021
44022						image.animate( params, options.animationSpeed, function()
44023						{
44024							inProgress = false;
44025							if( options.onLoadEnd !== false ) options.onLoadEnd();
44026						});
44027						if( options.preloadNext )
44028						{
44029							var nextTarget = targets.eq( targets.index( target ) + 1 );
44030							if( !nextTarget.length ) nextTarget = targets.eq( 0 );
44031							$( '<img />' ).attr( 'src', nextTarget.attr( 'href' ) ).load();
44032						}
44033					})
44034					.error( function()
44035					{
44036						if( options.onLoadEnd !== false ) options.onLoadEnd();
44037					})
44038
44039					var swipeStart	 = 0,
44040						swipeEnd	 = 0,
44041						imagePosLeft = 0;
44042
44043					image.on( hasPointers ? 'pointerup MSPointerUp' : 'click', function( e )
44044					{
44045						e.preventDefault();
44046						if( options.quitOnImgClick )
44047						{
44048							quitLightbox();
44049							return false;
44050						}
44051						if( wasTouched( e.originalEvent ) ) return true;
44052					    var posX = ( e.pageX || e.originalEvent.pageX ) - e.target.offsetLeft;
44053						target = targets.eq( targets.index( target ) - ( imageWidth / 2 > posX ? 1 : -1 ) );
44054						if( !target.length ) target = targets.eq( imageWidth / 2 > posX ? targets.length : 0 );
44055						loadImage( imageWidth / 2 > posX ? 'left' : 'right' );
44056					})
44057					.on( 'touchstart pointerdown MSPointerDown', function( e )
44058					{
44059						if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) return true;
44060						if( isCssTransitionSupport ) imagePosLeft = parseInt( image.css( 'left' ) );
44061						swipeStart = e.originalEvent.pageX || e.originalEvent.touches[ 0 ].pageX;
44062					})
44063					.on( 'touchmove pointermove MSPointerMove', function( e )
44064					{
44065						if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) return true;
44066						e.preventDefault();
44067						swipeEnd = e.originalEvent.pageX || e.originalEvent.touches[ 0 ].pageX;
44068						swipeDiff = swipeStart - swipeEnd;
44069						if( isCssTransitionSupport ) cssTransitionTranslateX( image, -swipeDiff + 'px', 0 );
44070						else image.css( 'left', imagePosLeft - swipeDiff + 'px' );
44071					})
44072					.on( 'touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel', function( e )
44073					{
44074						if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) return true;
44075						if( Math.abs( swipeDiff ) > 50 )
44076						{
44077							target = targets.eq( targets.index( target ) - ( swipeDiff < 0 ? 1 : -1 ) );
44078							if( !target.length ) target = targets.eq( swipeDiff < 0 ? targets.length : 0 );
44079							loadImage( swipeDiff > 0 ? 'right' : 'left' );	
44080						}
44081						else
44082						{
44083							if( isCssTransitionSupport ) cssTransitionTranslateX( image, 0 + 'px', options.animationSpeed / 1000 );
44084							else image.animate({ 'left': imagePosLeft + 'px' }, options.animationSpeed / 2 );
44085						}
44086					});
44087
44088				}, options.animationSpeed + 100 );
44089			},
44090
44091			removeImage = function()
44092			{
44093				if( !image.length ) return false;
44094				image.remove();
44095				image = $();
44096			},
44097
44098			quitLightbox = function()
44099			{
44100				if( !image.length ) return false;
44101				image.animate({ 'opacity': 0 }, options.animationSpeed, function()
44102				{
44103					removeImage();
44104					inProgress = false;
44105					if( options.onEnd !== false ) options.onEnd();
44106				});
44107			};
44108
44109		$( window ).on( 'resize', setImage );
44110
44111		if( options.quitOnDocClick )
44112		{
44113			$( document ).on( hasTouch ? 'touchend' : 'click', function( e )
44114			{
44115				if( image.length && !$( e.target ).is( image ) ) quitLightbox();
44116			})
44117		}
44118
44119		if( options.enableKeyboard )
44120		{
44121			$( document ).on( 'keyup', function( e )
44122			{
44123				if( !image.length ) return true;
44124				e.preventDefault();
44125				if( e.keyCode == 27 ) quitLightbox();
44126				if( !inProgress && ( e.keyCode == 37 || e.keyCode == 39 ) )
44127				{
44128					target = targets.eq( targets.index( target ) - ( e.keyCode == 37 ? 1 : -1 ) );
44129					if( !target.length ) target = targets.eq( e.keyCode == 37 ? targets.length : 0 );
44130					loadImage( e.keyCode == 37 ? 'left' : 'right' );
44131				}
44132			});
44133		}
44134
44135		$( document ).on( 'click', this.selector, function( e )
44136		{
44137			if( !isTargetValid( this ) ) return true;
44138			e.preventDefault();
44139			if( inProgress ) return false;
44140			inProgress = false;
44141			if( options.onStart !== false ) options.onStart();
44142			target = $( this );
44143			loadImage();
44144		});
44145
44146		this.each( function()
44147		{
44148			if( !isTargetValid( this ) ) return true;
44149			targets = targets.add( $( this ) );
44150		});
44151
44152		this.switchImageLightbox = function( index )
44153		{
44154			var tmpTarget = targets.eq( index );
44155			if( tmpTarget.length )
44156			{
44157				var currentIndex = targets.index( target );
44158				target = tmpTarget;
44159				loadImage( index < currentIndex ? 'left' : 'right' );
44160			}
44161			return this;
44162		};
44163
44164		this.quitImageLightbox = function()
44165		{
44166			quitLightbox();
44167			return this;
44168		};
44169
44170		return this;
44171	};
44172})( jQuery, window, document );
44173(function() {
44174  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
44175
44176  App.Pages.CommunityRatingsNew = (function() {
44177    function CommunityRatingsNew() {
44178      this.displayInfo = bind(this.displayInfo, this);
44179      this.onTabClickSetCookie = bind(this.onTabClickSetCookie, this);
44180      this.onSkipLinkClick = bind(this.onSkipLinkClick, this);
44181      this.onTypeSelectorClick = bind(this.onTypeSelectorClick, this);
44182      this.onRadioClick = bind(this.onRadioClick, this);
44183      this.onRadioCheck = bind(this.onRadioCheck, this);
44184    }
44185
44186    CommunityRatingsNew.prototype.initialize = function() {
44187      this.handlers = this.eventHandlers();
44188      this.ratingTab = Cookies.get('rating_tab') ? Cookies.get('rating_tab') : '#tab-points';
44189      this.$radios = $('.js-rating-values input');
44190      this.$previewImages = $('.js-preview-images');
44191      this.$skipLink = $('.js-skip');
44192      this.$typeSelectors = $('.js-type-select :radio');
44193      this.$ratingForm = $('.js-rating-values').closest('form');
44194      this.$usersTabs = $('#users-tabs ul li');
44195      this.$ratingButton = $('.js-cast-rating');
44196      this.bindEvents();
44197      this.initPreview();
44198      this.initTabs();
44199      return this.displayInfo();
44200    };
44201
44202    CommunityRatingsNew.prototype.bindEvents = function() {
44203      this.$radios.on('change', this.onRadioCheck);
44204      this.$radios.on('click', this.onRadioClick);
44205      this.$typeSelectors.on('change', this.onTypeSelectorClick);
44206      this.$skipLink.on('click', this.onSkipLinkClick);
44207      this.$usersTabs.on('click', this.onTabClickSetCookie);
44208      return this.$ratingButton.on('click', this.showUpdatingSpinner);
44209    };
44210
44211    CommunityRatingsNew.prototype.initPreview = function() {
44212      App.events.subscribe('app:preview:change', this.handlers.previewItemChange);
44213      $('.product-thumbs li a').imageLightbox({
44214        onStart: function() {
44215          return $('body').append('<div class="product-slideshow-overlay is-hidden"></div>').find('.product-slideshow-overlay').hide().removeClass('is-hidden').fadeIn({
44216            duration: 600,
44217            easing: 'easeInOutCirc'
44218          });
44219        },
44220        onEnd: function() {
44221          return $('.product-slideshow-overlay').fadeOut({
44222            duration: 800,
44223            easing: 'easeInOutCirc'
44224          }).promise().then(function() {
44225            return $(this).remove();
44226          });
44227        }
44228      });
44229      if (!$('.product-thumbs').exists()) {
44230        return;
44231      }
44232      this.sly = new Sly('.product-thumbs', {
44233        horizontal: true,
44234        itemNav: 'basic',
44235        smart: true,
44236        itemSelector: '.product-thumbs li',
44237        startAt: 0,
44238        mouseDragging: true,
44239        releaseSwing: true,
44240        touchDragging: false,
44241        swingSpeed: 0.1,
44242        scrollBar: $('.js-thumb-scrollbar'),
44243        dragHandle: true,
44244        clickBar: true,
44245        dynamicHandle: true,
44246        next: $('.js-preview-next'),
44247        prev: $('.js-preview-prev'),
44248        draggedClass: 'is-dragged',
44249        activeClass: 'is-active',
44250        disabledClass: 'is-disabled'
44251      });
44252      this.sly.on('load', (function(_this) {
44253        return function() {
44254          if (_this.sly.pos.start === _this.sly.pos.end) {
44255            $('.js-thumb-scrollbar').hide();
44256            return $('.product-thumb-navigation__item').addClass('navigation-without-scrollbar');
44257          } else {
44258            $('.js-thumb-scrollbar').show();
44259            return $('.product-thumb-navigation__item').removeClass('navigation-without-scrollbar');
44260          }
44261        };
44262      })(this));
44263      this.sly.init();
44264      if (this.$previewImages.children().length <= 1) {
44265        $('.product-preview__navigation').hide();
44266      }
44267      $('.product-thumbs li').on('click', this.handlers.thumbClick);
44268      $('.product-thumbs li').hoverIntent(this.handlers.thumbHover, this.handlers.thumbHoverOut);
44269      return $('.js-preview-next, .js-preview-prev').on('click', this.handlers.previewNavClick);
44270    };
44271
44272    CommunityRatingsNew.prototype.initTabs = function() {
44273      return $('#users-tabs').easytabs({
44274        animate: true,
44275        panelActiveClass: 'is-active',
44276        tabActiveClass: 'is-active',
44277        updateDash: false,
44278        defaultTab: $("#users-tabs > ul > li > a[href='" + this.ratingTab + "']").first().parent()
44279      });
44280    };
44281
44282    CommunityRatingsNew.prototype.setRating = function($input) {
44283      $('.js-value-label').removeClass('is-active');
44284      if ($input.prop('checked')) {
44285        $('.js-rate-value').val($input.val());
44286        $input.parents('.rating-action__value').find('.js-value-label').addClass('is-active');
44287      }
44288      return this.$ratingButton.prop('disabled', false);
44289    };
44290
44291    CommunityRatingsNew.prototype.onRadioCheck = function(e) {
44292      var $el;
44293      $el = $(e.currentTarget);
44294      return this.setRating($el);
44295    };
44296
44297    CommunityRatingsNew.prototype.onRadioClick = function(e) {
44298      var $el;
44299      $el = $(e.currentTarget);
44300      $el.attr('checked', 'checked');
44301      return this.setRating($el);
44302    };
44303
44304    CommunityRatingsNew.prototype.onTypeSelectorClick = function(e) {
44305      var $el;
44306      if (document.querySelector('input[name="product_type"]:checked')) {
44307        $el = $(e.currentTarget);
44308        $el.attr('checked', 'checked');
44309        $el.closest('form').submit();
44310        return this.showUpdatingSpinner();
44311      }
44312    };
44313
44314    CommunityRatingsNew.prototype.onSkipLinkClick = function(e) {
44315      e.preventDefault();
44316      $('.js-skipped-value').val(1);
44317      return this.$ratingForm.submit();
44318    };
44319
44320    CommunityRatingsNew.prototype.onTabClickSetCookie = function(e) {
44321      return Cookies.set('rating_tab', e.target.hash);
44322    };
44323
44324    CommunityRatingsNew.prototype.showUpdatingSpinner = function() {
44325      return $('.js-rating-content').addClass('is-updating-with-spinner');
44326    };
44327
44328    CommunityRatingsNew.prototype.eventHandlers = function() {
44329      return {
44330        thumbHover: function(e) {
44331          if ($(this).hasClass('is-active')) {
44332            return;
44333          }
44334          return $('.product-thumbs li').not(this).not('.is-active').addClass('is-disabled');
44335        },
44336        thumbHoverOut: function(e) {
44337          return $('.product-thumbs li').removeClass('is-disabled');
44338        },
44339        thumbClick: (function(_this) {
44340          return function(e) {
44341            var $el, $target;
44342            e.preventDefault();
44343            e.stopPropagation();
44344            $el = $(e.currentTarget);
44345            $target = _this.$previewImages.find("li:eq(" + ($el.index()) + ")");
44346            return App.events.publish('app:preview:change', $target, 'auto');
44347          };
44348        })(this),
44349        previewNavClick: (function(_this) {
44350          return function(e) {
44351            var $active, $el, $target, direction;
44352            $el = $(e.currentTarget);
44353            $active = _this.$previewImages.find('.is-active');
44354            if ($(e.currentTarget).hasClass('js-preview-next')) {
44355              $target = $active.next();
44356              direction = 'next';
44357              if ($el.data('trigger') === 'manual') {
44358                _this.sly.next();
44359              }
44360            } else {
44361              $target = $active.prev();
44362              direction = 'prev';
44363              if ($el.data('trigger') === 'manual') {
44364                _this.sly.prev();
44365              }
44366            }
44367            if ($target.length === 0) {
44368              return;
44369            }
44370            return App.events.publish('app:preview:change', $target, direction);
44371          };
44372        })(this),
44373        previewItemChange: (function(_this) {
44374          return function($target, direction) {
44375            var $active;
44376            $active = _this.$previewImages.find('.is-active');
44377            if (direction === 'auto') {
44378              direction = 'next';
44379              _this.sly.activate($target.index());
44380            }
44381            $active.removeClass('is-active');
44382            return $target.addClass('is-active');
44383          };
44384        })(this)
44385      };
44386    };
44387
44388    CommunityRatingsNew.prototype.displayInfo = function() {
44389      if ($('#modal-info')) {
44390        return $('#modal-info').makeMeModal();
44391      }
44392    };
44393
44394    return CommunityRatingsNew;
44395
44396  })();
44397
44398}).call(this);
44399/*
44400	Redactor v9.1.6
44401	Updated: Oct 16, 2013
44402
44403	http://imperavi.com/redactor/
44404
44405	Copyright (c) 2009-2013, Imperavi LLC.
44406	License: http://imperavi.com/redactor/license/
44407
44408	Usage: $('#content').redactor();
44409*/
44410
44411
44412(function($)
44413{
44414	var uuid = 0;
44415
44416	"use strict";
44417
44418	var Range = function(range)
44419	{
44420		this[0] = range.startOffset;
44421		this[1] = range.endOffset;
44422
44423		this.range = range;
44424
44425		return this;
44426	};
44427
44428	Range.prototype.equals = function()
44429	{
44430		return this[0] === this[1];
44431	};
44432
44433	// Plugin
44434	$.fn.redactor = function(options)
44435	{
44436		var val = [];
44437		var args = Array.prototype.slice.call(arguments, 1);
44438
44439		if (typeof options === 'string')
44440		{
44441			this.each(function()
44442			{
44443				var instance = $.data(this, 'redactor');
44444				if (typeof instance !== 'undefined' && $.isFunction(instance[options]))
44445				{
44446					var methodVal = instance[options].apply(instance, args);
44447					if (methodVal !== undefined && methodVal !== instance) val.push(methodVal);
44448				}
44449				else return $.error('No such method "' + options + '" for Redactor');
44450			});
44451		}
44452		else
44453		{
44454			this.each(function()
44455			{
44456				if (!$.data(this, 'redactor')) $.data(this, 'redactor', Redactor(this, options));
44457			});
44458		}
44459
44460		if (val.length === 0) return this;
44461		else if (val.length === 1) return val[0];
44462		else return val;
44463
44464	};
44465
44466	// Initialization
44467	function Redactor(el, options)
44468	{
44469		return new Redactor.prototype.init(el, options);
44470	}
44471
44472	$.Redactor = Redactor;
44473	$.Redactor.VERSION = '9.1.6';
44474	$.Redactor.opts = {
44475
44476			// settings
44477			rangy: false,
44478
44479			iframe: false,
44480			fullpage: false,
44481			css: false, // url
44482
44483			lang: 'en',
44484			direction: 'ltr', // ltr or rtl
44485
44486			placeholder: false,
44487
44488			wym: false,
44489			mobile: true,
44490			cleanup: true,
44491			tidyHtml: true,
44492			pastePlainText: false,
44493			removeEmptyTags: true,
44494			templateVars: false,
44495			xhtml: false,
44496
44497			visual: true,
44498			focus: false,
44499			tabindex: false,
44500			autoresize: true,
44501			minHeight: false,
44502			shortcuts: true,
44503
44504			autosave: false, // false or url
44505			autosaveInterval: 60, // seconds
44506
44507			plugins: false, // array
44508
44509			linkAnchor: true,
44510			linkEmail: true,
44511			linkProtocol: 'http://',
44512			linkNofollow: false,
44513			linkSize: 50,
44514
44515			imageFloatMargin: '10px',
44516			imageGetJson: false, // url (ex. /folder/images.json ) or false
44517
44518			imageUpload: false, // url
44519			imageUploadParam: 'file', // input name
44520			fileUpload: false, // url
44521			fileUploadParam: 'file', // input name
44522			clipboardUpload: true, // or false
44523			clipboardUploadUrl: false, // url
44524			dragUpload: true, // false
44525
44526			dnbImageTypes: ['image/png', 'image/jpeg', 'image/gif'], // or false
44527
44528			s3: false,
44529			uploadFields: false,
44530
44531			observeImages: true,
44532			observeLinks: true,
44533
44534			modalOverlay: true,
44535
44536			tabSpaces: false, // true or number of spaces
44537			tabFocus: true,
44538
44539			air: false,
44540			airButtons: ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent'],
44541
44542			toolbar: true,
44543			toolbarFixed: false,
44544			toolbarFixedTarget: document,
44545			toolbarFixedTopOffset: 0, // pixels
44546			toolbarFixedBox: false,
44547			toolbarExternal: false, // ID selector
44548			buttonSource: true,
44549
44550			buttonSeparator: '<li class="redactor_separator"></li>',
44551
44552			buttonsCustom: {},
44553			buttonsAdd: [],
44554			buttons: ['html', '|', 'formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image', 'video', 'file', 'table', 'link', '|', 'alignment', '|', 'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify'
44555
44556			activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist', 'alignleft', 'aligncenter', 'alignright', 'justify', 'table'],
44557			activeButtonsStates: {
44558				b: 'bold',
44559				strong: 'bold',
44560				i: 'italic',
44561				em: 'italic',
44562				del: 'deleted',
44563				strike: 'deleted',
44564				ul: 'unorderedlist',
44565				ol: 'orderedlist',
44566				u: 'underline',
44567				tr: 'table',
44568				td: 'table',
44569				table: 'table'
44570			},
44571			activeButtonsAdd: false, // object, ex.: { tag: 'buttonName' }
44572
44573			formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
44574
44575			linebreaks: false,
44576			paragraphy: true,
44577			convertDivs: true,
44578			convertLinks: true,
44579			convertImageLinks: false,
44580			convertVideoLinks: false,
44581			formattingPre: false,
44582			phpTags: false,
44583
44584			allowedTags: false,
44585			deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'],
44586
44587			boldTag: 'strong',
44588			italicTag: 'em',
44589
44590			// private
44591			indentValue: 20,
44592			buffer: [],
44593			rebuffer: [],
44594			textareamode: false,
44595			emptyHtml: '<p>&#x200b;</p>',
44596			invisibleSpace: '&#x200b;',
44597			rBlockTest: /^(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)$/i,
44598			alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'TD',
44599								'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION',
44600								'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
44601			ownLine: ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'],
44602			contOwnLine: ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'],
44603			newLevel: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'],
44604			blockLevelElements: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI',
44605								'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION',
44606								'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'],
44607			// lang
44608			langs: {
44609				en: {
44610					html: 'HTML',
44611					video: 'Insert Video',
44612					image: 'Insert Image',
44613					table: 'Table',
44614					link: 'Link',
44615					link_insert: 'Insert link',
44616					link_edit: 'Edit link',
44617					unlink: 'Unlink',
44618					formatting: 'Formatting',
44619					paragraph: 'Normal text',
44620					quote: 'Quote',
44621					code: 'Code',
44622					header1: 'Header 1',
44623					header2: 'Header 2',
44624					header3: 'Header 3',
44625					header4: 'Header 4',
44626					header5: 'Header 5',
44627					bold: 'Bold',
44628					italic: 'Italic',
44629					fontcolor: 'Font Color',
44630					backcolor: 'Back Color',
44631					unorderedlist: 'Unordered List',
44632					orderedlist: 'Ordered List',
44633					outdent: 'Outdent',
44634					indent: 'Indent',
44635					cancel: 'Cancel',
44636					insert: 'Insert',
44637					save: 'Save',
44638					_delete: 'Delete',
44639					insert_table: 'Insert Table',
44640					insert_row_above: 'Add Row Above',
44641					insert_row_below: 'Add Row Below',
44642					insert_column_left: 'Add Column Left',
44643					insert_column_right: 'Add Column Right',
44644					delete_column: 'Delete Column',
44645					delete_row: 'Delete Row',
44646					delete_table: 'Delete Table',
44647					rows: 'Rows',
44648					columns: 'Columns',
44649					add_head: 'Add Head',
44650					delete_head: 'Delete Head',
44651					title: 'Title',
44652					image_position: 'Position',
44653					none: 'None',
44654					left: 'Left',
44655					right: 'Right',
44656					image_web_link: 'Image Web Link',
44657					text: 'Text',
44658					mailto: 'Email',
44659					web: 'URL',
44660					video_html_code: 'Video Embed Code',
44661					file: 'Insert File',
44662					upload: 'Upload',
44663					download: 'Download',
44664					choose: 'Choose',
44665					or_choose: 'Or choose',
44666					drop_file_here: 'Drop file here',
44667					align_left: 'Align text to the left',
44668					align_center: 'Center text',
44669					align_right: 'Align text to the right',
44670					align_justify: 'Justify text',
44671					horizontalrule: 'Insert Horizontal Rule',
44672					deleted: 'Deleted',
44673					anchor: 'Anchor',
44674					link_new_tab: 'Open link in new tab',
44675					underline: 'Underline',
44676					alignment: 'Alignment',
44677					filename: 'Name (optional)',
44678					edit: 'Edit'
44679				}
44680			}
44681	};
44682
44683	// Functionality
44684	Redactor.fn = $.Redactor.prototype = {
44685
44686		keyCode: {
44687			BACKSPACE: 8,
44688			DELETE: 46,
44689			DOWN: 40,
44690			ENTER: 13,
44691			ESC: 27,
44692			TAB: 9,
44693			CTRL: 17,
44694			META: 91,
44695			LEFT: 37,
44696			LEFT_WIN: 91
44697		},
44698
44699		// Initialization
44700		init: function(el, options)
44701		{
44702
44703			this.rtePaste = false;
44704			this.$element = this.$source = $(el);
44705			this.uuid = uuid++;
44706
44707			// clonning options
44708			var opts = $.extend(true, {}, $.Redactor.opts);
44709
44710			// current settings
44711			this.opts = $.extend(
44712				{},
44713				opts,
44714				this.$element.data(),
44715				options
44716			);
44717
44718			this.start = true;
44719			this.dropdowns = [];
44720
44721			// get sizes
44722			this.sourceHeight = this.$source.css('height');
44723			this.sourceWidth = this.$source.css('width');
44724
44725			// dependency of the editor modes
44726			if (this.opts.fullpage) this.opts.iframe = true;
44727			if (this.opts.linebreaks) this.opts.paragraphy = false;
44728			if (this.opts.paragraphy) this.opts.linebreaks = false;
44729			if (this.opts.toolbarFixedBox) this.opts.toolbarFixed = true;
44730
44731			// the alias for iframe mode
44732			this.document = document;
44733			this.window = window;
44734
44735			// selection saved
44736			this.savedSel = false;
44737
44738			// clean setup
44739			this.cleanlineBefore = new RegExp('^<(/?' + this.opts.ownLine.join('|/?' ) + '|' + this.opts.contOwnLine.join('|') + ')[ >]');
44740			this.cleanlineAfter = new RegExp('^<(br|/?' + this.opts.ownLine.join('|/?' ) + '|/' + this.opts.contOwnLine.join('|/') + ')[ >]');
44741			this.cleannewLevel = new RegExp('^</?(' + this.opts.newLevel.join('|' ) + ')[ >]');
44742
44743			// block level
44744			this.rTestBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i');
44745
44746			// setup formatting permissions
44747			if (this.opts.linebreaks === false)
44748			{
44749				if (this.opts.allowedTags !== false && $.inArray('p', this.opts.allowedTags) === '-1') this.opts.allowedTags.push('p');
44750
44751				if (this.opts.deniedTags !== false)
44752				{
44753					var pos = $.inArray('p', this.opts.deniedTags);
44754					if (pos !== '-1') this.opts.deniedTags.splice(pos, pos);
44755				}
44756			}
44757
44758			// ie & opera
44759			if (this.browser('msie') || this.browser('opera'))
44760			{
44761				this.opts.buttons = this.removeFromArrayByValue(this.opts.buttons, 'horizontalrule');
44762			}
44763
44764			// load lang
44765			this.opts.curLang = this.opts.langs[this.opts.lang];
44766
44767			// Build
44768			this.buildStart();
44769
44770		},
44771		toolbarInit: function(lang)
44772		{
44773			return {
44774				html:
44775				{
44776					title: lang.html,
44777					func: 'toggle'
44778				},
44779				formatting:
44780				{
44781					title: lang.formatting,
44782					func: 'show',
44783					dropdown:
44784					{
44785						p:
44786						{
44787							title: lang.paragraph,
44788							func: 'formatBlocks'
44789						},
44790						blockquote:
44791						{
44792							title: lang.quote,
44793							func: 'formatQuote',
44794							className: 'redactor_format_blockquote'
44795						},
44796						pre:
44797						{
44798							title: lang.code,
44799							func: 'formatBlocks',
44800							className: 'redactor_format_pre'
44801						},
44802						h1:
44803						{
44804							title: lang.header1,
44805							func: 'formatBlocks',
44806							className: 'redactor_format_h1'
44807						},
44808						h2:
44809						{
44810							title: lang.header2,
44811							func: 'formatBlocks',
44812							className: 'redactor_format_h2'
44813						},
44814						h3:
44815						{
44816							title: lang.header3,
44817							func: 'formatBlocks',
44818							className: 'redactor_format_h3'
44819						},
44820						h4:
44821						{
44822							title: lang.header4,
44823							func: 'formatBlocks',
44824							className: 'redactor_format_h4'
44825						},
44826						h5:
44827						{
44828							title: lang.header5,
44829							func: 'formatBlocks',
44830							className: 'redactor_format_h5'
44831						}
44832					}
44833				},
44834				bold:
44835				{
44836					title: lang.bold,
44837					exec: 'bold'
44838				},
44839				italic:
44840				{
44841					title: lang.italic,
44842					exec: 'italic'
44843				},
44844				deleted:
44845				{
44846					title: lang.deleted,
44847					exec: 'strikethrough'
44848				},
44849				underline:
44850				{
44851					title: lang.underline,
44852					exec: 'underline'
44853				},
44854				unorderedlist:
44855				{
44856					title: '&bull; ' + lang.unorderedlist,
44857					exec: 'insertunorderedlist'
44858				},
44859				orderedlist:
44860				{
44861					title: '1. ' + lang.orderedlist,
44862					exec: 'insertorderedlist'
44863				},
44864				outdent:
44865				{
44866					title: '< ' + lang.outdent,
44867					func: 'indentingOutdent'
44868				},
44869				indent:
44870				{
44871					title: '> ' + lang.indent,
44872					func: 'indentingIndent'
44873				},
44874				image:
44875				{
44876					title: lang.image,
44877					func: 'imageShow'
44878				},
44879				video:
44880				{
44881					title: lang.video,
44882					func: 'videoShow'
44883				},
44884				file:
44885				{
44886					title: lang.file,
44887					func: 'fileShow'
44888				},
44889				table:
44890				{
44891					title: lang.table,
44892					func: 'show',
44893					dropdown:
44894					{
44895						insert_table:
44896						{
44897							title: lang.insert_table,
44898							func: 'tableShow'
44899						},
44900						separator_drop1:
44901						{
44902							name: 'separator'
44903						},
44904						insert_row_above:
44905						{
44906							title: lang.insert_row_above,
44907							func: 'tableAddRowAbove'
44908						},
44909						insert_row_below:
44910						{
44911							title: lang.insert_row_below,
44912							func: 'tableAddRowBelow'
44913						},
44914						insert_column_left:
44915						{
44916							title: lang.insert_column_left,
44917							func: 'tableAddColumnLeft'
44918						},
44919						insert_column_right:
44920						{
44921							title: lang.insert_column_right,
44922							func: 'tableAddColumnRight'
44923						},
44924						separator_drop2:
44925						{
44926							name: 'separator'
44927						},
44928						add_head:
44929						{
44930							title: lang.add_head,
44931							func: 'tableAddHead'
44932						},
44933						delete_head:
44934						{
44935							title: lang.delete_head,
44936							func: 'tableDeleteHead'
44937						},
44938						separator_drop3:
44939						{
44940							name: 'separator'
44941						},
44942						delete_column:
44943						{
44944							title: lang.delete_column,
44945							func: 'tableDeleteColumn'
44946						},
44947						delete_row:
44948						{
44949							title: lang.delete_row,
44950							func: 'tableDeleteRow'
44951						},
44952						delete_table:
44953						{
44954							title: lang.delete_table,
44955							func: 'tableDeleteTable'
44956						}
44957					}
44958				},
44959				link: {
44960					title: lang.link,
44961					func: 'show',
44962					dropdown:
44963					{
44964						link:
44965						{
44966							title: lang.link_insert,
44967							func: 'linkShow'
44968						},
44969						unlink:
44970						{
44971							title: lang.unlink,
44972							exec: 'unlink'
44973						}
44974					}
44975				},
44976				fontcolor:
44977				{
44978					title: lang.fontcolor,
44979					func: 'show'
44980				},
44981				backcolor:
44982				{
44983					title: lang.backcolor,
44984					func: 'show'
44985				},
44986				alignment:
44987				{
44988					title: lang.alignment,
44989					func: 'show',
44990					dropdown:
44991					{
44992						alignleft:
44993						{
44994							title: lang.align_left,
44995							func: 'alignmentLeft'
44996						},
44997						aligncenter:
44998						{
44999							title: lang.align_center,
45000							func: 'alignmentCenter'
45001						},
45002						alignright:
45003						{
45004							title: lang.align_right,
45005							func: 'alignmentRight'
45006						},
45007						justify:
45008						{
45009							title: lang.align_justify,
45010							func: 'alignmentJustify'
45011						}
45012					}
45013				},
45014				alignleft:
45015				{
45016					title: lang.align_left,
45017					func: 'alignmentLeft'
45018				},
45019				aligncenter:
45020				{
45021					title: lang.align_center,
45022					func: 'alignmentCenter'
45023				},
45024				alignright:
45025				{
45026					title: lang.align_right,
45027					func: 'alignmentRight'
45028				},
45029				justify:
45030				{
45031					title: lang.align_justify,
45032					func: 'alignmentJustify'
45033				},
45034				horizontalrule:
45035				{
45036					exec: 'inserthorizontalrule',
45037					title: lang.horizontalrule
45038				}
45039
45040			}
45041		},
45042
45043		// CALLBACKS
45044		callback: function(type, event, data)
45045		{
45046			var callback = this.opts[ type + 'Callback' ];
45047			if ($.isFunction(callback))
45048			{
45049				if (event === false) return callback.call(this, data);
45050				else return callback.call(this, event, data);
45051			}
45052			else return data;
45053		},
45054
45055
45056		// DESTROY
45057		destroy: function()
45058		{
45059			clearInterval(this.autosaveInterval);
45060
45061			$(window).off('.redactor');
45062			this.$source.off('redactor-textarea');
45063			this.$element.off('.redactor').removeData('redactor');
45064
45065			var html = this.get();
45066
45067			if (this.opts.textareamode)
45068			{
45069				this.$box.after(this.$source);
45070				this.$box.remove();
45071				this.$source.val(html).show();
45072			}
45073			else
45074			{
45075				var $elem = this.$editor;
45076				if (this.opts.iframe) $elem = this.$element;
45077
45078				this.$box.after($elem);
45079				this.$box.remove();
45080
45081				$elem.removeClass('redactor_editor').removeClass('redactor_editor_wym').removeAttr('contenteditable').html(html).show();
45082			}
45083
45084			if (this.opts.air)
45085			{
45086				$('#redactor_air_' + this.uuid).remove();
45087			}
45088		},
45089
45090		// API GET
45091		getObject: function()
45092		{
45093			return $.extend({}, this);
45094		},
45095		getEditor: function()
45096		{
45097			return this.$editor;
45098		},
45099		getBox: function()
45100		{
45101			return this.$box;
45102		},
45103		getIframe: function()
45104		{
45105			return (this.opts.iframe) ? this.$frame : false;
45106		},
45107		getToolbar: function()
45108		{
45109			return this.$toolbar;
45110		},
45111
45112
45113		// CODE GET & SET
45114		get: function()
45115		{
45116			return this.$source.val();
45117		},
45118		getCodeIframe: function()
45119		{
45120			this.$editor.removeAttr('contenteditable').removeAttr('dir');
45121			var html = this.outerHtml(this.$frame.contents().children());
45122			this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });
45123
45124			return html;
45125		},
45126		set: function(html, strip, placeholderRemove)
45127		{
45128			html = html.toString();
45129
45130			if (this.opts.fullpage) this.setCodeIframe(html);
45131			else this.setEditor(html, strip);
45132
45133			if (placeholderRemove !== false) this.placeholderRemove();
45134		},
45135		setEditor: function(html, strip)
45136		{
45137			if (strip !== false)
45138			{
45139				html = this.cleanSavePreCode(html);
45140				html = this.cleanStripTags(html);
45141				html = this.cleanConvertProtected(html);
45142				html = this.cleanConvertInlineTags(html);
45143
45144				if (this.opts.linebreaks === false) html = this.cleanConverters(html);
45145				else html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
45146			}
45147
45148			html = this.cleanEmpty(html);
45149
45150			this.$editor.html(html);
45151
45152			// set no editable
45153			this.setNonEditable();
45154			this.setSpansVerified();
45155			this.sync();
45156		},
45157		setCodeIframe: function(html)
45158		{
45159			var doc = this.iframePage();
45160			this.$frame[0].src = "about:blank";
45161
45162			html = this.cleanConvertProtected(html);
45163			html = this.cleanConvertInlineTags(html);
45164			html = this.cleanRemoveSpaces(html);
45165
45166			doc.open();
45167			doc.write(html);
45168			doc.close();
45169
45170			// redefine editor for fullpage mode
45171			if (this.opts.fullpage)
45172			{
45173				this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
45174			}
45175
45176			// set no editable
45177			this.setNonEditable();
45178			this.setSpansVerified();
45179			this.sync();
45180
45181		},
45182		setFullpageOnInit: function(html)
45183		{
45184			html = this.cleanSavePreCode(html, true);
45185			html = this.cleanConverters(html);
45186			html = this.cleanEmpty(html);
45187
45188			// set code
45189			this.$editor.html(html);
45190
45191			// set no editable
45192			this.setNonEditable();
45193			this.setSpansVerified();
45194			this.sync();
45195		},
45196		setSpansVerified: function()
45197		{
45198			var spans = this.$editor.find('span');
45199			var replacementTag = 'inline';
45200
45201			$.each(spans, function() {
45202				var outer = this.outerHTML;
45203
45204				// Replace opening tag
45205				var regex = new RegExp('<' + this.tagName, 'i');
45206				var newTag = outer.replace(regex, '<' + replacementTag);
45207
45208				// Replace closing tag
45209				regex = new RegExp('</' + this.tagName, 'i');
45210				newTag = newTag.replace(regex, '</' + replacementTag);
45211
45212				$(this).replaceWith(newTag);
45213			});
45214
45215		},
45216		setSpansVerifiedHtml: function(html)
45217		{
45218			html = html.replace(/<span(.*?)>/, '<inline$1>');
45219			return html.replace(/<\/span>/, '</inline>');
45220		},
45221		setNonEditable: function()
45222		{
45223			this.$editor.find('.noneditable').attr('contenteditable', false);
45224		},
45225
45226		// SYNC
45227		sync: function()
45228		{
45229			var html = '';
45230
45231			this.cleanUnverified();
45232
45233			if (this.opts.fullpage) html = this.getCodeIframe();
45234			else html = this.$editor.html();
45235
45236			html = this.syncClean(html);
45237			//html = this.cleanRemoveSpaces(html);
45238			html = this.cleanRemoveEmptyTags(html);
45239
45240			// fix second level up ul, ol
45241			html = html.replace(/<\/li><(ul|ol)>([\w\W]*?)<\/(ul|ol)>/gi, '<$1>$2</$1></li>');
45242
45243			if ($.trim(html) === '<br>') html = '';
45244
45245			// xhtml
45246			if (this.opts.xhtml)
45247			{
45248				var xhtmlTags = ['br', 'hr', 'img', 'link', 'input', 'meta'];
45249				$.each(xhtmlTags, function(i,s)
45250				{
45251					html = html.replace(new RegExp('<' + s + '(.*?[^\/$]?)>', 'gi'), '<' + s + '$1 />');
45252				});
45253
45254			}
45255
45256			// before callback
45257			html = this.callback('syncBefore', false, html);
45258
45259			this.$source.val(html);
45260
45261			// onchange & after callback
45262			this.callback('syncAfter', false, html);
45263
45264			if (this.start === false)
45265			{
45266				this.callback('change', false, html);
45267			}
45268
45269		},
45270		syncClean: function(html)
45271		{
45272			if (!this.opts.fullpage) html = this.cleanStripTags(html);
45273
45274			html = $.trim(html);
45275
45276			// removeplaceholder
45277			html = this.placeholderRemoveFromCode(html);
45278
45279			// remove space
45280			html = html.replace(/&#x200b;/gi, '');
45281			html = html.replace(/&#8203;/gi, '');
45282			html = html.replace(/&nbsp;/gi, ' ');
45283
45284			// link nofollow
45285			if (this.opts.linkNofollow)
45286			{
45287				html = html.replace(/<a(.*?)rel="nofollow"(.*?)>
45287/gi, '<a$1$2>');
45288				html = html.replace(/<a(.*?)>/gi, '<a$1 rel="nofollow">');
45289			}
45290
45291			// php code fix
45292			html = html.replace('<!--?php', '<?php');
45293			html = html.replace('?-->', '?>');
45294
45295			// revert no editable
45296			html = html.replace(/<(.*?)class="noeditable"(.*?) contenteditable="false"(.*?)>/gi, '<$1class="noeditable"$2$3>');
45297
45298			html = html.replace(/ data-tagblock=""/gi, '');
45299			html = html.replace(/<br\s?\/?>\n?<\/(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)>/gi, '</$1>');
45300
45301			// remove image resize
45302			html = html.replace(/<span(.*?)id="redactor-image-box"(.*?)>([\w\W]*?)<img(.*?)><\/span>/i, '$3<img$4>');
45303			html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?)>(.*?)<\/span>/i, '');
45304			html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?)>(.*?)<\/span>/i, '');
45305
45306			// remove spans
45307			html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
45308			html = html.replace(/<inline(.*?)>([\w\W]*?)<\/inline>/gi, '<span$1>$2</span>');
45309
45310			// amp fix
45311			html = html.replace(/&amp;/gi, '&');
45312
45313			html = this.cleanReConvertProtected(html);
45314
45315			return html;
45316		},
45317
45318
45319		// BUILD
45320		buildStart: function()
45321		{
45322			// content
45323			this.content = '';
45324
45325			// container
45326			this.$box = $('<div class="redactor_box" />');
45327
45328			// textarea test
45329			if (this.$source[0].tagName === 'TEXTAREA') this.opts.textareamode = true;
45330
45331			// mobile
45332			if (this.opts.mobile === false && this.isMobile())
45333			{
45334				this.buildMobile();
45335			}
45336			else
45337			{
45338				// get the content at the start
45339				this.buildContent();
45340
45341				if (this.opts.iframe)
45342				{
45343					// build as iframe
45344					this.opts.autoresize = false;
45345					this.iframeStart();
45346				}
45347				else if (this.opts.textareamode) this.buildFromTextarea();
45348				else this.buildFromElement();
45349
45350				// options and final setup
45351				if (!this.opts.iframe)
45352				{
45353					this.buildOptions();
45354					this.buildAfter();
45355				}
45356			}
45357		},
45358		buildMobile: function()
45359		{
45360			if (!this.opts.textareamode)
45361			{
45362				this.$editor = this.$source;
45363				this.$editor.hide();
45364				this.$source = this.buildCodearea(this.$editor);
45365				this.$source.val(this.content);
45366			}
45367
45368			this.$box.insertAfter(this.$source).append(this.$source);
45369		},
45370		buildContent: function()
45371		{
45372			if (this.opts.textareamode) this.content = $.trim(this.$source.val());
45373			else this.content = $.trim(this.$source.html());
45374		},
45375		buildFromTextarea: function()
45376		{
45377			this.$editor = $('<div />');
45378			this.$box.insertAfter(this.$source).append(this.$editor).append(this.$source);
45379
45380			// enable
45381			this.buildAddClasses(this.$editor);
45382			this.buildEnable();
45383		},
45384		buildFromElement: function()
45385		{
45386			this.$editor = this.$source;
45387			this.$source = this.buildCodearea(this.$editor);
45388			this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$source);
45389
45390			// enable
45391			this.buildEnable();
45392		},
45393		buildCodearea: function($source)
45394		{
45395			return $('<textarea />').attr('name', $source.attr('id')).css('height', this.sourceHeight);
45396		},
45397		buildAddClasses: function(el)
45398		{
45399			// append textarea classes to editable layer
45400			$.each(this.$source.get(0).className.split(/\s+/), function(i,s)
45401			{
45402				el.addClass('redactor_' + s);
45403			});
45404		},
45405		buildEnable: function()
45406		{
45407			this.$editor.addClass('redactor_editor').attr({ 'contenteditable': true, 'dir': this.opts.direction });
45408			this.$source.attr('dir', this.opts.direction).hide();
45409
45410			// set code
45411			this.set(this.content, true, false);
45412		},
45413		buildOptions: function()
45414		{
45415			var $source = this.$editor;
45416			if (this.opts.iframe) $source = this.$frame;
45417
45418			// options
45419			if (this.opts.tabindex) $source.attr('tabindex', this.opts.tabindex);
45420			if (this.opts.minHeight) $source.css('min-height', this.opts.minHeight + 'px');
45421			if (this.opts.wym) this.$editor.addClass('redactor_editor_wym');
45422			if (!this.opts.autoresize) $source.css('height', this.sourceHeight);
45423		},
45424		buildAfter: function()
45425		{
45426			this.start = false;
45427
45428			// load toolbar
45429			if (this.opts.toolbar)
45430			{
45431				this.opts.toolbar = this.toolbarInit(this.opts.curLang);
45432				this.toolbarBuild();
45433			}
45434
45435			// modal templates
45436			this.modalTemplatesInit();
45437
45438			// plugins
45439			this.buildPlugins();
45440
45441			// enter, tab, etc.
45442			this.buildBindKeyboard();
45443
45444			// autosave
45445			if (this.opts.autosave) this.autosave();
45446
45447			// observers
45448			setTimeout($.proxy(this.observeStart, this), 4);
45449
45450			// FF fix
45451			if (this.browser('mozilla'))
45452			{
45453				try {
45454					this.document.execCommand('enableObjectResizing', false, false);
45455					this.document.execCommand('enableInlineTableEditing', false, false);
45456				} catch (e) {}
45457			}
45458
45459			// focus
45460			if (this.opts.focus) setTimeout($.proxy(this.focus, this), 100);
45461
45462			// code mode
45463			if (!this.opts.visual)
45464			{
45465				setTimeout($.proxy(function()
45466				{
45467					this.opts.visual = true;
45468					this.toggle(false);
45469
45470				}, this), 200);
45471			}
45472
45473			// init callback
45474			this.callback('init');
45475		},
45476		buildBindKeyboard: function()
45477		{
45478			if (this.opts.dragUpload)
45479			{
45480				this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this));
45481			}
45482
45483			this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this));
45484			this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this));
45485			this.$editor.on('keyup.redactor', $.proxy(this.buildEventKeyup, this));
45486
45487
45488
45489			// textarea callback
45490			if ($.isFunction(this.opts.textareaKeydownCallback))
45491			{
45492				this.$source.on('keydown.redactor-textarea', $.proxy(this.opts.textareaKeydownCallback, this));
45493			}
45494
45495			// focus callback
45496			if ($.isFunction(this.opts.focusCallback))
45497			{
45498				this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
45499			}
45500
45501			var clickedElement;
45502			$(document).mousedown(function(e) {
45503				clickedElement = $(e.target);
45504			});
45505
45506			// blur callback
45507			this.$editor.on('blur.redactor', $.proxy(function(e)
45508			{
45509				if (!$(clickedElement).hasClass('redactor_toolbar') && $(clickedElement).parents('.redactor_toolbar').size() == 0)
45510				{
45511					this.selectall = false;
45512					if ($.isFunction(this.opts.blurCallback)) this.callback('blur', e);
45513				}
45514			}, this));
45515
45516		},
45517		buildEventDrop: function(e)
45518		{
45519			e = e.originalEvent || e;
45520
45521			if (window.FormData === undefined || !e.dataTransfer) return true;
45522
45523		    var length = e.dataTransfer.files.length;
45524		    if (length == 0) return true;
45525
45526		    e.preventDefault();
45527
45528	        var file = e.dataTransfer.files[0];
45529
45530	        if (this.opts.dnbImageTypes !== false && this.opts.dnbImageTypes.indexOf(file.type) == -1)
45531	        {
45532		        return true;
45533	        }
45534
45535			this.bufferSet();
45536
45537			var progress = $('<div id="redactor-progress-drag" class="redactor-progress redactor-progress-striped"><div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div></div>');
45538			$(document.body).append(progress);
45539
45540			this.dragUploadAjax(this.opts.imageUpload, file, true, progress, e, this.opts.imageUploadParam);
45541
45542		},
45543		buildEventPaste: function(e)
45544		{
45545			var oldsafari = false;
45546			if (this.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1)
45547			{
45548				var arr = this.browser('version').split('.');
45549				if (arr[0] < 536) oldsafari = true;
45550			}
45551
45552			if (oldsafari) return true;
45553
45554			// paste except opera (not webkit)
45555			if (this.browser('opera')) return true;
45556
45557			// clipboard upload
45558			if (this.opts.clipboardUpload && this.buildEventClipboardUpload(e)) return true;
45559
45560			if (this.opts.cleanup)
45561			{
45562				this.rtePaste = true;
45563
45564				this.selectionSave();
45565
45566				if (!this.selectall)
45567				{
45568					if (this.opts.autoresize === true )
45569					{
45570						this.$editor.height(this.$editor.height());
45571						this.saveScroll = this.document.body.scrollTop;
45572					}
45573					else
45574					{
45575						this.saveScroll = this.$editor.scrollTop();
45576					}
45577				}
45578
45579				var frag = this.extractContent();
45580
45581				setTimeout($.proxy(function()
45582				{
45583					var pastedFrag = this.extractContent();
45584					this.$editor.append(frag);
45585
45586					this.selectionRestore();
45587
45588					var html = this.getFragmentHtml(pastedFrag);
45589					this.pasteClean(html);
45590
45591					if (this.opts.autoresize === true) this.$editor.css('height', 'auto');
45592
45593				}, this), 1);
45594			}
45595		},
45596		buildEventClipboardUpload: function(e)
45597		{
45598			var event = e.originalEvent || e;
45599			this.clipboardFilePaste = false;
45600
45601			if (typeof(event.clipboardData) === 'undefined') return false;
45602			if (event.clipboardData.items)
45603			{
45604				var file = event.clipboardData.items[0].getAsFile();
45605				if (file !== null)
45606				{
45607					this.bufferSet();
45608					this.clipboardFilePaste = true;
45609
45610					var reader = new FileReader();
45611					reader.onload = $.proxy(this.pasteClipboardUpload, this);
45612			        reader.readAsDataURL(file);
45613
45614			        return true;
45615				}
45616			}
45617
45618			return false;
45619
45620		},
45621		buildEventKeydown: function(e)
45622		{
45623			if (this.rtePaste) return false;
45624
45625			var key = e.which;
45626			var ctrl = e.ctrlKey || e.metaKey;
45627			var parent = this.getParent();
45628			var current = this.getCurrent();
45629			var block = this.getBlock();
45630			var pre = false;
45631
45632			this.callback('keydown', e);
45633
45634			this.imageResizeHide(false);
45635
45636			// pre & down
45637			if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE'))
45638			{
45639				pre = true;
45640				if (key === this.keyCode.DOWN) this.insertAfterLastElement(block);
45641			}
45642
45643			// down
45644			if (key === this.keyCode.DOWN)
45645			{
45646				if (parent && $(parent).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(parent);
45647				if (current && $(current).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(current);
45648			}
45649
45650			// shortcuts setup
45651			if (ctrl && !e.shiftKey) this.shortcuts(e, key);
45652
45653			// buffer setup
45654			if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
45655			{
45656				e.preventDefault();
45657				if (this.opts.buffer.length) this.bufferUndo();
45658				else this.document.execCommand('undo', false, false);
45659				return;
45660			}
45661			// undo
45662			else if (ctrl && key === 90 && e.shiftKey && !e.altKey)
45663			{
45664				e.preventDefault();
45665				if (this.opts.rebuffer.length != 0) this.bufferRedo();
45666				else this.document.execCommand('redo', false, false);
45667				return;
45668			}
45669
45670			// select all
45671			if (ctrl && key === 65) this.selectall = true;
45672			else if (key != this.keyCode.LEFT_WIN && !ctrl) this.selectall = false;
45673
45674			// enter
45675			if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey )
45676			{
45677				// In ie, opera in the tables are created paragraphs, fix it.
45678				if (parent.nodeType == 1 && (parent.tagName == 'TD' || parent.tagName == 'TH'))
45679				{
45680					e.preventDefault();
45681					this.bufferSet();
45682					this.insertNode(document.createElement('br'));
45683					this.callback('enter', e);
45684					return false;
45685				}
45686
45687				// pre
45688				if (pre === true) return this.buildEventKeydownPre(e, current);
45689				else
45690				{
45691					if (!this.opts.linebreaks)
45692					{
45693						// replace div to p
45694						if (block && this.opts.rBlockTest.test(block.tagName))
45695						{
45696							// hit enter
45697							this.bufferSet();
45698
45699							setTimeout($.proxy(function()
45700							{
45701								var blockElem = this.getBlock();
45702								if (blockElem.tagName === 'DIV' && !$(blockElem).hasClass('redactor_editor'))
45703								{
45704									var node = $('<p>' + this.opts.invisibleSpace + '</p>');
45705									$(blockElem).replaceWith(node);
45706									this.selectionStart(node);
45707								}
45708
45709							}, this), 1);
45710						}
45711						else if (block === false)
45712						{
45713							// hit enter
45714							this.bufferSet();
45715
45716							var node = $('<p>' + this.opts.invisibleSpace + '</p>');
45717							this.insertNode(node[0]);
45718							this.selectionStart(node);
45719							this.callback('enter', e);
45720							return false;
45721						}
45722
45723					}
45724
45725					if (this.opts.linebreaks)
45726					{
45727						// replace div to br
45728						if (block && this.opts.rBlockTest.test(block.tagName))
45729						{
45730							// hit enter
45731							this.bufferSet();
45732
45733							setTimeout($.proxy(function()
45734							{
45735								var blockElem = this.getBlock();
45736								if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && !$(blockElem).hasClass('redactor_editor'))
45737								{
45738									this.replaceLineBreak(blockElem);
45739								}
45740
45741							}, this), 1);
45742						}
45743						else
45744						{
45745							return this.buildEventKeydownInsertLineBreak(e);
45746						}
45747					}
45748
45749					// blockquote, figcaption
45750					if (block.tagName == 'BLOCKQUOTE' || block.tagName == 'FIGCAPTION')
45751					{
45752						return this.buildEventKeydownInsertLineBreak(e);
45753					}
45754
45755				}
45756
45757				this.callback('enter', e);
45758			}
45759			else if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) // Shift+Enter or Ctrl+Enter
45760			{
45761				this.bufferSet();
45762
45763				e.preventDefault();
45764				this.insertLineBreak();
45765			}
45766
45767			// tab
45768			if (key === this.keyCode.TAB && this.opts.shortcuts) return this.buildEventKeydownTab(e, pre);
45769
45770			// delete zero-width space before the removing
45771			if (key === this.keyCode.BACKSPACE) this.buildEventKeydownBackspace(current);
45772
45773		},
45774		buildEventKeydownPre: function(e, current)
45775		{
45776			e.preventDefault();
45777			this.bufferSet();
45778			var html = $(current).parent().text();
45779			this.insertNode(document.createTextNode('\n'));
45780			if (html.search(/\s$/) == -1)
45781			{
45782				this.insertNode(document.createTextNode('\n'));
45783			}
45784
45785			this.sync();
45786			this.callback('enter', e);
45787			return false;
45788		},
45789		buildEventKeydownTab: function(e, pre)
45790		{
45791			if (!this.opts.tabFocus) return true;
45792			if (this.isEmpty(this.get())) return true;
45793
45794			e.preventDefault();
45795
45796			if (pre === true && !e.shiftKey)
45797			{
45798				this.bufferSet();
45799				this.insertNode(document.createTextNode('\t'));
45800				this.sync();
45801				return false;
45802
45803			}
45804			else if (this.opts.tabSpaces !== false)
45805			{
45806				this.bufferSet();
45807				this.insertNode(document.createTextNode(Array(this.opts.tabSpaces + 1).join('\u00a0')));
45808				this.sync();
45809				return false;
45810			}
45811			else
45812			{
45813				if (!e.shiftKey) this.indentingIndent();
45814				else this.indentingOutdent();
45815			}
45816
45817			return false;
45818		},
45819		buildEventKeydownBackspace: function(current)
45820		{
45821			if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName))
45822			{
45823				var node;
45824				if (this.opts.linebreaks === false) node = $('<p>' + this.opts.invisibleSpace + '</p>');
45825				else node = $('<br>' + this.opts.invisibleSpace);
45826
45827				$(current).replaceWith(node);
45828				this.selectionStart(node);
45829			}
45830
45831			if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null)
45832			{
45833				//var value = $.trim(current.nodeValue.replace(/[^\u0000-\u1C7F]/g, ''));
45834				if (current.remove && current.nodeType === 3 && current.nodeValue.match(/[^/\u200B]/g) == null)
45835				{
45836					current.remove();
45837				}
45838			}
45839		},
45840		buildEventKeydownInsertLineBreak: function(e)
45841		{
45842			this.bufferSet();
45843			e.preventDefault();
45844			this.insertLineBreak();
45845			this.callback('enter', e);
45846			return;
45847		},
45848		buildEventKeyup: function(e)
45849		{
45850			if (this.rtePaste) return false;
45851
45852			var key = e.which;
45853			var parent = this.getParent();
45854			var current = this.getCurrent();
45855
45856			// replace to p before / after the table or body
45857			if (!this.opts.linebreaks && current.nodeType == 3 && (parent == false || parent.tagName == 'BODY'))
45858			{
45859				var node = $('<p>').append($(current).clone());
45860				$(current).replaceWith(node);
45861				var next = $(node).next();
45862				if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
45863				{
45864					next.remove();
45865				}
45866				this.selectionEnd(node);
45867			}
45868
45869			// convert links
45870			if ((this.opts.convertLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
45871			{
45872				this.buildEventKeyupConverters();
45873			}
45874
45875			// if empty
45876			if (this.opts.linebreaks === false && (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE))
45877			{
45878				return this.formatEmpty(e);
45879			}
45880
45881			this.callback('keyup', e);
45882			this.sync();
45883		},
45884		buildEventKeyupConverters: function()
45885		{
45886			this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks, this.opts.linkSize);
45887
45888			setTimeout($.proxy(function()
45889			{
45890				if (this.opts.convertImageLinks) this.observeImages();
45891				if (this.opts.observeLinks) this.observeLinks();
45892			}, this), 5);
45893		},
45894		buildPlugins: function()
45895		{
45896			if (!this.opts.plugins ) return;
45897
45898			$.each(this.opts.plugins, $.proxy(function(i, s)
45899			{
45900				if (RedactorPlugins[s])
45901				{
45902					$.extend(this, RedactorPlugins[s]);
45903					if ($.isFunction( RedactorPlugins[ s ].init)) this.init();
45904				}
45905
45906			}, this ));
45907		},
45908
45909		// IFRAME
45910		iframeStart: function()
45911		{
45912			this.iframeCreate();
45913
45914			if (this.opts.textareamode) this.iframeAppend(this.$source);
45915			else
45916			{
45917				this.$sourceOld = this.$source.hide();
45918				this.$source = this.buildCodearea(this.$sourceOld);
45919				this.iframeAppend(this.$sourceOld);
45920			}
45921		},
45922		iframeAppend: function(el)
45923		{
45924			this.$source.attr('dir', this.opts.direction).hide();
45925			this.$box.insertAfter(el).append(this.$frame).append(this.$source);
45926		},
45927		iframeCreate: function()
45928		{
45929			this.$frame = $('<iframe style="width: 100%;" frameborder="0" />').one('load', $.proxy(function()
45930			{
45931				if (this.opts.fullpage)
45932				{
45933					this.iframePage();
45934
45935					if (this.content === '') this.content = this.opts.invisibleSpace;
45936
45937					this.$frame.contents()[0].write(this.content);
45938					this.$frame.contents()[0].close();
45939
45940					var timer = setInterval($.proxy(function()
45941					{
45942						if (this.$frame.contents().find('body').html())
45943						{
45944							clearInterval(timer);
45945							this.iframeLoad();
45946						}
45947
45948					}, this), 0);
45949				}
45950				else this.iframeLoad();
45951
45952			}, this));
45953		},
45954		iframeDoc: function()
45955		{
45956			return this.$frame[0].contentWindow.document;
45957		},
45958		iframePage: function()
45959		{
45960			var doc = this.iframeDoc();
45961			if (doc.documentElement) doc.removeChild(doc.documentElement);
45962
45963			return doc;
45964		},
45965		iframeAddCss: function(css)
45966		{
45967			css = css || this.opts.css;
45968
45969			if (this.isString(css))
45970			{
45971				this.$frame.contents().find('head').append('<link rel="stylesheet" href="' + css + '" />');
45972			}
45973
45974			if ($.isArray(css))
45975			{
45976				$.each(css, $.proxy(function(i, url)
45977				{
45978					this.iframeAddCss(url);
45979
45980				}, this));
45981			}
45982		},
45983		iframeLoad: function()
45984		{
45985			this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
45986
45987			// set document & window
45988			if (this.$editor[0])
45989			{
45990				this.document = this.$editor[0].ownerDocument;
45991				this.window = this.document.defaultView || window;
45992			}
45993
45994			// iframe css
45995			this.iframeAddCss();
45996
45997			if (this.opts.fullpage) this.setFullpageOnInit(this.$editor.html());
45998			else this.set(this.content, true, false);
45999
46000			this.buildOptions();
46001			this.buildAfter();
46002		},
46003
46004		// PLACEHOLDER
46005		placeholderStart: function(html)
46006		{
46007			if (this.isEmpty(html))
46008			{
46009				if (this.$element.attr('placeholder')) this.opts.placeholder = this.$element.attr('placeholder');
46010				if (this.opts.placeholder === '') this.opts.placeholder = false;
46011
46012				if (this.opts.placeholder !== false)
46013				{
46014					this.opts.focus = false;
46015					this.$editor.one('focus.redactor_placeholder', $.proxy(this.placeholderFocus, this));
46016
46017					return $('<span class="redactor_placeholder" data-redactor="verified">').attr('contenteditable', false).text(this.opts.placeholder);
46018				}
46019			}
46020
46021			return false;
46022		},
46023		placeholderFocus: function()
46024		{
46025			this.$editor.find('span.redactor_placeholder').remove();
46026
46027			var html = '';
46028			if (this.opts.linebreaks === false) html = this.opts.emptyHtml;
46029
46030			this.$editor.off('focus.redactor_placeholder');
46031			this.$editor.html(html);
46032
46033			if (this.opts.linebreaks === false)
46034			{
46035				// place the cursor inside emptyHtml
46036				this.selectionStart(this.$editor.children()[0]);
46037			}
46038			else
46039			{
46040				this.focus();
46041			}
46042
46043			this.sync();
46044		},
46045		placeholderRemove: function()
46046		{
46047			this.opts.placeholder = false;
46048			this.$editor.find('span.redactor_placeholder').remove();
46049			this.$editor.off('focus.redactor_placeholder');
46050		},
46051		placeholderRemoveFromCode: function(html)
46052		{
46053			return html.replace(/<span class="redactor_placeholder"(.*?)>(.*?)<\/span>/i, '');
46054		},
46055
46056		// SHORTCUTS
46057		shortcuts: function(e, key)
46058		{
46059
46060			if (!this.opts.shortcuts) return;
46061
46062			if (!e.altKey)
46063			{
46064				if (key === 77) this.shortcutsLoad(e, 'removeFormat'); // Ctrl + m
46065				else if (key === 66) this.shortcutsLoad(e, 'bold'); // Ctrl + b
46066				else if (key === 73) this.shortcutsLoad(e, 'italic'); // Ctrl + i
46067
46068				else if (key === 74) this.shortcutsLoad(e, 'insertunorderedlist'); // Ctrl + j
46069				else if (key === 75) this.shortcutsLoad(e, 'insertorderedlist'); // Ctrl + k
46070
46071				else if (key === 72) this.shortcutsLoad(e, 'superscript'); // Ctrl + h
46072				else if (key === 76) this.shortcutsLoad(e, 'subscript'); // Ctrl + l
46073			}
46074			else
46075			{
46076				if (key === 48) this.shortcutsLoadFormat(e, 'p'); // ctrl + alt + 0
46077				else if (key === 49) this.shortcutsLoadFormat(e, 'h1'); // ctrl + alt + 1
46078				else if (key === 50) this.shortcutsLoadFormat(e, 'h2'); // ctrl + alt + 2
46079				else if (key === 51) this.shortcutsLoadFormat(e, 'h3'); // ctrl + alt + 3
46080				else if (key === 52) this.shortcutsLoadFormat(e, 'h4'); // ctrl + alt + 4
46081				else if (key === 53) this.shortcutsLoadFormat(e, 'h5'); // ctrl + alt + 5
46082				else if (key === 54) this.shortcutsLoadFormat(e, 'h6'); // ctrl + alt + 6
46083
46084			}
46085
46086		},
46087		shortcutsLoad: function(e, cmd)
46088		{
46089			e.preventDefault();
46090			this.execCommand(cmd, false);
46091		},
46092		shortcutsLoadFormat: function(e, cmd)
46093		{
46094			e.preventDefault();
46095			this.formatBlocks(cmd);
46096		},
46097
46098		// FOCUS
46099		focus: function()
46100		{
46101			if (!this.browser('opera')) this.window.setTimeout($.proxy(this.focusSet, this, true), 1);
46102			else this.$editor.focus();
46103		},
46104		focusEnd: function()
46105		{
46106			this.focusSet();
46107		},
46108		focusSet: function(collapse)
46109		{
46110			this.$editor.focus();
46111
46112			var range = this.getRange();
46113			range.selectNodeContents(this.$editor[0]);
46114
46115			// collapse - controls the position of focus: the beginning (true), at the end (false).
46116			range.collapse(collapse || false);
46117
46118			var sel = this.getSelection();
46119			sel.removeAllRanges();
46120			sel.addRange(range);
46121		},
46122
46123		// TOGGLE
46124		toggle: function(direct)
46125		{
46126			if (this.opts.visual) this.toggleCode(direct);
46127			else this.toggleVisual();
46128		},
46129		toggleVisual: function()
46130		{
46131			var html = this.$source.hide().val();
46132
46133			if (typeof this.modified !== 'undefined')
46134			{
46135				this.modified = this.cleanRemoveSpaces(this.modified, false) !== this.cleanRemoveSpaces(html, false);
46136			}
46137
46138			if (this.modified)
46139			{
46140				// don't remove the iframe even if cleared all.
46141				if (this.opts.fullpage && html === '') this.setFullpageOnInit(html);
46142				else
46143				{
46144					this.set(html);
46145					if (this.opts.fullpage) this.buildBindKeyboard();
46146				}
46147			}
46148
46149			if (this.opts.iframe) this.$frame.show();
46150			else this.$editor.show();
46151
46152			if (this.opts.fullpage) this.$editor.attr('contenteditable', true );
46153
46154			this.$source.off('keydown.redactor-textarea-indenting');
46155
46156			this.$editor.focus();
46157			this.selectionRestore();
46158
46159			this.observeStart();
46160			this.buttonActiveVisual();
46161			this.buttonInactive('html');
46162			this.opts.visual = true;
46163		},
46164		toggleCode: function(direct)
46165		{
46166			if (direct !== false) this.selectionSave();
46167
46168			var height = null;
46169			if (this.opts.iframe)
46170			{
46171				height = this.$frame.height();
46172				if (this.opts.fullpage) this.$editor.removeAttr('contenteditable');
46173				this.$frame.hide();
46174			}
46175			else
46176			{
46177				height = this.$editor.innerHeight();
46178				this.$editor.hide();
46179			}
46180
46181			var html = this.$source.val();
46182
46183			// tidy html
46184			if (html !== '' && this.opts.tidyHtml)
46185			{
46186				this.$source.val(this.cleanHtml(html));
46187			}
46188
46189			this.modified = html;
46190
46191			this.$source.height(height).show().focus();
46192
46193			// textarea indenting
46194			this.$source.on('keydown.redactor-textarea-indenting', this.textareaIndenting);
46195
46196			this.buttonInactiveVisual();
46197			this.buttonActive('html');
46198			this.opts.visual = false;
46199		},
46200		textareaIndenting: function(e)
46201		{
46202			if (e.keyCode === 9)
46203			{
46204				var $el = $(this);
46205				var start = $el.get(0).selectionStart;
46206				$el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
46207				$el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
46208				return false;
46209			}
46210		},
46211
46212		// AUTOSAVE
46213		autosave: function()
46214		{
46215			var savedHtml = false;
46216			this.autosaveInterval = setInterval($.proxy(function()
46217			{
46218				var html = this.get();
46219				if (savedHtml !== html)
46220				{
46221					$.ajax({
46222						url: this.opts.autosave,
46223						type: 'post',
46224						data: this.$source.attr('name') + '=' + escape(encodeURIComponent(html)),
46225						success: $.proxy(function(data)
46226						{
46227							this.callback('autosave', false, data);
46228							savedHtml = html;
46229
46230						}, this)
46231					});
46232				}
46233			}, this), this.opts.autosaveInterval*1000);
46234		},
46235
46236		// TOOLBAR
46237		toolbarBuild: function()
46238		{
46239			// extend buttons
46240			if (this.opts.air)
46241			{
46242				this.opts.buttons = this.opts.airButtons;
46243			}
46244			else
46245			{
46246				if (!this.opts.buttonSource)
46247				{
46248					var index = this.opts.buttons.indexOf('html'), next = this.opts.buttons[index + 1];
46249					this.opts.buttons.splice(index, 1);
46250					if (next === '|') this.opts.buttons.splice(index, 1);
46251				}
46252			}
46253
46254			$.extend(this.opts.toolbar, this.opts.buttonsCustom);
46255			$.each(this.opts.buttonsAdd, $.proxy(function(i, s)
46256			{
46257				this.opts.buttons.push(s);
46258
46259			}, this));
46260
46261			// formatting tags
46262			if (this.opts.toolbar)
46263			{
46264				$.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
46265				{
46266					if ($.inArray(i, this.opts.formattingTags ) == '-1') delete this.opts.toolbar.formatting.dropdown[i];
46267
46268				}, this));
46269			}
46270
46271			// if no buttons don't create a toolbar
46272			if (this.opts.buttons.length === 0) return false;
46273
46274			// air enable
46275			this.airEnable();
46276
46277			// toolbar build
46278			this.$toolbar = $('<ul>').addClass('redactor_toolbar').attr('id', 'redactor_toolbar_' + this.uuid);
46279
46280			if (this.opts.air)
46281			{
46282				// air box
46283				this.$air = $('<div class="redactor_air">').attr('id', 'redactor_air_' + this.uuid).hide();
46284				this.$air.append(this.$toolbar);
46285				$('body').append(this.$air);
46286			}
46287			else
46288			{
46289				if (this.opts.toolbarExternal) $(this.opts.toolbarExternal).html(this.$toolbar);
46290				else this.$box.prepend(this.$toolbar);
46291			}
46292
46293			$.each(this.opts.buttons, $.proxy(function(i, btnName)
46294			{
46295				// separator
46296				if ( btnName === '|' ) this.$toolbar.append($(this.opts.buttonSeparator));
46297				else if(this.opts.toolbar[btnName])
46298				{
46299					var btnObject = this.opts.toolbar[btnName];
46300					if (this.opts.fileUpload === false && btnName === 'file') return true;
46301					this.$toolbar.append( $('<li>').append(this.buttonBuild(btnName, btnObject)));
46302				}
46303
46304			}, this));
46305
46306			this.$toolbar.find('a').attr('tabindex', '-1');
46307
46308			// fixed
46309			if (this.opts.toolbarFixed)
46310			{
46311				this.toolbarObserveScroll();
46312				$(this.opts.toolbarFixedTarget).on('scroll.redactor', $.proxy(this.toolbarObserveScroll, this));
46313			}
46314
46315			// buttons response
46316			if (this.opts.activeButtons)
46317			{
46318				var buttonActiveObserver = $.proxy(this.buttonActiveObserver, this);
46319				this.$editor.on('mouseup.redactor keyup.redactor', buttonActiveObserver);
46320			}
46321		},
46322		toolbarObserveScroll: function()
46323		{
46324			var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
46325			var boxTop = this.$box.offset().top;
46326			var left = 0;
46327
46328			var end = boxTop + this.$box.height() + 40;
46329
46330			if (scrollTop > boxTop)
46331			{
46332				var width = '100%';
46333				if (this.opts.toolbarFixedBox)
46334				{
46335					left = this.$box.offset().left;
46336					width = this.$box.innerWidth();
46337					this.$toolbar.addClass('toolbar_fixed_box');
46338				}
46339
46340				this.toolbarFixed = true;
46341				this.$toolbar.css({
46342					position: 'fixed',
46343					width: width,
46344					zIndex: 1005,
46345					top: this.opts.toolbarFixedTopOffset + 'px',
46346					left: left
46347				});
46348
46349				if (scrollTop < end) this.$toolbar.css('visibility', 'visible');
46350				else this.$toolbar.css('visibility', 'hidden');
46351			}
46352			else
46353			{
46354				this.toolbarFixed = false;
46355				this.$toolbar.css({
46356					position: 'relative',
46357					width: 'auto',
46358					top: 0,
46359					left: left
46360				});
46361
46362				if (this.opts.toolbarFixedBox) this.$toolbar.removeClass('toolbar_fixed_box');
46363			}
46364		},
46365
46366		// AIR
46367		airEnable: function()
46368		{
46369			if (!this.opts.air) return;
46370
46371			this.$editor.on('mouseup.redactor keyup.redactor', this, $.proxy(function(e)
46372			{
46373				var text = this.getSelectionText();
46374
46375				if (e.type === 'mouseup' && text != '') this.airShow(e);
46376				if (e.type === 'keyup' && e.shiftKey && text != '')
46377				{
46378					var $focusElem = $(this.getElement(this.getSelection().focusNode)), offset = $focusElem.offset();
46379					offset.height = $focusElem.height();
46380					this.airShow(offset, true);
46381				}
46382
46383			}, this));
46384		},
46385		airShow: function (e, keyboard)
46386		{
46387			if (!this.opts.air) return;
46388
46389			var left, top;
46390			$('.redactor_air').hide();
46391
46392			if (keyboard)
46393			{
46394				left = e.left;
46395				top = e.top + e.height + 14;
46396
46397				if (this.opts.iframe)
46398				{
46399					top += this.$box.position().top - $(this.document).scrollTop();
46400					left += this.$box.position().left;
46401				}
46402			}
46403			else
46404			{
46405				var width = this.$air.innerWidth();
46406
46407				left = e.clientX;
46408				if ($(this.document).width() < (left + width)) left -= width;
46409
46410				top = e.clientY + 14;
46411				if (this.opts.iframe)
46412				{
46413					top += this.$box.position().top;
46414					left += this.$box.position().left;
46415				}
46416				else top += $( this.document ).scrollTop();
46417			}
46418
46419			this.$air.css({
46420				left: left + 'px',
46421				top: top + 'px'
46422			}).show();
46423
46424			this.airBindHide();
46425		},
46426		airBindHide: function()
46427		{
46428			if (!this.opts.air) return;
46429
46430			var hideHandler = $.proxy(function(doc)
46431			{
46432				$(doc).on('mousedown.redactor', $.proxy(function(e)
46433				{
46434					if ($( e.target ).closest(this.$toolbar).length === 0)
46435					{
46436						this.$air.fadeOut(100);
46437						this.selectionRemove();
46438						$(doc).off(e);
46439					}
46440
46441				}, this)).on('keydown.redactor', $.proxy(function(e)
46442				{
46443					if (e.which === this.keyCode.ESC)
46444					{
46445						this.getSelection().collapseToStart();
46446					}
46447
46448					this.$air.fadeOut(100);
46449					$(doc).off(e);
46450
46451				}, this));
46452			}, this);
46453
46454			// Hide the toolbar at events in all documents (iframe)
46455			hideHandler(document);
46456			if (this.opts.iframe) hideHandler(this.document);
46457		},
46458		airBindMousemoveHide: function()
46459		{
46460			if (!this.opts.air) return;
46461
46462			var hideHandler = $.proxy(function(doc)
46463			{
46464				$(doc).on('mousemove.redactor', $.proxy(function(e)
46465				{
46466					if ($( e.target ).closest(this.$toolbar).length === 0)
46467					{
46468						this.$air.fadeOut(100);
46469						$(doc).off(e);
46470					}
46471
46472				}, this));
46473			}, this);
46474
46475			// Hide the toolbar at events in all documents (iframe)
46476			hideHandler(document);
46477			if (this.opts.iframe) hideHandler(this.document);
46478		},
46479
46480		// DROPDOWNS
46481		dropdownBuild: function($dropdown, dropdownObject)
46482		{
46483			$.each(dropdownObject, $.proxy(function(btnName, btnObject)
46484			{
46485				if (!btnObject.className) btnObject.className = '';
46486
46487				var $item;
46488				if (btnObject.name === 'separator') $item = $('<a class="redactor_separator_drop">');
46489				else
46490				{
46491					$item = $('<a href="#" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>');
46492					$item.on('click', $.proxy(function(e)
46493					{
46494						if (e.preventDefault) e.preventDefault();
46495						if (this.browser('msie')) e.returnValue = false;
46496
46497						if (btnObject.callback) btnObject.callback.call(this, btnName, $item, btnObject, e);
46498						if (btnObject.exec) this.execCommand(btnObject.exec, btnName);
46499						if (btnObject.func) this[btnObject.func](btnName);
46500
46501						this.buttonActiveObserver();
46502						if (this.opts.air) this.$air.fadeOut(100);
46503
46504					}, this));
46505				}
46506
46507				$dropdown.append($item);
46508
46509			}, this));
46510		},
46511		dropdownShow: function(e, key)
46512		{
46513			if (!this.opts.visual)
46514			{
46515				e.preventDefault();
46516				return false;
46517			}
46518
46519			var $dropdown = this.$toolbar.find('.redactor_dropdown_box_' + key);
46520			var $button = this.buttonGet(key);
46521
46522			if ($button.hasClass('dropact')) this.dropdownHideAll();
46523			else
46524			{
46525				this.dropdownHideAll();
46526
46527				this.buttonActive(key);
46528				$button.addClass('dropact');
46529
46530				var keyPosition = $button.position();
46531				if (this.toolbarFixed)
46532				{
46533					keyPosition = $button.offset();
46534				}
46535
46536				// fix right placement
46537				var dropdownWidth = $dropdown.width();
46538				if ((keyPosition.left + dropdownWidth) > $(document).width())
46539				{
46540					keyPosition.left -= dropdownWidth;
46541				}
46542
46543				var left = keyPosition.left + 'px';
46544				var btnHeight = 29;
46545
46546				var position = 'absolute';
46547				var top = btnHeight + 'px';
46548
46549				if (this.opts.toolbarFixed && this.toolbarFixed) position = 'fixed';
46550				else if (!this.opts.air) top = keyPosition.top + btnHeight + 'px';
46551
46552				$dropdown.css({ position: position, left: left, top: top }).show();
46553			}
46554
46555
46556			var hdlHideDropDown = $.proxy(function(e)
46557			{
46558				this.dropdownHide(e, $dropdown);
46559
46560			}, this);
46561
46562			$(document).one('click', hdlHideDropDown);
46563			this.$editor.one('click', hdlHideDropDown);
46564
46565			e.stopPropagation();
46566		},
46567		dropdownHideAll: function()
46568		{
46569			this.$toolbar.find('a.dropact').removeClass('redactor_act').removeClass('dropact');
46570			$('.redactor_dropdown').hide();
46571		},
46572		dropdownHide: function (e, $dropdown)
46573		{
46574			if (!$(e.target).hasClass('dropact'))
46575			{
46576				$dropdown.removeClass('dropact');
46577				this.dropdownHideAll();
46578			}
46579		},
46580
46581		// BUTTONS
46582		buttonBuild: function(btnName, btnObject)
46583		{
46584			var $button = $('<a href="javascript:;" title="' + btnObject.title + '" tabindex="-1" class="redactor_btn redactor_btn_' + btnName + '"></a>');
46585
46586			$button.on('click', $.proxy(function(e)
46587			{
46588				if (e.preventDefault) e.preventDefault();
46589				if (this.browser('msie')) e.returnValue = false;
46590
46591				if ($button.hasClass('redactor_button_disabled')) return false;
46592
46593				if (this.isFocused() === false && !btnObject.exec)
46594				{
46595					this.$editor.focus();
46596				}
46597
46598				if (btnObject.exec)
46599				{
46600					this.$editor.focus();
46601					this.execCommand(btnObject.exec, btnName);
46602					this.airBindMousemoveHide();
46603
46604				}
46605				else if (btnObject.func && btnObject.func !== 'show')
46606				{
46607					this[btnObject.func](btnName);
46608					this.airBindMousemoveHide();
46609
46610				}
46611				else if (btnObject.callback)
46612				{
46613					btnObject.callback.call(this, btnName, $button, btnObject, e);
46614					this.airBindMousemoveHide();
46615
46616				}
46617				else if (btnObject.dropdown)
46618				{
46619					this.dropdownShow(e, btnName);
46620				}
46621
46622				this.buttonActiveObserver(false, btnName);
46623
46624			}, this));
46625
46626			// dropdown
46627			if (btnObject.dropdown)
46628			{
46629				var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_' + btnName + '" style="display: none;">');
46630				$dropdown.appendTo(this.$toolbar);
46631				this.dropdownBuild($dropdown, btnObject.dropdown);
46632			}
46633
46634			return $button;
46635		},
46636		buttonGet: function(key)
46637		{
46638			if (!this.opts.toolbar) return false;
46639			return $(this.$toolbar.find('a.redactor_btn_' + key));
46640		},
46641		buttonActiveToggle: function(key)
46642		{
46643			var btn = this.buttonGet(key);
46644
46645			if (btn.hasClass('redactor_act')) btn.removeClass('redactor_act');
46646			else btn.addClass('redactor_act');
46647		},
46648		buttonActive: function(key)
46649		{
46650			this.buttonGet(key).addClass('redactor_act');
46651		},
46652		buttonInactive: function(key)
46653		{
46654			this.buttonGet(key).removeClass('redactor_act');
46655		},
46656		buttonInactiveAll: function(btnName)
46657		{
46658			$.each(this.opts.toolbar, $.proxy(function(k)
46659			{
46660				if (k != btnName) this.buttonInactive(k);
46661
46662			}, this));
46663		},
46664		buttonActiveVisual: function()
46665		{
46666			this.$toolbar.find('a.redactor_btn').not('a.redactor_btn_html').removeClass('redactor_button_disabled');
46667		},
46668		buttonInactiveVisual: function()
46669		{
46670			this.$toolbar.find('a.redactor_btn').not('a.redactor_btn_html').addClass('redactor_button_disabled');
46671		},
46672		buttonChangeIcon: function (key, classname)
46673		{
46674			this.buttonGet(key).addClass('redactor_btn_' + classname);
46675		},
46676		buttonRemoveIcon: function(key, classname)
46677		{
46678			this.buttonGet(key).removeClass('redactor_btn_' + classname);
46679		},
46680		buttonAddSeparator: function()
46681		{
46682			this.$toolbar.append($(this.opts.buttonSeparator));
46683		},
46684		buttonAddSeparatorAfter: function(key)
46685		{
46686			this.buttonGet(key).parent().after($(this.opts.buttonSeparator));
46687		},
46688		buttonAddSeparatorBefore: function(key)
46689		{
46690			this.buttonGet(key).parent().before($(this.opts.buttonSeparator));
46691		},
46692		buttonRemoveSeparatorAfter: function(key)
46693		{
46694			this.buttonGet(key).parent().next().remove();
46695		},
46696		buttonRemoveSeparatorBefore: function(key)
46697		{
46698			this.buttonGet(key).parent().prev().remove();
46699		},
46700		buttonSetRight: function(key)
46701		{
46702			if (!this.opts.toolbar) return;
46703			this.buttonGet(key).parent().addClass('redactor_btn_right');
46704		},
46705		buttonSetLeft: function(key)
46706		{
46707			if (!this.opts.toolbar) return;
46708			this.buttonGet(key).parent().removeClass('redactor_btn_right');
46709		},
46710		buttonAdd: function(key, title, callback, dropdown)
46711		{
46712			if (!this.opts.toolbar) return;
46713			var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
46714			this.$toolbar.append( $('<li>').append(btn));
46715		},
46716		buttonAddFirst: function(key, title, callback, dropdown)
46717		{
46718			if (!this.opts.toolbar) return;
46719			var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
46720			this.$toolbar.prepend($('<li>').append(btn));
46721		},
46722		buttonAddAfter: function(afterkey, key, title, callback, dropdown)
46723		{
46724			if (!this.opts.toolbar) return;
46725			var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
46726			var $btn = this.buttonGet(afterkey);
46727
46728			if ($btn.size() !== 0) $btn.parent().after($('<li>').append(btn));
46729			else this.$toolbar.append($('<li>').append(btn));
46730		},
46731		buttonAddBefore: function(beforekey, key, title, callback, dropdown)
46732		{
46733			if (!this.opts.toolbar) return;
46734			var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
46735			var $btn = this.buttonGet(beforekey);
46736
46737			if ($btn.size() !== 0) $btn.parent().before($('<li>').append(btn));
46738			else this.$toolbar.append($('<li>').append(btn));
46739		},
46740		buttonRemove: function (key, separator)
46741		{
46742			var $btn = this.buttonGet(key);
46743			if (separator) $btn.parent().next().remove();
46744			$btn.parent().removeClass('redactor_btn_right');
46745			$btn.remove();
46746		},
46747		buttonActiveObserver: function(e, btnName)
46748		{
46749			var parent = this.getParent();
46750			this.buttonInactiveAll(btnName);
46751
46752			if (e === false && btnName !== 'html')
46753			{
46754				if ($.inArray(btnName, this.opts.activeButtons) != -1)
46755				{
46756					this.buttonActiveToggle(btnName);
46757				}
46758				return;
46759			}
46760
46761			if (parent && parent.tagName === 'A') this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_edit);
46762			else this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_insert);
46763
46764			if (this.opts.activeButtonsAdd)
46765			{
46766				$.each(this.opts.activeButtonsAdd, $.proxy(function(i,s)
46767				{
46768					this.opts.activeButtons.push(s);
46769
46770				}, this));
46771
46772				$.extend(this.opts.activeButtonsStates, this.opts.activeButtonsAdd);
46773			}
46774
46775			$.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
46776			{
46777				if ($(parent).closest(key, this.$editor.get()[0]).length != 0)
46778				{
46779					this.buttonActive(value);
46780				}
46781
46782			}, this));
46783
46784			var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
46785			if ($parent.length)
46786			{
46787				var align = $parent.css('text-align');
46788
46789				switch (align)
46790				{
46791					case 'right':
46792						this.buttonActive('alignright');
46793					break;
46794					case 'center':
46795						this.buttonActive('aligncenter');
46796					break;
46797					case 'justify':
46798						this.buttonActive('justify');
46799					break;
46800					default:
46801						this.buttonActive('alignleft');
46802					break;
46803				}
46804			}
46805		},
46806
46807		// EXEC
46808		exec: function(cmd, param, sync)
46809		{
46810			if (cmd === 'formatblock' && this.browser('msie')) param = '<' + param + '>';
46811
46812			if (cmd === 'inserthtml' && this.browser('msie'))
46813			{
46814				this.$editor.focus();
46815				this.document.selection.createRange().pasteHTML(param);
46816			}
46817			else
46818			{
46819				this.document.execCommand(cmd, false, param);
46820			}
46821
46822			if (sync !== false) this.sync();
46823			this.callback('execCommand', cmd, param);
46824		},
46825		execCommand: function(cmd, param, sync)
46826		{
46827			if (!this.opts.visual)
46828			{
46829				this.$source.focus();
46830				return false;
46831			}
46832
46833			if (cmd === 'inserthtml')
46834			{
46835				this.insertHtml(param, sync);
46836				this.callback('execCommand', cmd, param);
46837				return;
46838			}
46839
46840			// stop formatting pre
46841			if (this.currentOrParentIs('PRE') && !this.opts.formattingPre)
46842			{
46843				return false;
46844			}
46845
46846			if (cmd === 'insertunorderedlist' || cmd === 'insertorderedlist')
46847			{
46848				this.bufferSet();
46849
46850				var parent = this.getParent();
46851				var $list = $(parent).closest('ol, ul');
46852				var remove = false;
46853
46854				if ($list.length)
46855				{
46856					remove = true;
46857					var listTag = $list[0].tagName;
46858				 	if ((cmd === 'insertunorderedlist' && listTag === 'OL')
46859				 	|| (cmd === 'insertorderedlist' && listTag === 'UL'))
46860				 	{
46861					 	remove = false;
46862					}
46863				}
46864
46865				this.selectionSave();
46866
46867				// remove lists
46868				if (remove)
46869				{
46870					var nodes = this.getNodes();
46871					var elems = this.getBlocks(nodes);
46872
46873					if (typeof nodes[0] != 'undefined' && nodes.length > 1 && nodes[0].nodeType == 3)
46874					{
46875						// fix the adding the first li to the array
46876						elems.unshift(this.getBlock());
46877					}
46878
46879					var data = '', replaced = '';
46880					$.each(elems, $.proxy(function(i,s)
46881					{
46882						if (s.tagName == 'LI')
46883						{
46884							var $s = $(s);
46885							var cloned = $s.clone();
46886							cloned.find('ul', 'ol').remove();
46887
46888							if (this.opts.linebreaks === false) data += this.outerHtml($('<p>').append(cloned.contents()));
46889							else data += cloned.html() + '<br>';
46890
46891							if (i == 0)
46892							{
46893								$s.addClass('redactor-replaced').empty();
46894								replaced = this.outerHtml($s);
46895							}
46896							else $s.remove();
46897						}
46898
46899					}, this));
46900
46901					html = this.$editor.html().replace(replaced, '</' + listTag + '>' + data + '<' + listTag + '>');
46902
46903					this.$editor.html(html);
46904					this.$editor.find(listTag + ':empty').remove();
46905
46906				}
46907
46908				// insert lists
46909				else
46910				{
46911					this.document.execCommand(cmd);
46912
46913					var parent = this.getParent();
46914					var $list = $(parent).closest('ol, ul');
46915
46916					if ($list.length)
46917					{
46918						if ((this.browser('msie') || this.browser('mozilla')) && parent.tagName !== 'LI')
46919						{
46920							$(parent).replaceWith($(parent).html());
46921						}
46922
46923						var $listParent = $list.parent();
46924						if (this.isParentRedactor($listParent) && this.nodeTestBlocks($listParent[0]))
46925						{
46926							$listParent.replaceWith($listParent.contents());
46927						}
46928					}
46929
46930					if (this.browser('mozilla')) this.$editor.focus();
46931
46932				}
46933
46934				this.selectionRestore();
46935
46936				this.sync();
46937				this.callback('execCommand', cmd, param);
46938				return;
46939			}
46940
46941			if (cmd === 'unlink')
46942			{
46943				this.bufferSet();
46944
46945				var link = this.currentOrParentIs('A');
46946				if (link)
46947				{
46948					$(link).replaceWith($(link).text());
46949
46950					this.sync();
46951					this.callback('execCommand', cmd, param);
46952					return;
46953				}
46954			}
46955
46956			this.exec(cmd, param, sync);
46957
46958			if (cmd === 'inserthorizontalrule')
46959			{
46960				this.$editor.find('hr').removeAttr('id');
46961			}
46962		},
46963
46964		// INDENTING
46965		indentingIndent: function()
46966		{
46967			this.indentingStart('indent');
46968		},
46969		indentingOutdent: function()
46970		{
46971			this.indentingStart('outdent');
46972		},
46973		indentingStart: function(cmd)
46974		{
46975			this.bufferSet();
46976
46977			if (cmd === 'indent')
46978			{
46979				var block = this.getBlock();
46980
46981				this.selectionSave();
46982
46983				if (block && block.tagName == 'LI')
46984				{
46985					// li
46986					var parent = this.getParent();
46987
46988					var $list = $(parent).closest('ol, ul');
46989					var listTag = $list[0].tagName;
46990
46991					var elems = this.getBlocks();
46992
46993					$.each(elems, function(i,s)
46994					{
46995						if (s.tagName == 'LI')
46996						{
46997							var $prev = $(s).prev();
46998							if ($prev.size() != 0 && $prev[0].tagName == 'LI')
46999							{
47000								var $childList = $prev.children('ul, ol');
47001								if ($childList.size() == 0)
47002								{
47003									$prev.append($('<' + listTag + '>').append(s));
47004								}
47005								else $childList.append(s);
47006							}
47007						}
47008					});
47009				}
47010				// linebreaks
47011				else if (block === false && this.opts.linebreaks === true)
47012				{
47013					this.exec('formatBlock', 'blockquote');
47014					var newblock = this.getBlock();
47015					var block = $('<div data-tagblock="">').html($(newblock).html());
47016					$(newblock).replaceWith(block);
47017
47018					var left = this.normalize($(block).css('margin-left')) + this.opts.indentValue;
47019					$(block).css('margin-left', left + 'px');
47020				}
47021				else
47022				{
47023					// all block tags
47024					var elements = this.getBlocks();
47025					$.each(elements, $.proxy(function(i, elem)
47026					{
47027						var $el = false;
47028
47029						if (elem.tagName === 'TD') return;
47030
47031						if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
47032						{
47033							$el = $(elem);
47034						}
47035						else
47036						{
47037							$el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
47038						}
47039
47040						var left = this.normalize($el.css('margin-left')) + this.opts.indentValue;
47041						$el.css('margin-left', left + 'px');
47042
47043					}, this));
47044				}
47045
47046				this.selectionRestore();
47047
47048			}
47049			// outdent
47050			else
47051			{
47052				this.selectionSave();
47053
47054				var block = this.getBlock();
47055				if (block && block.tagName == 'LI')
47056				{
47057					// li
47058					var elems = this.getBlocks();
47059					var index = 0;
47060
47061					this.insideOutdent(block, index, elems);
47062				}
47063				else
47064				{
47065					// all block tags
47066					var elements = this.getBlocks();
47067					$.each(elements, $.proxy(function(i, elem)
47068					{
47069						var $el = false;
47070
47071						if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
47072						{
47073							$el = $(elem);
47074						}
47075						else
47076						{
47077							$el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
47078						}
47079
47080						var left = this.normalize($el.css('margin-left')) - this.opts.indentValue;
47081						if (left <= 0)
47082						{
47083							// linebreaks
47084							if (this.opts.linebreaks === true && typeof($el.data('tagblock')) !== 'undefined')
47085							{
47086								$el.replaceWith($el.html());
47087							}
47088							// all block tags
47089							else
47090							{
47091								$el.css('margin-left', '');
47092								this.removeEmptyAttr($el, 'style');
47093							}
47094						}
47095						else
47096						{
47097							$el.css('margin-left', left + 'px');
47098						}
47099
47100					}, this));
47101				}
47102
47103
47104				this.selectionRestore();
47105			}
47106
47107			this.sync();
47108
47109		},
47110		insideOutdent: function (li, index, elems)
47111		{
47112			if (li && li.tagName == 'LI')
47113			{
47114				var $parent = $(li).parent().parent();
47115				if ($parent.size() != 0 && $parent[0].tagName == 'LI')
47116				{
47117					$parent.after(li);
47118				}
47119				else
47120				{
47121					if (typeof elems[index] != 'undefined')
47122					{
47123						li = elems[index];
47124						index++;
47125
47126						this.insideOutdent(li, index, elems);
47127					}
47128					else
47129					{
47130						this.execCommand('insertunorderedlist');
47131					}
47132				}
47133			}
47134		},
47135
47136		// ALIGNMENT
47137		alignmentLeft: function()
47138		{
47139			this.alignmentSet('', 'JustifyLeft');
47140		},
47141		alignmentRight: function()
47142		{
47143			this.alignmentSet('right', 'JustifyRight');
47144		},
47145		alignmentCenter: function()
47146		{
47147			this.alignmentSet('center', 'JustifyCenter');
47148		},
47149		alignmentJustify: function()
47150		{
47151			this.alignmentSet('justify', 'JustifyFull');
47152		},
47153		alignmentSet: function(type, cmd)
47154		{
47155			this.bufferSet();
47156
47157			if (this.oldIE())
47158			{
47159				this.document.execCommand(cmd, false, false);
47160				return true;
47161			}
47162
47163			this.selectionSave();
47164
47165			var block = this.getBlock();
47166			if (!block && this.opts.linebreaks)
47167			{
47168				// one element
47169				this.exec('formatBlock', 'blockquote');
47170				var newblock = this.getBlock();
47171				var block = $('<div data-tagblock="">').html($(newblock).html());
47172				$(newblock).replaceWith(block);
47173
47174				$(block).css('text-align', type);
47175				this.removeEmptyAttr(block, 'style');
47176
47177				if (type == '' && typeof($(block).data('tagblock')) !== 'undefined')
47178				{
47179					$(block).replaceWith($(block).html());
47180				}
47181			}
47182			else
47183			{
47184				var elements = this.getBlocks();
47185				$.each(elements, $.proxy(function(i, elem)
47186				{
47187					var $el = false;
47188
47189					if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
47190					{
47191						$el = $(elem);
47192					}
47193					else
47194					{
47195						$el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
47196					}
47197
47198					if ($el)
47199					{
47200						$el.css('text-align', type);
47201						this.removeEmptyAttr($el, 'style');
47202					}
47203
47204				}, this));
47205			}
47206
47207			this.selectionRestore();
47208
47209			this.sync();
47210		},
47211
47212		// CLEAN
47213		cleanEmpty: function(html)
47214		{
47215			var ph = this.placeholderStart(html);
47216			if (ph !== false) return ph;
47217
47218			if (this.opts.linebreaks === false)
47219			{
47220				if (html === '') html = this.opts.emptyHtml;
47221				else if (html.search(/^<hr\s?\/?>$/gi) !== -1) html = '<hr>' + this.opts.emptyHtml;
47222			}
47223
47224			return html;
47225		},
47226		cleanConverters: function(html)
47227		{
47228			// convert div to p
47229			if (this.opts.convertDivs) html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
47230			if (this.opts.paragraphy) html = this.cleanParagraphy(html);
47231
47232			return html;
47233		},
47234		cleanConvertProtected: function(html)
47235		{
47236			if (this.opts.templateVars)
47237			{
47238				html = html.replace(/\{\{(.*?)\}\}/gi, '<!-- template double $1 -->');
47239				html = html.replace(/\{(.*?)\}/gi, '<!-- template $1 -->');
47240			}
47241
47242			html = html.replace(/<script(.*?)>([\w\W]*?)<\/script>/gi, '<title type="text/javascript" style="display: none;" class="redactor-script-tag"$1>$2</title>');
47243			html = html.replace(/<style(.*?)>([\w\W]*?)<\/style>/gi, '<section$1 style="display: none;" rel="redactor-style-tag">$2</section>');
47244			html = html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
47245
47246			// php tags convertation
47247			if (this.opts.phpTags) html = html.replace(/<\?php([\w\W]*?)\?>/gi, '<section style="display: none;" rel="redactor-php-tag">$1</section>');
47248			else html = html.replace(/<\?php([\w\W]*?)\?>/gi, '');
47249
47250			return html;
47251		},
47252		cleanReConvertProtected: function(html)
47253		{
47254			if (this.opts.templateVars)
47255			{
47256				html = html.replace(/<!-- template double (.*?) -->/gi, '{{$1}}');
47257				html = html.replace(/<!-- template (.*?) -->/gi, '{$1}');
47258			}
47259
47260			html = html.replace(/<title type="text\/javascript" style="display: none;" class="redactor-script-tag"(.*?)>([\w\W]*?)<\/title>/gi, '<script$1 type="text/javascript">$2</script>');
47261			html = html.replace(/<section(.*?) style="display: none;" rel="redactor-style-tag">([\w\W]*?)<\/section>/gi, '<style$1>$2</style>');
47262			html = html.replace(/<section(.*?)rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
47263
47264			// php tags convertation
47265			if (this.opts.phpTags) html = html.replace(/<section style="display: none;" rel="redactor-php-tag">([\w\W]*?)<\/section>/gi, '<?php\r\n$1\r\n?>');
47266
47267			return html;
47268		},
47269		cleanRemoveSpaces: function(html, buffer)
47270		{
47271			if (buffer !== false)
47272			{
47273				var buffer = []
47274				var matches = html.match(/<(pre|style|script|title)(.*?)>
47274([\w\W]*?)<\/(pre|style|script|title)>/gi);
47275				if (matches === null) matches = [];
47276
47277				if (this.opts.phpTags)
47278				{
47279					var phpMatches = html.match(/<\?php([\w\W]*?)\?>/gi);
47280					if (phpMatches) matches = $.merge(matches, phpMatches);
47281				}
47282
47283				if (matches)
47284				{
47285					$.each(matches, function(i, s)
47286					{
47287						html = html.replace(s, 'buffer_' + i);
47288						buffer.push(s);
47289					});
47290				}
47291			}
47292
47293			html = html.replace(/\n/g, ' ');
47294			html = html.replace(/[\t]*/g, '');
47295			html = html.replace(/\n\s*\n/g, "\n");
47296			html = html.replace(/^[\s\n]*/g, ' ');
47297			html = html.replace(/[\s\n]*$/g, ' ');
47298			html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
47299
47300			html = this.cleanReplacer(html, buffer);
47301
47302			html = html.replace(/\n\n/g, "\n");
47303
47304			return html;
47305		},
47306		cleanReplacer: function(html, buffer)
47307		{
47308			if (buffer === false) return html;
47309
47310			$.each(buffer, function(i,s)
47311			{
47312				html = html.replace('buffer_' + i, s);
47313			});
47314
47315			return html;
47316		},
47317		cleanRemoveEmptyTags: function(html)
47318		{
47319			html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
47320
47321			// remove zero width-space
47322			html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
47323
47324			var etagsInline = ["<b>\\s*</b>", "<b>&nbsp;</b>", "<em>\\s*</em>"]
47325			var etags = ["<pre></pre>", "<blockquote>\\s*</blockquote>", "<dd></dd>", "<dt></dt>", "<ul></ul>", "<ol></ol>", "<li></li>", "<table></table>", "<tr></tr>", "<span>\\s*<span>", "<span>&nbsp;<span>", "<p>\\s*</p>", "<p></p>", "<p>&nbsp;</p>",  "<p>\\s*<br>\\s*</p>", "<div>\\s*</div>", "<div>\\s*<br>\\s*</div>"];
47326
47327			if (this.opts.removeEmptyTags)
47328			{
47329				etags = etags.concat(etagsInline);
47330			}
47331			else etags = etagsInline;
47332
47333			var len = etags.length;
47334			for (var i = 0; i < len; ++i)
47335			{
47336				html = html.replace(new RegExp(etags[i], 'gi'), "");
47337			}
47338
47339			return html;
47340		},
47341		cleanParagraphy: function(html)
47342		{
47343			html = $.trim(html);
47344
47345			if (this.opts.linebreaks === true) return html;
47346			if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
47347
47348			html = html + "\n";
47349
47350
47351			var safes = [];
47352			var matches = html.match(/<(table|div|pre|object)(.*?)>([\w\W]*?)<\/(table|div|pre|object)>/gi);
47353			if (!matches) matches = [];
47354
47355			var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
47356			if (commentsMatches) matches = $.merge(matches, commentsMatches);
47357
47358			if (this.opts.phpTags)
47359			{
47360				var phpMatches = html.match(/<section(.*?)rel="redactor-php-tag">([\w\W]*?)<\/section>/gi);
47361				if (phpMatches) matches = $.merge(matches, phpMatches);
47362			}
47363
47364			if (matches)
47365			{
47366				$.each(matches, function(i,s)
47367				{
47368					safes[i] = s;
47369					html = html.replace(s, '{replace' + i + '}\n');
47370				});
47371			}
47372
47373			html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
47374
47375			function R(str, mod, r)
47376			{
47377				return html.replace(new RegExp(str, mod), r);
47378			}
47379
47380			var blocks = '(comment|html|body|head|title|meta|style|script|link|iframe|table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|option|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)';
47381
47382			html = R('(<' + blocks + '[^>]*>)', 'gi', "\n$1");
47383			html = R('(</' + blocks + '>)', 'gi', "$1\n\n");
47384			html = R("\r\n", 'g', "\n");
47385			html = R("\r", 'g', "\n");
47386			html = R("/\n\n+/", 'g', "\n\n");
47387
47388			var htmls = html.split(new RegExp('\n\s*\n', 'g'), -1);
47389
47390			html = '';
47391			for (var i in htmls)
47392			{
47393				if (htmls.hasOwnProperty(i))
47394                {
47395					if (htmls[i].search('{replace') == -1)
47396					{
47397						html += '<p>' +  htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
47398					}
47399					else html += htmls[i];
47400				}
47401			}
47402
47403			// blockquote
47404			if (html.search(/<blockquote/gi) !== -1)
47405			{
47406				$.each(html.match(/<blockquote(.*?)>([\w\W]*?)<\/blockquote>/gi), function(i,s)
47407				{
47408					var str = '';
47409					str = s.replace('<p>', '');
47410					str = str.replace('</p>', '<br>');
47411					html = html.replace(s, str);
47412				});
47413			}
47414
47415			html = R('<p>\s*</p>', 'gi', '');
47416			html = R('<p>([^<]+)</(div|address|form)>', 'gi', "<p>$1</p></$2>");
47417			html = R('<p>\s*(</?' + blocks + '[^>]*>)\s*</p>', 'gi', "$1");
47418			html = R("<p>(<li.+?)</p>", 'gi', "$1");
47419			html = R('<p>\s*(</?' + blocks + '[^>]*>)', 'gi', "$1");
47420
47421			html = R('(</?' + blocks + '[^>]*>)\s*</p>', 'gi', "$1");
47422			html = R('(</?' + blocks + '[^>]*>)\s*<br />', 'gi', "$1");
47423			html = R('<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)', 'gi', '$1');
47424			html = R("\n</p>", 'gi', '</p>');
47425
47426			html = R('<li><p>
47426', 'gi', '<li>');
47427			html = R('</p></li>', 'gi', '</li>');
47428			html = R('</li><p>', 'gi', '</li>');
47429			//html = R('</ul><p>(.*?)</li>', 'gi', '</ul></li>');
47430			/* html = R('</ol><p>', 'gi', '</ol>'); */
47431			html = R('<p>\t?\n?<p>', 'gi', '<p>');
47432			html = R('</dt><p>', 'gi', '</dt>');
47433			html = R('</dd><p>', 'gi', '</dd>');
47434			html = R('<br></p></blockquote>', 'gi', '</blockquote>');
47435			html = R('<p>\t*</p>', 'gi', '');
47436
47437
47438			// restore safes
47439			$.each(safes, function(i,s)
47440			{
47441				html = html.replace('{replace' + i + '}', s);
47442			});
47443
47444			return $.trim(html);
47445		},
47446		cleanConvertInlineTags: function(html)
47447		{
47448			var boldTag = 'strong';
47449			if (this.opts.boldTag === 'b') boldTag = 'b';
47450
47451			var italicTag = 'em';
47452			if (this.opts.italicTag === 'i') italicTag = 'i';
47453
47454			html = html.replace(/<span style="font-style: italic;">([\w\W]*?)<\/span>/gi, '<' + italicTag + '>$1</' + italicTag + '>');
47455			html = html.replace(/<span style="font-weight: bold;">([\w\W]*?)<\/span>/gi, '<' + boldTag + '>$1</' + boldTag + '>');
47456
47457			// bold, italic, del
47458			if (this.opts.boldTag === 'strong') html = html.replace(/<b>([\w\W]*?)<\/b>/gi, '<strong>$1</strong>');
47459			else html = html.replace(/<strong>([\w\W]*?)<\/strong>/gi, '<b>$1</b>');
47460
47461			if (this.opts.italicTag === 'em') html = html.replace(/<i>([\w\W]*?)<\/i>/gi, '<em>$1</em>');
47462			else html = html.replace(/<em>([\w\W]*?)<\/em>/gi, '<i>$1</i>');
47463
47464			html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>');
47465
47466			return html;
47467		},
47468		cleanStripTags: function(html)
47469		{
47470			if (html == '' || typeof html == 'undefined') return html;
47471
47472			var allowed = false;
47473			if (this.opts.allowedTags !== false) allowed = true;
47474
47475			var arr = allowed === true ? this.opts.allowedTags : this.opts.deniedTags;
47476
47477			var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
47478			html = html.replace(tags, function ($0, $1)
47479			{
47480				if (allowed === true) return $.inArray($1.toLowerCase(), arr) > '-1' ? $0 : '';
47481				else return $.inArray($1.toLowerCase(), arr) > '-1' ? '' : $0;
47482			});
47483
47484			html = this.cleanConvertInlineTags(html);
47485
47486			return html;
47487
47488		},
47489		cleanSavePreCode: function(html, encode)
47490		{
47491			var pre = html.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi);
47492			if (pre !== null)
47493			{
47494				$.each(pre, $.proxy(function(i,s)
47495				{
47496					var arr = s.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i);
47497
47498					arr[3] = arr[3].replace(/&nbsp;/g, ' ');
47499					if (encode !== false) arr[3] = this.cleanEncodeEntities(arr[3]);
47500
47501					html = html.replace(s, '<' + arr[1] + arr[2] + '>' + arr[3] + '</' + arr[1] + '>');
47502
47503				}, this));
47504			}
47505
47506			return html;
47507		},
47508		cleanEncodeEntities: function(str)
47509		{
47510			str = String(str).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
47511			return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
47512		},
47513		cleanUnverified: function()
47514		{
47515			// label, abbr, mark, meter, code, q, dfn, ins, time, kbd, var
47516			var $elem = this.$editor.find('li, img, a, b, strong, sub, sup, i, em, u, small, strike, del, span, cite');
47517
47518			$elem.filter('[style*="background-color: transparent;"][style*="line-height"]')
47519			.css('background-color', '')
47520			.css('line-height', '');
47521
47522			$elem.filter('[style*="background-color: transparent;"]')
47523			.css('background-color', '');
47524
47525			$elem.css('line-height', '');
47526
47527			$.each($elem, $.proxy(function(i,s)
47528			{
47529				this.removeEmptyAttr(s, 'style');
47530			}, this));
47531
47532			// When we paste text in Safari is wrapping inserted div (remove it)
47533			this.$editor.find('div[style="text-align: -webkit-auto;"]').contents().unwrap();
47534		},
47535		cleanHtml: function(code)
47536		{
47537			var i = 0,
47538			codeLength = code.length,
47539			point = 0,
47540			start = null,
47541			end = null,
47542			tag = '',
47543			out = '',
47544			cont = '';
47545
47546			this.cleanlevel = 0;
47547
47548			for (; i < codeLength; i++)
47549			{
47550				point = i;
47551
47552				// if no more tags, copy and exit
47553				if (-1 == code.substr(i).indexOf( '<' ))
47554				{
47555					out += code.substr(i);
47556
47557					return this.cleanFinish(out);
47558				}
47559
47560				// copy verbatim until a tag
47561				while (point < codeLength && code.charAt(point) != '<')
47562				{
47563					point++;
47564				}
47565
47566				if (i != point)
47567				{
47568					cont = code.substr(i, point - i);
47569					if (!cont.match(/^\s{2,}$/g))
47570					{
47571						if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
47572						else if ('\n' == cont.charAt(0))
47573						{
47574							out += '\n' + this.cleanGetTabs();
47575							cont = cont.replace(/^\s+/, '');
47576						}
47577
47578						out += cont;
47579					}
47580
47581					if (cont.match(/\n/)) out += '\n' + this.cleanGetTabs();
47582				}
47583
47584				start = point;
47585
47586				// find the end of the tag
47587				while (point < codeLength && '>' != code.charAt(point))
47588				{
47589					point++;
47590				}
47591
47592				tag = code.substr(start, point - start);
47593				i = point;
47594
47595				var t;
47596
47597				if ('!--' == tag.substr(1, 3))
47598				{
47599					if (!tag.match(/--$/))
47600					{
47601						while ('-->' != code.substr(point, 3))
47602						{
47603							point++;
47604						}
47605						point += 2;
47606						tag = code.substr(start, point - start);
47607						i = point;
47608					}
47609
47610					if ('\n' != out.charAt(out.length - 1)) out += '\n';
47611
47612					out += this.cleanGetTabs();
47613					out += tag + '>\n';
47614				}
47615				else if ('!' == tag[1])
47616				{
47617					out = this.placeTag(tag + '>', out);
47618				}
47619				else if ('?' == tag[1])
47620				{
47621					out += tag + '>\n';
47622				}
47623				else if (t = tag.match(/^<(script|style|pre)/i))
47624				{
47625					t[1] = t[1].toLowerCase();
47626					tag = this.cleanTag(tag);
47627					out = this.placeTag(tag, out);
47628					end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
47629
47630					if (end)
47631					{
47632						cont = code.substr(i + 1, end);
47633						i += end;
47634						out += cont;
47635					}
47636				}
47637				else
47638				{
47639					tag = this.cleanTag(tag);
47640					out = this.placeTag(tag, out);
47641				}
47642			}
47643
47644			return this.cleanFinish( out );
47645		},
47646		cleanGetTabs: function()
47647		{
47648			var s = '';
47649			for ( var j = 0; j < this.cleanlevel; j++ )
47650			{
47651				s += '\t';
47652			}
47653
47654			return s;
47655		},
47656		cleanFinish: function(code)
47657		{
47658			code = code.replace( /\n\s*\n/g, '\n' );
47659			code = code.replace( /^[\s\n]*/, '' );
47660			code = code.replace( /[\s\n]*$/, '' );
47661			code = code.replace( /<script(.*?)>\n<\/script>/gi, '<script$1></script>' );
47662
47663			this.cleanlevel = 0;
47664
47665			return code;
47666		},
47667		cleanTag: function (tag)
47668		{
47669			var tagout = '';
47670			tag = tag.replace(/\n/g, ' ');
47671			tag = tag.replace(/\s{2,}/g, ' ');
47672			tag = tag.replace(/^\s+|\s+$/g, ' ');
47673
47674			var suffix = '';
47675			if (tag.match(/\/$/))
47676			{
47677				suffix = '/';
47678				tag = tag.replace(/\/+$/, '');
47679			}
47680
47681			var m;
47682			while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
47683			{
47684				if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
47685				else if (m[1]) tagout += m[1].toLowerCase();
47686
47687				tagout += ' ';
47688				tag = tag.substr(m[0].length);
47689			}
47690
47691			return tagout.replace(/\s*$/, '') + suffix + '>';
47692		},
47693		placeTag: function (tag, out)
47694		{
47695			var nl = tag.match(this.cleannewLevel);
47696			if (tag.match(this.cleanlineBefore) || nl)
47697			{
47698				out = out.replace(/\s*$/, '');
47699				out += '\n';
47700			}
47701
47702			if (nl && '/' == tag.charAt(1)) this.cleanlevel--;
47703			if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
47704			if (nl && '/' != tag.charAt(1)) this.cleanlevel++;
47705
47706			out += tag;
47707
47708			if (tag.match(this.cleanlineAfter) || tag.match(this.cleannewLevel))
47709			{
47710				out = out.replace(/ *$/, '');
47711				out += '\n';
47712			}
47713
47714			return out;
47715		},
47716
47717		// FORMAT
47718		formatEmpty: function(e)
47719		{
47720			var html = $.trim(this.$editor.html());
47721
47722			html = html.replace(/<br\s?\/?>/i, '');
47723			var thtml = html.replace(/<p>\s?<\/p>/gi, '');
47724
47725			if (html === '' || thtml === '')
47726			{
47727				e.preventDefault();
47728
47729				var node = $(this.opts.emptyHtml).get(0);
47730				this.$editor.html(node);
47731				this.focus();
47732			}
47733
47734			this.sync();
47735		},
47736		formatBlocks: function(tag)
47737		{
47738			this.bufferSet();
47739
47740			var nodes = this.getBlocks();
47741			this.selectionSave();
47742
47743			$.each(nodes, $.proxy(function(i, node)
47744			{
47745				if (node.tagName !== 'LI')
47746				{
47747					this.formatBlock(tag, node);
47748				}
47749
47750			}, this));
47751
47752			this.selectionRestore();
47753			this.sync();
47754		},
47755		formatBlock: function(tag, block)
47756		{
47757			if (block === false) block = this.getBlock();
47758			if (block === false)
47759			{
47760				if (this.opts.linebreaks === true) this.execCommand('formatblock', tag);
47761				return true;
47762			}
47763
47764			var contents = '';
47765			if (tag !== 'pre')
47766			{
47767				contents = $(block).contents();
47768			}
47769			else
47770			{
47771				//contents = this.cleanEncodeEntities($(block).text());
47772				contents = $(block).html();
47773				if ($.trim(contents) === '')
47774				{
47775					contents = '<span id="selection-marker-1"></span>';
47776				}
47777			}
47778
47779			if (block.tagName === 'PRE') tag = 'p';
47780
47781			if (this.opts.linebreaks === true && tag === 'p')
47782			{
47783				$(block).replaceWith($('<div>').append(contents).html() + '<br>');
47784			}
47785			else
47786			{
47787				var node = $('<' + tag + '>').append(contents);
47788				$(block).replaceWith(node);
47789			}
47790		},
47791		formatChangeTag: function(fromElement, toTagName, save)
47792		{
47793			if (save !== false) this.selectionSave();
47794
47795			var newElement = $('<' + toTagName + '/>');
47796			$(fromElement).replaceWith(function() { return newElement.append($(this).contents()); });
47797
47798			if (save !== false) this.selectionRestore();
47799
47800			return newElement;
47801		},
47802
47803		// QUOTE
47804		formatQuote: function()
47805		{
47806			this.bufferSet();
47807
47808			if (this.opts.linebreaks === false)
47809			{
47810				this.selectionSave();
47811
47812				var blocks = this.getBlocks();
47813				if (blocks)
47814				{
47815					$.each(blocks, $.proxy(function(i,s)
47816					{
47817						if (s.tagName === 'BLOCKQUOTE')
47818						{
47819							this.formatBlock('p', s, false);
47820						}
47821						else if (s.tagName !== 'LI')
47822						{
47823							this.formatBlock('blockquote', s, false);
47824						}
47825
47826					}, this));
47827				}
47828
47829				this.selectionRestore();
47830			}
47831			// linebreaks
47832			else
47833			{
47834				var block = this.getBlock();
47835				if (block.tagName === 'BLOCKQUOTE')
47836				{
47837					this.selectionSave();
47838
47839					$(block).replaceWith($(block).html() + '<br>');
47840
47841					this.selectionRestore();
47842				}
47843				else
47844				{
47845					var wrapper = this.selectionWrap('blockquote');
47846					var html = $(wrapper).html();
47847
47848					var blocksElemsRemove = ['ul', 'ol', 'table', 'tr', 'tbody', 'thead', 'tfoot', 'dl'];
47849					$.each(blocksElemsRemove, function(i,s)
47850					{
47851						html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
47852						html = html.replace(new RegExp('</' + s + '>', 'gi'), '');
47853					});
47854
47855					var blocksElems = this.opts.blockLevelElements;
47856					blocksElems.push('td');
47857					$.each(blocksElems, function(i,s)
47858					{
47859						html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
47860						html = html.replace(new RegExp('</' + s + '>', 'gi'), '<br>');
47861					});
47862
47863					$(wrapper).html(html);
47864					this.selectionElement(wrapper);
47865					var next = $(wrapper).next();
47866					if (next.size() != 0 && next[0].tagName === 'BR')
47867					{
47868						next.remove();
47869					}
47870				}
47871			}
47872
47873			this.sync();
47874		},
47875
47876		// BLOCK
47877		blockRemoveAttr: function(attr, value)
47878		{
47879			var nodes = this.getBlocks();
47880			$(nodes).removeAttr(attr);
47881
47882			this.sync();
47883		},
47884		blockSetAttr: function(attr, value)
47885		{
47886			var nodes = this.getBlocks();
47887			$(nodes).attr(attr, value);
47888
47889			this.sync();
47890		},
47891		blockRemoveStyle: function(rule)
47892		{
47893			var nodes = this.getBlocks();
47894			$(nodes).css(rule, '');
47895			this.removeEmptyAttr(nodes, 'style');
47896
47897			this.sync();
47898		},
47899		blockSetStyle: function (rule, value)
47900		{
47901			var nodes = this.getBlocks();
47902			$(nodes).css(rule, value);
47903
47904			this.sync();
47905		},
47906		blockRemoveClass: function(className)
47907		{
47908			var nodes = this.getBlocks();
47909			$(nodes).removeClass(className);
47910			this.removeEmptyAttr(nodes, 'class');
47911
47912			this.sync();
47913		},
47914		blockSetClass: function(className)
47915		{
47916			var nodes = this.getBlocks();
47917			$(nodes).addClass(className);
47918
47919			this.sync();
47920		},
47921
47922		// INLINE
47923		inlineRemoveClass: function(className)
47924		{
47925			this.selectionSave();
47926
47927			this.inlineEachNodes(function(node)
47928			{
47929				$(node).removeClass(className);
47930				this.removeEmptyAttr(node, 'class');
47931			});
47932
47933			this.selectionRestore();
47934			this.sync();
47935		},
47936
47937		inlineSetClass: function(className)
47938		{
47939			var current = this.getCurrent();
47940			if (!$(current).hasClass(className)) this.inlineMethods('addClass', className);
47941		},
47942		inlineRemoveStyle: function (rule)
47943		{
47944			this.selectionSave();
47945
47946			this.inlineEachNodes(function(node)
47947			{
47948				$(node).css(rule, '');
47949				this.removeEmptyAttr(node, 'style');
47950			});
47951
47952			this.selectionRestore();
47953			this.sync();
47954		},
47955		inlineSetStyle: function(rule, value)
47956		{
47957			this.inlineMethods('css', rule, value);
47958		},
47959		inlineRemoveAttr: function (attr)
47960		{
47961			this.selectionSave();
47962
47963			var range = this.getRange(), node = this.getElement(), nodes = this.getNodes();
47964
47965			if (range.collapsed || range.startContainer === range.endContainer && node)
47966			{
47967				nodes = $( node );
47968			}
47969
47970			$(nodes).removeAttr(attr);
47971
47972			this.inlineUnwrapSpan();
47973
47974			this.selectionRestore();
47975			this.sync();
47976		},
47977		inlineSetAttr: function(attr, value)
47978		{
47979			this.inlineMethods('attr', attr, value );
47980		},
47981		inlineMethods: function(type, attr, value)
47982		{
47983			this.bufferSet();
47984			this.selectionSave();
47985
47986			var range = this.getRange()
47987			var el = this.getElement();
47988
47989			if ((range.collapsed || range.startContainer === range.endContainer) && el && !this.nodeTestBlocks(el))
47990			{
47991				$(el)[type](attr, value);
47992			}
47993			else
47994			{
47995				this.document.execCommand('fontSize', false, 4 );
47996
47997				var fonts = this.$editor.find('font');
47998				$.each(fonts, $.proxy(function(i, s)
47999				{
48000					this.inlineSetMethods(type, s, attr, value);
48001
48002				}, this));
48003			}
48004
48005			this.selectionRestore();
48006
48007			this.sync();
48008		},
48009		inlineSetMethods: function(type, s, attr, value)
48010		{
48011			var parent = $(s).parent(), el;
48012
48013			if (parent && parent[0].tagName === 'INLINE' && parent[0].attributes.length != 0)
48014			{
48015				el = parent;
48016				$(s).replaceWith($(s).html());
48017			}
48018			else
48019			{
48020				el = $('<inline>').append($(s).contents());
48021				$(s).replaceWith(el);
48022			}
48023
48024			$(el)[type](attr, value);
48025
48026			return el;
48027		},
48028		// Sort elements and execute callback
48029		inlineEachNodes: function(callback)
48030		{
48031			var range = this.getRange(),
48032				node = this.getElement(),
48033				nodes = this.getNodes(),
48034				collapsed;
48035
48036			if (range.collapsed || range.startContainer === range.endContainer && node)
48037			{
48038				nodes = $(node);
48039				collapsed = true;
48040			}
48041
48042			$.each(nodes, $.proxy(function(i, node)
48043			{
48044				if (!collapsed && node.tagName !== 'INLINE')
48045				{
48046					if (node.parentNode.tagName === 'INLINE' && !$(node.parentNode).hasClass('redactor_editor'))
48047					{
48048						node = node.parentNode;
48049					}
48050					else return;
48051				}
48052				callback.call(this, node);
48053
48054			}, this ) );
48055		},
48056		inlineUnwrapSpan: function()
48057		{
48058			var $spans = this.$editor.find('inline');
48059
48060			$.each($spans, $.proxy(function(i, span)
48061			{
48062				var $span = $(span);
48063
48064				if ($span.attr('class') === undefined && $span.attr('style') === undefined)
48065				{
48066					$span.contents().unwrap();
48067				}
48068
48069			}, this));
48070		},
48071		inlineFormat: function(tag)
48072		{
48073			this.selectionSave();
48074
48075			this.document.execCommand('fontSize', false, 4 );
48076
48077			var fonts = this.$editor.find('font');
48078			var last;
48079			$.each(fonts, function(i, s)
48080			{
48081				var el = $('<' + tag + '/>').append($(s).contents());
48082				$(s).replaceWith(el);
48083				last = el;
48084			});
48085
48086			this.selectionRestore();
48087
48088			this.sync();
48089		},
48090		inlineRemoveFormat: function(tag)
48091		{
48092			this.selectionSave();
48093
48094			var utag = tag.toUpperCase();
48095			var nodes = this.getNodes();
48096			var parent = $(this.getParent()).parent();
48097
48098			$.each(nodes, function(i, s)
48099			{
48100				if (s.tagName === utag) this.inlineRemoveFormatReplace(s);
48101			});
48102
48103			if (parent && parent[0].tagName === utag) this.inlineRemoveFormatReplace(parent);
48104
48105			this.selectionRestore();
48106			this.sync();
48107		},
48108		inlineRemoveFormatReplace: function(el)
48109		{
48110			$(el).replaceWith($(el).contents());
48111		},
48112
48113		// INSERT
48114		insertHtml: function (html, sync)
48115		{
48116			var current = this.getCurrent();
48117			var parent = current.parentNode;
48118
48119			this.$editor.focus();
48120
48121			this.bufferSet();
48122
48123			var $html = $('<div>').append($.parseHTML(html));
48124			html = $html.html();
48125
48126			html = this.cleanRemoveEmptyTags(html);
48127
48128			// Update value
48129			$html = $('<div>').append($.parseHTML(html));
48130
48131			var currBlock = this.getBlock();
48132
48133			if ($html.contents().length == 1)
48134			{
48135				var htmlTagName = $html.contents()[0].tagName;
48136
48137				// If the inserted and received text tags match
48138				if (htmlTagName != 'P' && htmlTagName == currBlock.tagName || htmlTagName == 'PRE')
48139				{
48140					html = $html.text();
48141					$html = $('<div>').append(html);
48142				}
48143			}
48144
48145			// add text in a paragraph
48146			if (!this.opts.linebreaks && $html.contents().length == 1 && $html.contents()[0].nodeType == 3
48147				&& (this.getRangeSelectedNodes().length > 2 || (!current || current.tagName == 'BODY' && !parent || parent.tagName == 'HTML')))
48148			{
48149				html = '<p>' + html + '</p>';
48150			}
48151
48152			html = this.setSpansVerifiedHtml(html);
48153
48154			if ($html.contents().length > 1 && currBlock
48155					|| $html.contents().is('p, :header, ul, ol, div, table, blockquote, pre, address, section, header, footer, aside, article'))
48156			{
48157				if (this.browser('msie')) this.document.selection.createRange().pasteHTML(html);
48158				else this.document.execCommand('inserthtml', false, html);
48159			}
48160			else this.insertHtmlAdvanced(html, false);
48161
48162			if (this.selectall)
48163			{
48164				this.window.setTimeout($.proxy(function()
48165				{
48166					if (!this.opts.linebreaks) this.selectionEnd(this.$editor.contents().last());
48167					else this.focusEnd();
48168
48169				}, this), 1);
48170			}
48171
48172			this.observeStart();
48173
48174			// set no editable
48175			this.setNonEditable();
48176
48177			if (sync !== false) this.sync();
48178		},
48179		insertHtmlAdvanced: function(html, sync)
48180		{
48181			var sel = this.getSelection();
48182
48183			if (sel.getRangeAt && sel.rangeCount)
48184			{
48185				var range = sel.getRangeAt(0);
48186				range.deleteContents();
48187
48188				var el = this.document.createElement('div');
48189				el.innerHTML = html;
48190				var frag = this.document.createDocumentFragment(), node, lastNode;
48191				while ((node = el.firstChild))
48192				{
48193					lastNode = frag.appendChild(node);
48194				}
48195
48196				range.insertNode(frag);
48197
48198				if (lastNode)
48199				{
48200					range = range.cloneRange();
48201					range.setStartAfter(lastNode);
48202					range.collapse(true);
48203					sel.removeAllRanges();
48204					sel.addRange(range);
48205				}
48206			}
48207
48208			if (sync !== false)
48209			{
48210				this.sync();
48211			}
48212
48213		},
48214		insertText: function(html)
48215		{
48216			var $html = $($.parseHTML(html));
48217
48218			if ($html.length) html = $html.text();
48219
48220			this.$editor.focus();
48221
48222			if (this.browser('msie')) this.document.selection.createRange().pasteHTML(html);
48223			else this.document.execCommand('inserthtml', false, html);
48224
48225			this.sync();
48226		},
48227		insertNode: function(node)
48228		{
48229			node = node[0] || node;
48230			var sel = this.getSelection();
48231			if (sel.getRangeAt && sel.rangeCount)
48232			{
48233				// with delete contents
48234				range = sel.getRangeAt(0);
48235				range.deleteContents();
48236				range.insertNode(node);
48237				range.setEndAfter(node);
48238				range.setStartAfter(node);
48239				sel.removeAllRanges();
48240				sel.addRange(range);
48241			}
48242		},
48243		insertNodeToCaretPositionFromPoint: function(e, node)
48244		{
48245			var range;
48246			var x = e.clientX, y = e.clientY;
48247			if (this.document.caretPositionFromPoint)
48248			{
48249			    var pos = this.document.caretPositionFromPoint(x, y);
48250			    range = this.getRange();
48251			    range.setStart(pos.offsetNode, pos.offset);
48252			    range.collapse(true);
48253			    range.insertNode(node);
48254			}
48255			else if (this.document.caretRangeFromPoint)
48256			{
48257			    range = this.document.caretRangeFromPoint(x, y);
48258			    range.insertNode(node);
48259			}
48260			else if (typeof document.body.createTextRange != "undefined")
48261			{
48262		        range = this.document.body.createTextRange();
48263		        range.moveToPoint(x, y);
48264		        var endRange = range.duplicate();
48265		        endRange.moveToPoint(x, y);
48266		        range.setEndPoint("EndToEnd", endRange);
48267		        range.select();
48268			}
48269
48270		},
48271		insertAfterLastElement: function(element)
48272		{
48273			if (this.isEndOfElement())
48274			{
48275
48276				if ($($.trim(this.$editor.html())).get(0) != $.trim(element)
48277				&& this.$editor.contents().last()[0] !== element) return false;
48278
48279				this.bufferSet();
48280
48281				if (this.opts.linebreaks === false)
48282				{
48283					var node = $(this.opts.emptyHtml);
48284					$(element).after(node);
48285					this.selectionStart(node);
48286				}
48287				else
48288				{
48289					var node = $('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>', this.document)[0];
48290					$(element).after(node);
48291					$(node).after(this.opts.invisibleSpace);
48292					this.selectionRestore();
48293				}
48294			}
48295		},
48296		insertLineBreak: function()
48297		{
48298			this.selectionSave();
48299			this.$editor.find('#selection-marker-1').before('<br>' + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
48300			this.selectionRestore();
48301		},
48302		insertDoubleLineBreak: function()
48303		{
48304			this.selectionSave();
48305			this.$editor.find('#selection-marker-1').before('<br><br>' + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
48306			this.selectionRestore();
48307		},
48308		replaceLineBreak: function(element)
48309		{
48310			//var node = this.document.createTextNode('\uFEFF');
48311			var node = $('<br>' + this.opts.invisibleSpace);
48312			$(element).replaceWith(node);
48313			this.selectionStart(node);
48314		},
48315
48316		// PASTE
48317		pasteClean: function(html)
48318		{
48319			html = this.callback('pasteBefore', false, html);
48320
48321			if (this.opts.pastePlainText)
48322			{
48323				var tmp = this.document.createElement('div');
48324
48325				html = html.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
48326
48327				tmp.innerHTML = html;
48328				html = tmp.textContent || tmp.innerText;
48329
48330				html = $.trim(html);
48331				html = html.replace('\n', '<br>');
48332				html = this.cleanParagraphy(html);
48333
48334				this.pasteInsert(html);
48335				return false;
48336			}
48337
48338			// clean up pre
48339			if (this.currentOrParentIs('PRE'))
48340			{
48341				html = this.pastePre(html);
48342				this.pasteInsert(html);
48343				return true;
48344			}
48345
48346
48347			// ms word list
48348			html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul><li$2</li>');
48349			html = html.replace(/<p(.*?)class="MsoListParagraphCxSpMiddle"([\w\W]*?)<\/p>/gi, '<li$2</li>');
48350			html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<li$2</li></ul>');
48351			// one line
48352			html = html.replace(/<p(.*?)class="MsoListParagraph"([\w\W]*?)<\/p>/gi, '<ul><li$2</li></ul>');
48353			// remove ms word's bullet
48354			html = html.replace(/·/g, '');
48355
48356			// remove comments and php tags
48357			html = html.replace(/<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi, '');
48358
48359			// remove nbsp
48360			html = html.replace(/(&nbsp;){2,}/gi, '&nbsp;');
48361			html = html.replace(/&nbsp;/gi, ' ');
48362
48363			// remove google docs marker
48364			html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
48365			html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
48366
48367			// strip tags
48368			html = this.cleanStripTags(html);
48369
48370			// prevert
48371			html = html.replace(/<td><\/td>/gi, '[td]');
48372			html = html.replace(/<td>&nbsp;<\/td>/gi, '[td]');
48373			html = html.replace(/<td><br><\/td>/gi, '[td]');
48374			html = html.replace(/<td(.*?)colspan="(.*?)"(.*?)>([\w\W]*?)<\/td>/gi, '[td colspan="$2"]$4[/td]');
48375			html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
48376			html = html.replace(/<iframe(.*?)>([\w\W]*?)<\/iframe>/gi, '[iframe$1]$2[/iframe]');
48377			html = html.replace(/<video(.*?)>([\w\W]*?)<\/video>/gi, '[video$1]$2[/video]');
48378			html = html.replace(/<audio(.*?)>([\w\W]*?)<\/audio>/gi, '[audio$1]$2[/audio]');
48379			html = html.replace(/<embed(.*?)>([\w\W]*?)<\/embed>/gi, '[embed$1]$2[/embed]');
48380			html = html.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]');
48381			html = html.replace(/<param(.*?)>/gi, '[param$1]');
48382			html = html.replace(/<img(.*?)>/gi, '[img$1]');
48383
48384			// remove classes
48385			html = html.replace(/ class="(.*?)"/gi, '');
48386
48387			// remove all attributes
48388			html = html.replace(/<(\w+)([\w\W]*?)>/gi, '<$1>');
48389
48390			// remove empty
48391			html = html.replace(/<[^\/>][^>]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');
48392			html = html.replace(/<div>\s*?\t*?\n*?(<ul>|<ol>|<p>)/gi, '$1');
48393
48394			// revert
48395			html = html.replace(/\[td colspan="(.*?)"\]([\w\W]*?)\[\/td\]/gi, '<td colspan="$1">$2</td>');
48396			html = html.replace(/\[td\]/gi, '<td>&nbsp;</td>');
48397			html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
48398			html = html.replace(/\[iframe(.*?)\]([\w\W]*?)\[\/iframe\]/gi, '<iframe$1>$2</iframe>');
48399			html = html.replace(/\[video(.*?)\]([\w\W]*?)\[\/video\]/gi, '<video$1>$2</video>');
48400			html = html.replace(/\[audio(.*?)\]([\w\W]*?)\[\/audio\]/gi, '<audio$1>$2</audio>');
48401			html = html.replace(/\[embed(.*?)\]([\w\W]*?)\[\/embed\]/gi, '<embed$1>$2</embed>');
48402			html = html.replace(/\[object(.*?)\]([\w\W]*?)\[\/object\]/gi, '<object$1>$2</object>');
48403			html = html.replace(/\[param(.*?)\]/gi, '<param$1>');
48404			html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
48405
48406			// convert div to p
48407			if (this.opts.convertDivs)
48408			{
48409				html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p>$2</p>');
48410				html = html.replace(/<\/div><p>/gi, '<p>');
48411				html = html.replace(/<\/p><\/div>/gi, '</p>');
48412			}
48413
48414			if (this.currentOrParentIs('LI'))
48415			{
48416				html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br>');
48417			}
48418			else
48419			{
48420				html = this.cleanParagraphy(html);
48421			}
48422
48423
48424			// remove span
48425			html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
48426
48427			// remove empty
48428			html = html.replace(/<img>/gi, '');
48429			html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');
48430
48431			html = html.replace(/\n{3,}/gi, '\n');
48432
48433			// remove dirty p
48434			html = html.replace(/<p><p>/gi, '<p>');
48435			html = html.replace(/<\/p><\/p>/gi, '</p>');
48436
48437			html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
48438			html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
48439
48440			if (this.opts.linebreaks === true)
48441			{
48442				html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
48443			}
48444
48445			// remove empty finally
48446			html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');
48447
48448			// remove safari local images
48449			html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
48450
48451			// remove p in td
48452			html = html.replace(/<td(.*?)>(\s*|\t*|\n*)<p>([\w\W]*?)<\/p>(\s*|\t*|\n*)<\/td>/gi, '<td$1>$3</td>');
48453
48454			// remove divs
48455			html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2');
48456			html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2');
48457
48458			// FF specific
48459			this.pasteClipboardMozilla = false;
48460			if (this.browser('mozilla'))
48461			{
48462				if (this.opts.clipboardUpload)
48463				{
48464					var matches = html.match(/<img src="data:image(.*?)"(.*?)>/gi);
48465					if (matches !== null)
48466					{
48467						this.pasteClipboardMozilla = matches;
48468						for (k in matches)
48469						{
48470							var img = matches[k].replace('<img', '<img data-mozilla-paste-image="' + k + '" ');
48471							html = html.replace(matches[k], img);
48472						}
48473					}
48474				}
48475
48476				// FF fix
48477				while (/<br>$/gi.test(html))
48478				{
48479					html = html.replace(/<br>$/gi, '');
48480				}
48481			}
48482
48483			// bullets again
48484			html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
48485
48486			// ie inserts a blank font tags when pasting
48487			while (/<font>([\w\W]*?)<\/font>/gi.test(html))
48488			{
48489				html = html.replace(/<font>([\w\W]*?)<\/font>/gi, '$1');
48490			}
48491
48492
48493			this.pasteInsert(html);
48494
48495		},
48496		pastePre: function(s)
48497		{
48498			s = s.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
48499
48500			var tmp = this.document.createElement('div');
48501			tmp.innerHTML = s;
48502			return this.cleanEncodeEntities(tmp.textContent || tmp.innerText);
48503		},
48504		pasteInsert: function(html)
48505		{
48506			if (this.selectall)
48507			{
48508				if (!this.opts.linebreaks) this.$editor.html(this.opts.emptyHtml);
48509				else this.$editor.html('');
48510
48511				this.$editor.focus();
48512			}
48513
48514			html = this.callback('pasteAfter', false, html);
48515
48516			this.insertHtml(html);
48517
48518			this.selectall = false;
48519			setTimeout($.proxy(function()
48520			{
48521				this.rtePaste = false;
48522
48523				// FF specific
48524				if (this.browser('mozilla'))
48525				{
48526					this.$editor.find('p:empty').remove()
48527				}
48528				if (this.pasteClipboardMozilla !== false)
48529				{
48530					this.pasteClipboardUploadMozilla();
48531				}
48532
48533			}, this), 100);
48534
48535			if (this.opts.autoresize) $(this.document.body).scrollTop(this.saveScroll);
48536			else this.$editor.scrollTop(this.saveScroll);
48537		},
48538		pasteClipboardUploadMozilla: function()
48539		{
48540			var imgs = this.$editor.find('img[data-mozilla-paste-image]');
48541			$.each(imgs, $.proxy(function(i,s)
48542			{
48543				var $s = $(s);
48544				var arr = s.src.split(",");
48545				var data = arr[1]; // raw base64
48546				var contentType = arr[0].split(";")[0].split(":")[1];
48547
48548				$.post(this.opts.clipboardUploadUrl, {
48549				    contentType: contentType,
48550				    data: data
48551				},
48552				$.proxy(function(data)
48553				{
48554					var json = (typeof data === 'string' ? $.parseJSON(data) : data);
48555		        	$s.attr('src', json.filelink);
48556		        	$s.removeAttr('data-mozilla-paste-image');
48557
48558		        	this.sync();
48559
48560					// upload callback
48561					this.callback('imageUpload', $s, json);
48562
48563				}, this));
48564
48565			}, this));
48566		},
48567		pasteClipboardUpload: function(e)
48568		{
48569	        var result = e.target.result;
48570			var arr = result.split(",");
48571			var data = arr[1]; // raw base64
48572			var contentType = arr[0].split(";")[0].split(":")[1];
48573
48574			if (this.opts.clipboardUpload)
48575			{
48576				$.post(this.opts.clipboardUploadUrl, {
48577				    contentType: contentType,
48578				    data: data
48579				},
48580				$.proxy(function(data)
48581				{
48582					var json = (typeof data === 'string' ? $.parseJSON(data) : data);
48583
48584					var html = '<img src="' + json.filelink + '" id="clipboard-image-marker" />';
48585					this.execCommand('inserthtml', html, false);
48586
48587					var image = $(this.$editor.find('img#clipboard-image-marker'));
48588
48589					if (image.length) image.removeAttr('id');
48590					else image = false;
48591
48592					this.sync();
48593
48594					// upload callback
48595					if (image)
48596					{
48597						this.callback('imageUpload', image, json);
48598					}
48599
48600
48601				}, this));
48602			}
48603			else
48604			{
48605	        	this.insertHtml('<img src="' + result + '" />');
48606        	}
48607		},
48608
48609		// BUFFER
48610		bufferSet: function(html)
48611		{
48612			if (html !== undefined) this.opts.buffer.push(html);
48613			else
48614			{
48615				this.selectionSave();
48616				this.opts.buffer.push(this.$editor.html());
48617				this.selectionRemoveMarkers('buffer');
48618			}
48619		},
48620		bufferUndo: function()
48621		{
48622			if (this.opts.buffer.length === 0)
48623			{
48624				this.$editor.focus();
48625				return;
48626			}
48627
48628			// rebuffer
48629			this.selectionSave();
48630			this.opts.rebuffer.push(this.$editor.html());
48631			this.selectionRestore(false, true);
48632
48633			this.$editor.html(this.opts.buffer.pop());
48634
48635			this.selectionRestore();
48636			setTimeout($.proxy(this.observeStart, this), 100);
48637		},
48638		bufferRedo: function()
48639		{
48640			if (this.opts.rebuffer.length === 0)
48641			{
48642				this.$editor.focus();
48643				return false;
48644			}
48645
48646			// buffer
48647			this.selectionSave();
48648			this.opts.buffer.push(this.$editor.html());
48649			this.selectionRestore(false, true);
48650
48651			this.$editor.html(this.opts.rebuffer.pop());
48652			this.selectionRestore(true);
48653			setTimeout($.proxy(this.observeStart, this), 4);
48654		},
48655
48656		// OBSERVE
48657		observeStart: function()
48658		{
48659			this.observeImages();
48660
48661			if (this.opts.observeLinks) this.observeLinks();
48662		},
48663		observeLinks: function()
48664		{
48665			this.$editor.find('a').on('click', $.proxy(this.linkObserver, this));
48666			this.$editor.on('click.redactor', $.proxy(function(e)
48667			{
48668				this.linkObserverTooltipClose(e);
48669
48670			}, this));
48671		},
48672		observeImages: function()
48673		{
48674			if (this.opts.observeImages === false) return false;
48675
48676			this.$editor.find('img').each($.proxy(function(i, elem)
48677			{
48678                $(elem).addClass('with-preview');
48679                $(elem).attr('data-target', elem.src.replace('large_', ''));
48680
48681				if (this.browser('msie')) $(elem).attr('unselectable', 'on');
48682				this.imageResize(elem);
48683
48684			}, this));
48685		},
48686		linkObserver: function(e)
48687		{
48688			var $link = $(e.target);
48689			var pos = $link.offset();
48690			if (this.opts.iframe)
48691			{
48692				var posFrame = this.$frame.offset();
48693				pos.top = posFrame.top + (pos.top - $(this.document).scrollTop());
48694				pos.left += posFrame.left;
48695			}
48696
48697			var tooltip = $('<span class="redactor-link-tooltip"></span>');
48698
48699			var href = $link.attr('href');
48700			if (href.length > 24) href = href.substring(0,24) + '...';
48701
48702			var aLink = $('<a href="' + $link.attr('href') + '" target="_blank">' + href + '</a>').on('click', $.proxy(function(e)
48703			{
48704				this.linkObserverTooltipClose(false);
48705			}, this));
48706
48707			var aEdit = $('<a href="#">' + this.opts.curLang.edit + '</a>').on('click', $.proxy(function(e)
48708			{
48709				e.preventDefault();
48710				this.linkShow();
48711				this.linkObserverTooltipClose(false);
48712
48713			}, this));
48714
48715			var aUnlink = $('<a href="#">' + this.opts.curLang.unlink + '</a>').on('click', $.proxy(function(e)
48716			{
48717				e.preventDefault();
48718				this.execCommand('unlink');
48719				this.linkObserverTooltipClose(false);
48720
48721			}, this));
48722
48723
48724			tooltip.append(aLink);
48725			tooltip.append(' | ');
48726			tooltip.append(aEdit);
48727			tooltip.append(' | ');
48728			tooltip.append(aUnlink);
48729			tooltip.css({
48730				top: (pos.top + 20) + 'px',
48731				left: pos.left + 'px'
48732			});
48733
48734			$('.redactor-link-tooltip').remove();
48735			$('body').append(tooltip);
48736		},
48737		linkObserverTooltipClose: function(e)
48738		{
48739			if (e !== false && e.target.tagName == 'A') return false;
48740			$('.redactor-link-tooltip').remove();
48741		},
48742
48743		// SELECTION
48744		getSelection: function()
48745		{
48746			if (!this.opts.rangy) return this.document.getSelection();
48747			else // rangy
48748			{
48749				if (!this.opts.iframe) return rangy.getSelection();
48750				else return rangy.getSelection(this.$frame[0]);
48751			}
48752		},
48753		getRange: function()
48754		{
48755			if (!this.opts.rangy)
48756			{
48757				if (this.document.getSelection)
48758				{
48759					var sel = this.getSelection();
48760					if (sel.getRangeAt && sel.rangeCount) return sel.getRangeAt(0);
48761				}
48762
48763				return this.document.createRange();
48764			}
48765			else // rangy
48766			{
48767				if (!this.opts.iframe) return rangy.createRange();
48768				else return rangy.createRange(this.iframeDoc());
48769			}
48770		},
48771		selectionElement: function(node)
48772		{
48773			this.setCaret(node);
48774		},
48775		selectionStart: function(node)
48776		{
48777			this.selectionSet(node[0] || node, 0, null, 0);
48778		},
48779		selectionEnd: function(node)
48780		{
48781			this.selectionSet(node[0] || node, 1, null, 1);
48782		},
48783		selectionSet: function(orgn, orgo, focn, foco)
48784		{
48785			if (focn == null) focn = orgn;
48786			if (foco == null) foco = orgo;
48787
48788			var sel = this.getSelection();
48789			if (!sel) return;
48790
48791			var range = this.getRange();
48792			range.setStart(orgn, orgo);
48793			range.setEnd(focn, foco );
48794
48795			try {
48796				sel.removeAllRanges();
48797			} catch (e) {}
48798
48799			sel.addRange(range);
48800		},
48801		selectionWrap: function(tag)
48802		{
48803			tag = tag.toLowerCase();
48804
48805			var block = this.getBlock();
48806			if (block)
48807			{
48808				var wrapper = this.formatChangeTag(block, tag);
48809				this.sync();
48810				return wrapper;
48811			}
48812
48813			var sel = this.getSelection();
48814			var range = sel.getRangeAt(0);
48815			var wrapper = document.createElement(tag);
48816			wrapper.appendChild(range.extractContents());
48817			range.insertNode(wrapper);
48818
48819			this.selectionElement(wrapper);
48820
48821			return wrapper;
48822		},
48823		selectionAll: function()
48824		{
48825			var range = this.getRange();
48826			range.selectNodeContents(this.$editor[0]);
48827
48828			var sel = this.getSelection();
48829			sel.removeAllRanges();
48830			sel.addRange(range);
48831		},
48832		selectionRemove: function()
48833		{
48834			this.getSelection().removeAllRanges();
48835		},
48836		getCaretOffset: function (element)
48837		{
48838			var caretOffset = 0;
48839
48840			var range = this.getRange();
48841			var preCaretRange = range.cloneRange();
48842			preCaretRange.selectNodeContents(element);
48843			preCaretRange.setEnd(range.endContainer, range.endOffset);
48844			caretOffset = $.trim(preCaretRange.toString()).length;
48845
48846			return caretOffset;
48847		},
48848		getCaretOffsetRange: function()
48849		{
48850			return new Range(this.getSelection().getRangeAt(0));
48851		},
48852		setCaret: function (el, start, end)
48853		{
48854			if (typeof end === 'undefined') end = start;
48855			el = el[0] || el;
48856
48857			var range = this.getRange();
48858			range.selectNodeContents(el);
48859
48860			var textNodes = this.getTextNodesIn(el);
48861			var foundStart = false;
48862			var charCount = 0, endCharCount;
48863
48864			if (textNodes.length == 1 && start)
48865			{
48866				range.setStart(textNodes[0], start);
48867				range.setEnd(textNodes[0], end);
48868			}
48869			else
48870			{
48871				for (var i = 0, textNode; textNode = textNodes[i++];)
48872				{
48873					endCharCount = charCount + textNode.length;
48874					if (!foundStart && start >= charCount && (start < endCharCount || (start == endCharCount && i < textNodes.length)))
48875					{
48876						range.setStart(textNode, start - charCount);
48877						foundStart = true;
48878					}
48879
48880					if (foundStart && end <= endCharCount)
48881					{
48882						range.setEnd( textNode, end - charCount );
48883						break;
48884					}
48885
48886					charCount = endCharCount;
48887				}
48888			}
48889
48890			var sel = this.getSelection();
48891			sel.removeAllRanges();
48892			sel.addRange( range );
48893		},
48894		getTextNodesIn: function (node)
48895		{
48896			var textNodes = [];
48897
48898			if (node.nodeType == 3) textNodes.push(node);
48899			else
48900			{
48901				var children = node.childNodes;
48902				for (var i = 0, len = children.length; i < len; ++i)
48903				{
48904					textNodes.push.apply(textNodes, this.getTextNodesIn(children[i]));
48905				}
48906			}
48907
48908			return textNodes;
48909		},
48910
48911		// GET ELEMENTS
48912		getCurrent: function()
48913		{
48914			var el = false;
48915			var sel = this.getSelection();
48916
48917			if (sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;
48918
48919			return this.isParentRedactor(el);
48920		},
48921		getParent: function(elem)
48922		{
48923			elem = elem || this.getCurrent();
48924			if (elem) return this.isParentRedactor( $( elem ).parent()[0] );
48925			else return false;
48926		},
48927		getBlock: function(node)
48928		{
48929			if (typeof node === 'undefined') node = this.getCurrent();
48930
48931			while (node)
48932			{
48933				if (this.nodeTestBlocks(node))
48934				{
48935					if ($(node).hasClass('redactor_editor')) return false;
48936					return node;
48937				}
48938
48939				node = node.parentNode;
48940			}
48941
48942			return false;
48943		},
48944		getBlocks: function(nodes)
48945		{
48946			var newnodes = [];
48947			if (typeof nodes == 'undefined')
48948			{
48949				var range = this.getRange();
48950				if (range && range.collapsed === true) return [this.getBlock()];
48951				var nodes = this.getNodes(range);
48952			}
48953
48954			$.each(nodes, $.proxy(function(i,node)
48955			{
48956				if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;
48957				if (this.nodeTestBlocks(node)) newnodes.push(node);
48958
48959			}, this));
48960
48961			if (newnodes.length === 0) newnodes = [this.getBlock()];
48962
48963			return newnodes;
48964		},
48965		nodeTestBlocks: function(node)
48966		{
48967			return node.nodeType == 1 && this.rTestBlock.test(node.nodeName);
48968		},
48969		tagTestBlock: function(tag)
48970		{
48971			return this.rTestBlock.test(tag);
48972		},
48973		getSelectedNodes: function(range)
48974		{
48975			if (typeof range == 'undefined' || range == false) var range = this.getRange()
48976			if (range && range.collapsed === true)
48977			{
48978				return [this.getCurrent()];
48979			}
48980
48981		    var sel = this.getSelection();
48982		    try {
48983		    	var frag = sel.getRangeAt(0).cloneContents();
48984		    }
48985		    catch(e)
48986		    {
48987		    	return(false);
48988		    }
48989
48990		    var tempspan = this.document.createElement("span");
48991		    tempspan.appendChild(frag);
48992
48993		    window.selnodes = tempspan.childNodes;
48994
48995			var len = selnodes.length;
48996		    var output = [];
48997		    for(var i = 0, u = len; i<u; i++)
48998		    {
48999		        output.push(selnodes[i]);
49000			}
49001
49002			if (output.length == 0) output.push(this.getCurrent());
49003
49004		    return output;
49005		},
49006		getNodes: function(range, tag)
49007		{
49008			if (this.opts.linebreaks)
49009			{
49010				return this.getSelectedNodes(range);
49011			}
49012
49013
49014			if (typeof range == 'undefined' || range == false) var range = this.getRange();
49015			if (range && range.collapsed === true)
49016			{
49017				if (typeof tag === 'undefined' && this.tagTestBlock(tag))
49018				{
49019					var block = this.getBlock();
49020					if (block.tagName == tag) return [block];
49021					else return [];
49022				}
49023				else
49024				{
49025					return [this.getCurrent()];
49026				}
49027			}
49028
49029			var nodes = [], finalnodes = [];
49030
49031			var sel = this.document.getSelection();
49032			if (!sel.isCollapsed) nodes = this.getRangeSelectedNodes(sel.getRangeAt(0));
49033
49034			$.each(nodes, $.proxy(function(i,node)
49035			{
49036				if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;
49037
49038				if (typeof tag === 'undefined')
49039				{
49040					if ($.trim(node.textContent) != '')
49041					{
49042						finalnodes.push(node);
49043					}
49044				}
49045				else if (node.tagName == tag)
49046				{
49047					finalnodes.push(node);
49048				}
49049
49050			}, this));
49051
49052			if (finalnodes.length == 0)
49053			{
49054				if (typeof tag === 'undefined' && this.tagTestBlock(tag))
49055				{
49056					var block = this.getBlock();
49057					if (block.tagName == tag) return finalnodes.push(block);
49058					else return [];
49059				}
49060				else
49061				{
49062					finalnodes.push(this.getCurrent());
49063				}
49064			}
49065
49066			return finalnodes;
49067		},
49068		getElement: function(node)
49069		{
49070			if (!node) node = this.getCurrent();
49071			while (node)
49072			{
49073				if (node.nodeType == 1)
49074				{
49075					if ($(node).hasClass('redactor_editor')) return false;
49076					return node;
49077				}
49078
49079				node = node.parentNode;
49080			}
49081
49082			return false;
49083		},
49084		getRangeSelectedNodes: function(range)
49085		{
49086			range = range || this.getRange();
49087			var node = range.startContainer;
49088			var endNode = range.endContainer;
49089
49090			if (node == endNode) return [node];
49091
49092			var rangeNodes = [];
49093			while (node && node != endNode)
49094			{
49095				rangeNodes.push(node = this.nextNode(node));
49096			}
49097
49098			node = range.startContainer;
49099			while (node && node != range.commonAncestorContainer)
49100			{
49101				rangeNodes.unshift( node );
49102				node = node.parentNode;
49103			}
49104
49105			return rangeNodes;
49106		},
49107		nextNode: function(node)
49108		{
49109			if (node.hasChildNodes()) return node.firstChild;
49110			else
49111			{
49112				while (node && !node.nextSibling)
49113				{
49114					node = node.parentNode;
49115				}
49116
49117				if (!node) return null;
49118				return node.nextSibling;
49119			}
49120		},
49121
49122
49123		// GET SELECTION HTML OR TEXT
49124		getSelectionText: function()
49125		{
49126			return this.getSelection().toString();
49127		},
49128		getSelectionHtml: function()
49129		{
49130			var html = '';
49131
49132			var sel = this.getSelection();
49133			if (sel.rangeCount)
49134			{
49135				var container = this.document.createElement( "div" );
49136				var len = sel.rangeCount;
49137				for (var i = 0; i < len; ++i)
49138				{
49139					container.appendChild(sel.getRangeAt(i).cloneContents());
49140				}
49141
49142				html = container.innerHTML;
49143			}
49144
49145			return this.syncClean(html);
49146		},
49147
49148		// SAVE & RESTORE
49149		selectionSave: function()
49150		{
49151			if (!this.isFocused()) this.$editor.focus();
49152
49153			if (!this.opts.rangy)
49154			{
49155				this.selectionCreateMarker(this.getRange());
49156			}
49157			// rangy
49158			else
49159			{
49160				this.savedSel = rangy.saveSelection();
49161			}
49162		},
49163		selectionCreateMarker: function(range, remove)
49164		{
49165			if (!range) return;
49166
49167			var node1 = $('<span id="selection-marker-1" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];
49168			var node2 = $('<span id="selection-marker-2" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];
49169
49170			if (range.collapsed === true)
49171			{
49172				this.selectionSetMarker(range, node1, true);
49173			}
49174			else
49175			{
49176				this.selectionSetMarker(range, node1, true);
49177				this.selectionSetMarker(range, node2, false);
49178			}
49179
49180			this.savedSel = this.$editor.html();
49181
49182			this.selectionRestore(false, false);
49183		},
49184		selectionSetMarker: function(range, node, type)
49185		{
49186			var boundaryRange = range.cloneRange();
49187
49188			boundaryRange.collapse(type);
49189
49190			boundaryRange.insertNode(node);
49191			boundaryRange.detach();
49192		},
49193		selectionRestore: function(replace, remove)
49194		{
49195			if (!this.opts.rangy)
49196			{
49197				if (replace === true && this.savedSel)
49198				{
49199					this.$editor.html(this.savedSel);
49200				}
49201
49202				var node1 = this.$editor.find('span#selection-marker-1');
49203				var node2 = this.$editor.find('span#selection-marker-2');
49204
49205				if (this.browser('mozilla'))
49206				{
49207					this.$editor.focus();
49208				}
49209				else if (!this.isFocused())
49210				{
49211					this.$editor.focus();
49212				}
49213
49214				if (node1.length != 0 && node2.length != 0)
49215				{
49216					this.selectionSet(node1[0], 0, node2[0], 0);
49217				}
49218				else if (node1.length != 0)
49219				{
49220					this.selectionSet(node1[0], 0, null, 0);
49221				}
49222
49223				if (remove !== false)
49224				{
49225					this.selectionRemoveMarkers();
49226					this.savedSel = false;
49227				}
49228			}
49229			// rangy
49230			else
49231			{
49232				rangy.restoreSelection(this.savedSel);
49233			}
49234		},
49235		selectionRemoveMarkers: function(type)
49236		{
49237			if (!this.opts.rangy)
49238			{
49239				$.each(this.$editor.find('span.redactor-selection-marker'), function()
49240				{
49241					var html = $.trim($(this).html().replace(/[^\u0000-\u1C7F]/g, ''));
49242					if (html == '')
49243					{
49244						$(this).remove();
49245					}
49246					else
49247					{
49248						$(this).removeAttr('class').removeAttr('id');
49249					}
49250				});
49251			}
49252			// rangy
49253			else
49254			{
49255				rangy.removeMarkers(this.savedSel);
49256			}
49257		},
49258
49259		// TABLE
49260		tableShow: function()
49261		{
49262			this.selectionSave();
49263
49264			this.modalInit(this.opts.curLang.table, this.opts.modal_table, 300, $.proxy(function()
49265			{
49266				$('#redactor_insert_table_btn').click($.proxy(this.tableInsert, this));
49267
49268				setTimeout(function()
49269				{
49270					$('#redactor_table_rows').focus();
49271
49272				}, 200);
49273
49274			}, this));
49275		},
49276		tableInsert: function()
49277		{
49278			var rows = $('#redactor_table_rows').val(),
49279				columns = $('#redactor_table_columns').val(),
49280				$table_box = $('<div></div>'),
49281				tableId = Math.floor(Math.random() * 99999),
49282				$table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
49283				i, $row, z, $column;
49284
49285			for (i = 0; i < rows; i++)
49286			{
49287				$row = $('<tr></tr>');
49288
49289				for (z = 0; z < columns; z++)
49290				{
49291					$column = $('<td>' + this.opts.invisibleSpace + '</td>');
49292
49293					// set the focus to the first td
49294					if (i === 0 && z === 0) $column.append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
49295
49296					$($row).append($column);
49297				}
49298
49299				$table.append($row);
49300			}
49301
49302			$table_box.append($table);
49303			var html = $table_box.html();
49304
49305			this.modalClose();
49306			this.selectionRestore();
49307
49308			var current = this.getBlock() || this.getCurrent();
49309
49310			if (current && current.tagName != 'BODY') $(current).after(html)
49311			else this.insertHtmlAdvanced(html, false);
49312
49313			this.selectionRestore();
49314
49315			var table = this.$editor.find('#table' + tableId);
49316			this.buttonActiveObserver();
49317
49318			table.find('span#selection-marker-1').remove();
49319			table.removeAttr('id');
49320
49321			this.sync();
49322		},
49323		tableDeleteTable: function()
49324		{
49325			var $table = $(this.getParent()).closest('table');
49326			if ($table.size() == 0) return false;
49327
49328			this.bufferSet();
49329
49330			$table.remove();
49331			this.sync();
49332		},
49333		tableDeleteRow: function()
49334		{
49335			var $table = $(this.getParent()).closest('table');
49336			if ($table.size() == 0) return false;
49337
49338			this.bufferSet();
49339
49340			var $current_tr =  $(this.getParent()).closest('tr');
49341			var $focus_tr = $current_tr.prev().length ? $current_tr.prev() : $current_tr.next();
49342			if ($focus_tr.length)
49343			{
49344				var $focus_td = $focus_tr.children('td' ).first();
49345				if ($focus_td.length)
49346				{
49347					$focus_td.prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
49348				}
49349			}
49350
49351			$current_tr.remove();
49352			this.selectionRestore();
49353			this.sync();
49354		},
49355		tableDeleteColumn: function()
49356		{
49357			var $table = $(this.getParent()).closest('table');
49358			if ($table.size() == 0) return false;
49359
49360			this.bufferSet();
49361
49362			var $current_td =  $(this.getParent()).closest('td');
49363			var index = $current_td.get(0).cellIndex;
49364
49365			// Set the focus correctly
49366			$table.find('tr').each($.proxy(function(i, elem)
49367			{
49368				var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
49369				if (i === 0)
49370				{
49371					$(elem).find('td').eq(focusIndex).prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
49372				}
49373
49374				$(elem).find('td').eq(index).remove();
49375
49376			}, this));
49377
49378			this.selectionRestore();
49379			this.sync();
49380		},
49381		tableAddHead: function()
49382		{
49383			var $table = $(this.getParent()).closest('table');
49384			if ($table.size() == 0) return false;
49385
49386			this.bufferSet();
49387
49388			if ($table.find('thead').size() !== 0) this.tableDeleteHead();
49389			else
49390			{
49391				var tr = $table.find('tr').first().clone();
49392				tr.find('td').html(this.opts.invisibleSpace);
49393				$thead = $('<thead></thead>');
49394				$thead.append(tr);
49395				$table.prepend($thead);
49396
49397				this.sync();
49398			}
49399		},
49400		tableDeleteHead: function()
49401		{
49402			var $table = $(this.getParent()).closest('table');
49403			var $thead = $table.find('thead');
49404
49405			if ($thead.size() == 0) return false;
49406
49407			this.bufferSet();
49408
49409			$thead.remove();
49410			this.sync();
49411		},
49412		tableAddRowAbove: function()
49413		{
49414			this.tableAddRow('before');
49415		},
49416		tableAddRowBelow: function()
49417		{
49418			this.tableAddRow('after');
49419		},
49420		tableAddColumnLeft: function()
49421		{
49422			this.tableAddColumn('before');
49423		},
49424		tableAddColumnRight: function()
49425		{
49426			this.tableAddColumn('after');
49427		},
49428		tableAddRow: function(type)
49429		{
49430			var $table = $(this.getParent()).closest('table');
49431			if ($table.size() == 0) return false;
49432
49433			this.bufferSet();
49434
49435			var $current_tr = $(this.getParent()).closest('tr');
49436			var new_tr = $current_tr.clone();
49437			new_tr.find('td').html(this.opts.invisibleSpace);
49438
49439			if (type === 'after') $current_tr.after(new_tr);
49440			else $current_tr.before(new_tr);
49441
49442			this.sync();
49443		},
49444		tableAddColumn: function (type)
49445		{
49446			var $table = $(this.getParent()).closest('table');
49447			if ($table.size() == 0) return false;
49448
49449			this.bufferSet();
49450
49451			var index = 0;
49452
49453			var $current_tr = $(this.getParent()).closest('tr');
49454			var $current_td =  $(this.getParent()).closest('td');
49455
49456			$current_tr.find('td').each($.proxy(function(i, elem)
49457			{
49458				if ($(elem)[0] === $current_td[0]) index = i;
49459
49460			}, this));
49461
49462			$table.find('tr').each($.proxy(function(i, elem)
49463			{
49464				var $current = $(elem).find('td').eq(index);
49465
49466				var td = $current.clone();
49467				td.html(this.opts.invisibleSpace);
49468
49469				type === 'after' ? $current.after(td) : $current.before(td);
49470
49471			}, this));
49472
49473			this.sync();
49474		},
49475
49476		// VIDEO
49477		videoShow: function()
49478		{
49479			this.selectionSave();
49480
49481			this.modalInit(this.opts.curLang.video, this.opts.modal_video, 600, $.proxy(function()
49482			{
49483				$('#redactor_insert_video_btn').click($.proxy(this.videoInsert, this));
49484
49485				setTimeout(function()
49486				{
49487					$('#redactor_insert_video_area').focus();
49488
49489				}, 200);
49490
49491			}, this));
49492		},
49493		videoInsert: function ()
49494		{
49495			var data = $('#redactor_insert_video_area').val();
49496			data = this.cleanStripTags(data);
49497
49498			this.selectionRestore();
49499
49500			var current = this.getBlock() || this.getCurrent();
49501
49502			if (current) $(current).after(data)
49503			else this.insertHtmlAdvanced(data, false);
49504
49505			this.sync();
49506			this.modalClose();
49507		},
49508
49509		// LINK
49510		linkShow: function()
49511		{
49512			this.selectionSave();
49513
49514			var callback = $.proxy(function()
49515			{
49516				this.insert_link_node = false;
49517
49518				var sel = this.getSelection();
49519				var url = '', text = '', target = '';
49520
49521				var elem = this.getParent();
49522				var par = $(elem).parent().get(0);
49523				if (par && par.tagName === 'A')
49524				{
49525					elem = par;
49526				}
49527
49528				if (elem && elem.tagName === 'A')
49529				{
49530					url = elem.href;
49531					text = $(elem).text();
49532					target = elem.target;
49533
49534					this.insert_link_node = elem;
49535				}
49536				else text = sel.toString();
49537
49538				$('.redactor_link_text').val(text);
49539
49540				var thref = self.location.href.replace(/\/$/i, '');
49541				var turl = url.replace(thref, '');
49542
49543				// remove host from href
49544				if (this.opts.linkProtocol === false)
49545				{
49546					var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
49547					turl = turl.replace(re, '');
49548				}
49549
49550				var tabs = $('#redactor_tabs').find('a');
49551
49552				if (this.opts.linkEmail === false) tabs.eq(1).remove();
49553				if (this.opts.linkAnchor === false) tabs.eq(2).remove();
49554				if (this.opts.linkEmail === false && this.opts.linkAnchor === false)
49555				{
49556					$('#redactor_tabs').remove();
49557					$('#redactor_link_url').val(turl);
49558				}
49559				else
49560				{
49561					if (url.search('mailto:') === 0)
49562					{
49563						this.modalSetTab.call(this, 2);
49564
49565						$('#redactor_tab_selected').val(2);
49566						$('#redactor_link_mailto').val(url.replace('mailto:', ''));
49567					}
49568					else if (turl.search(/^#/gi) === 0)
49569					{
49570						this.modalSetTab.call(this, 3);
49571
49572						$('#redactor_tab_selected').val(3);
49573						$('#redactor_link_anchor').val(turl.replace(/^#/gi, '' ));
49574					}
49575					else
49576					{
49577						$('#redactor_link_url').val(turl);
49578					}
49579				}
49580
49581				if (target === '_blank') $('#redactor_link_blank').prop('checked', true);
49582
49583				$('#redactor_insert_link_btn').click($.proxy(this.linkProcess, this));
49584
49585				setTimeout(function()
49586				{
49587					$('#redactor_link_url').focus();
49588
49589				}, 200);
49590
49591			}, this);
49592
49593			this.modalInit(this.opts.curLang.link, this.opts.modal_link, 460, callback);
49594
49595		},
49596		linkProcess: function()
49597		{
49598			var tab_selected = $('#redactor_tab_selected').val();
49599			var link = '', text = '', target = '', targetBlank = '';
49600
49601			// url
49602			if (tab_selected === '1')
49603			{
49604				link = $('#redactor_link_url').val();
49605				text = $('#redactor_link_url_text').val();
49606
49607				if ($('#redactor_link_blank').prop('checked'))
49608				{
49609					target = ' target="_blank"';
49610					targetBlank = '_blank';
49611				}
49612
49613				// test url (add protocol)
49614				var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}';
49615				var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
49616				var re2 = new RegExp('^' + pattern, 'i');
49617
49618				if (link.search(re) == -1 && link.search(re2) == 0 && this.opts.linkProtocol)
49619				{
49620					link = this.opts.linkProtocol + link;
49621				}
49622			}
49623			// mailto
49624			else if (tab_selected === '2')
49625			{
49626				link = 'mailto:' + $('#redactor_link_mailto').val();
49627				text = $('#redactor_link_mailto_text').val();
49628			}
49629			// anchor
49630			else if (tab_selected === '3')
49631			{
49632				link = '#' + $('#redactor_link_anchor').val();
49633				text = $('#redactor_link_anchor_text').val();
49634			}
49635
49636			text = text.replace(/<|>/g, '');
49637			this.linkInsert('<a href="' + link + '"' + target + '>
49637' + text + '</a>', $.trim(text), link, targetBlank);
49638
49639		},
49640		linkInsert: function (a, text, link, target)
49641		{
49642			this.selectionRestore();
49643
49644			if (text !== '')
49645			{
49646				if (this.insert_link_node)
49647				{
49648					this.bufferSet();
49649
49650					$(this.insert_link_node).text(text).attr('href', link);
49651
49652					if (target !== '') $(this.insert_link_node).attr('target', target);
49653					else $(this.insert_link_node).removeAttr('target');
49654
49655					this.sync();
49656				}
49657				else
49658				{
49659					this.exec('inserthtml', a);
49660				}
49661			}
49662
49663			// link tooltip
49664			setTimeout($.proxy(function()
49665			{
49666				if (this.opts.observeLinks) this.observeLinks();
49667			}, this), 5);
49668
49669			this.modalClose();
49670		},
49671
49672		// FILE
49673		fileShow: function ()
49674		{
49675
49676			this.selectionSave();
49677
49678			var callback = $.proxy(function()
49679			{
49680				var sel = this.getSelection();
49681
49682				var text = '';
49683				if (this.oldIE()) text = sel.text;
49684				else text = sel.toString();
49685
49686				$('#redactor_filename').val(text);
49687
49688				// dragupload
49689				if (!this.isMobile())
49690				{
49691					this.draguploadInit('#redactor_file', {
49692						url: this.opts.fileUpload,
49693						uploadFields: this.opts.uploadFields,
49694						success: $.proxy(this.fileCallback, this),
49695						error: $.proxy( function(obj, json)
49696						{
49697							this.callback('fileUploadError', json);
49698
49699						}, this),
49700						uploadParam: this.opts.fileUploadParam
49701					});
49702				}
49703
49704				this.uploadInit('redactor_file', {
49705					auto: true,
49706					url: this.opts.fileUpload,
49707					success: $.proxy(this.fileCallback, this),
49708					error: $.proxy(function(obj, json)
49709					{
49710						this.callback('fileUploadError', json);
49711
49712					}, this)
49713				});
49714
49715			}, this);
49716
49717			this.modalInit(this.opts.curLang.file, this.opts.modal_file, 500, callback);
49718		},
49719		fileCallback: function(json)
49720		{
49721
49722			this.selectionRestore();
49723
49724			if (json !== false)
49725			{
49726
49727				var text = $('#redactor_filename').val();
49728				if (text === '') text = json.filename;
49729
49730				var link = '<a href="' + json.filelink + '" id="filelink-marker">
49730' + text + '</a>';
49731
49732				// chrome fix
49733				if (this.browser('webkit') && !!this.window.chrome)
49734				{
49735					link = link + '&nbsp;';
49736				}
49737
49738				this.execCommand('inserthtml', link, false);
49739
49740				var linkmarker = $(this.$editor.find('a#filelink-marker'));
49741				if (linkmarker.size() != 0) linkmarker.removeAttr('id');
49742				else linkmarker = false;
49743
49744				this.sync();
49745
49746				// file upload callback
49747				this.callback('fileUpload', linkmarker, json);
49748			}
49749
49750			this.modalClose();
49751		},
49752
49753		// IMAGE
49754		imageShow: function()
49755		{
49756
49757			this.selectionSave();
49758
49759			var callback = $.proxy(function()
49760			{
49761				// json
49762				if (this.opts.imageGetJson)
49763				{
49764					$.getJSON(this.opts.imageGetJson, $.proxy(function(data)
49765					{
49766						var folders = {}, count = 0;
49767
49768						// folders
49769						$.each(data, $.proxy(function(key, val)
49770						{
49771							if (typeof val.folder !== 'undefined')
49772							{
49773								count++;
49774								folders[val.folder] = count;
49775							}
49776
49777						}, this));
49778
49779						var folderclass = false;
49780						$.each(data, $.proxy(function(key, val)
49781						{
49782							// title
49783							var thumbtitle = '';
49784							if (typeof val.title !== 'undefined') thumbtitle = val.title;
49785
49786							var folderkey = 0;
49787							if (!$.isEmptyObject(folders) && typeof val.folder !== 'undefined')
49788							{
49789								folderkey = folders[val.folder];
49790								if (folderclass === false) folderclass = '.redactorfolder' + folderkey;
49791							}
49792
49793							var img = $('<img src="' + val.thumb + '" class="redactorfolder redactorfolder' + folderkey + '" rel="' + val.image + '" title="' + thumbtitle + '" />');
49794							$('#redactor_image_box').append(img);
49795							$(img).click($.proxy(this.imageThumbClick, this));
49796
49797						}, this));
49798
49799						// folders
49800						if (!$.isEmptyObject(folders))
49801						{
49802							$('.redactorfolder').hide();
49803							$(folderclass).show();
49804
49805							var onchangeFunc = function(e)
49806							{
49807								$('.redactorfolder').hide();
49808								$('.redactorfolder' + $(e.target).val()).show();
49809							};
49810
49811							var select = $('<select id="redactor_image_box_select">');
49812							$.each( folders, function(k, v)
49813							{
49814								select.append( $('<option value="' + v + '">' + k + '</option>'));
49815							});
49816
49817							$('#redactor_image_box').before(select);
49818							select.change(onchangeFunc);
49819						}
49820					}, this));
49821
49822				}
49823				else
49824				{
49825					$('#redactor_tabs').find('a').eq(1).remove();
49826				}
49827
49828				if (this.opts.imageUpload || this.opts.s3)
49829				{
49830					// dragupload
49831					if (!this.isMobile() && this.opts.s3 === false)
49832					{
49833						if ($('#redactor_file' ).length)
49834						{
49835							this.draguploadInit('#redactor_file', {
49836								url: this.opts.imageUpload,
49837								uploadFields: this.opts.uploadFields,
49838								success: $.proxy(this.imageCallback, this),
49839								error: $.proxy(function(obj, json)
49840								{
49841									this.callback('imageUploadError', json);
49842
49843								}, this),
49844								uploadParam: this.opts.imageUploadParam
49845							});
49846						}
49847					}
49848
49849					if (this.opts.s3 === false)
49850					{
49851						// ajax upload
49852						this.uploadInit('redactor_file', {
49853							auto: true,
49854							url: this.opts.imageUpload,
49855							success: $.proxy(this.imageCallback, this),
49856							error: $.proxy(function(obj, json)
49857							{
49858								this.callback('imageUploadError', json);
49859
49860							}, this)
49861						});
49862					}
49863					// s3 upload
49864					else
49865					{
49866						$('#redactor_file').on('change.redactor', $.proxy(this.s3handleFileSelect, this));
49867					}
49868
49869				}
49870				else
49871				{
49872					$('.redactor_tab').hide();
49873					if (!this.opts.imageGetJson)
49874					{
49875						$('#redactor_tabs').remove();
49876						$('#redactor_tab3').show();
49877					}
49878					else
49879					{
49880						var tabs = $('#redactor_tabs').find('a');
49881						tabs.eq(0).remove();
49882						tabs.eq(1).addClass('redactor_tabs_act');
49883						$('#redactor_tab2').show();
49884					}
49885				}
49886
49887				$('#redactor_upload_btn').click($.proxy(this.imageCallbackLink, this));
49888
49889				if (!this.opts.imageUpload && !this.opts.imageGetJson)
49890				{
49891					setTimeout(function()
49892					{
49893						$('#redactor_file_link').focus();
49894
49895					}, 200);
49896				}
49897
49898			}, this);
49899
49900			this.modalInit(this.opts.curLang.image, this.opts.modal_image, 610, callback);
49901
49902		},
49903		imageEdit: function(image)
49904		{
49905			var $el = image;
49906			var parent = $el.parent().parent();
49907
49908			var callback = $.proxy(function()
49909			{
49910				$('#redactor_file_alt').val($el.attr('alt'));
49911				$('#redactor_image_edit_src').attr('href', $el.attr('src'));
49912				$('#redactor_form_image_align').val($el.css('float'));
49913
49914				if ($(parent).get(0).tagName === 'A')
49915				{
49916					$('#redactor_file_link').val($(parent).attr('href'));
49917
49918					if ($(parent).attr('target') == '_blank')
49919					{
49920						$('#redactor_link_blank').prop('checked', true);
49921					}
49922				}
49923
49924				$('#redactor_image_delete_btn').click($.proxy(function()
49925				{
49926					this.imageRemove($el);
49927
49928				}, this));
49929
49930				$('#redactorSaveBtn').click($.proxy(function()
49931				{
49932					this.imageSave($el);
49933
49934				}, this));
49935
49936			}, this);
49937
49938			this.modalInit(this.opts.curLang.edit, this.opts.modal_image_edit, 380, callback);
49939
49940		},
49941		imageRemove: function(el)
49942		{
49943			var parent = $(el).parent();
49944			$(el).remove();
49945
49946			if (parent.length && parent[0].tagName === 'P')
49947			{
49948				this.$editor.focus();
49949				this.selectionStart(parent);
49950			}
49951
49952			// delete callback
49953			this.callback('imageDelete', el);
49954
49955			this.modalClose();
49956			this.sync();
49957		},
49958		imageSave: function(el)
49959		{
49960			var $el = $(el);
49961			var parent = $el.parent();
49962
49963			$el.attr('alt', $('#redactor_file_alt').val());
49964
49965			var floating = $('#redactor_form_image_align').val();
49966
49967			if (floating === 'left')
49968			{
49969				$el.css({ 'float': 'left', 'margin': '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0' });
49970			}
49971			else if (floating === 'right')
49972			{
49973				$el.css({ 'float': 'right', 'margin': '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + '' });
49974			}
49975			else
49976			{
49977				var imageBox = $el.closest('#redactor-image-box');
49978				if (imageBox.size() != 0) imageBox.css({ 'float': '', 'margin': '' });
49979				$el.css({ 'float': '', 'margin': '' });
49980			}
49981
49982			// as link
49983			var link = $.trim($('#redactor_file_link').val());
49984			if (link !== '')
49985			{
49986				var target = false;
49987				if ($('#redactor_link_blank').prop('checked'))
49988				{
49989					target = true;
49990				}
49991
49992				if (parent.get(0).tagName !== 'A')
49993				{
49994					var a = $('<a href="' + link + '">' + this.outerHtml(el) + '</a>');
49995
49996					if (target)
49997					{
49998						a.attr('target', '_blank');
49999					}
50000
50001					$el.replaceWith(a);
50002				}
50003				else
50004				{
50005					parent.attr('href', link);
50006					if (target)
50007					{
50008						parent.attr('target', '_blank');
50009					}
50010					else
50011					{
50012						parent.removeAttr('target');
50013					}
50014				}
50015			}
50016			else
50017			{
50018				if (parent.get(0).tagName === 'A')
50019				{
50020					parent.replaceWith(this.outerHtml(el));
50021				}
50022			}
50023
50024			this.modalClose();
50025			this.observeImages();
50026			this.sync();
50027
50028		},
50029		imageResizeHide: function(e)
50030		{
50031			if (e !== false && $(e.target).parent().size() != 0 && $(e.target).parent()[0].id === 'redactor-image-box')
50032			{
50033				return false;
50034			}
50035
50036			var imageBox = this.$editor.find('#redactor-image-box');
50037			if (imageBox.size() == 0)
50038			{
50039				return false;
50040			}
50041
50042			this.$editor.find('#redactor-image-editter, #redactor-image-resizer').remove();
50043
50044			var margin = imageBox[0].style.margin;
50045			if (margin != '0px')
50046			{
50047				imageBox.find('img').css('margin', margin);
50048				imageBox.css('margin', '');
50049			}
50050
50051			imageBox.find('img').css('opacity', '');
50052			imageBox.replaceWith(function()
50053			{
50054				return $(this).contents();
50055			});
50056
50057			$(document).off('click.redactor-image-resize-hide');
50058			this.$editor.off('click.redactor-image-resize-hide');
50059			this.$editor.off('keydown.redactor-image-delete');
50060
50061			this.sync()
50062
50063		},
50064		imageResize: function(image)
50065		{
50066			var $image = $(image);
50067
50068			$image.on('mousedown', $.proxy(function()
50069			{
50070				this.imageResizeHide(false);
50071			}, this));
50072
50073			$image.on('dragstart', $.proxy(function()
50074			{
50075				this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
50076				{
50077					setTimeout($.proxy(function()
50078					{
50079						this.observeImages();
50080						this.$editor.off('drop.redactor-image-inside-drop');
50081						this.sync();
50082
50083					}, this), 1);
50084
50085				},this));
50086			}, this));
50087
50088			$image.on('click', $.proxy(function(e)
50089			{
50090				if (this.$editor.find('#redactor-image-box').size() != 0)
50091				{
50092					return false;
50093				}
50094
50095				var clicked = false,
50096				start_x,
50097				start_y,
50098				ratio = $image.width() / $image.height(),
50099				min_w = 20,
50100				min_h = 10;
50101
50102				var imageResizer = this.imageResizeControls($image);
50103
50104				// resize
50105				var isResizing = false;
50106				imageResizer.on('mousedown', function(e)
50107				{
50108					isResizing = true;
50109					e.preventDefault();
50110
50111					ratio = $image.width() / $image.height();
50112
50113					start_x = Math.round(e.pageX - $image.eq(0).offset().left);
50114					start_y = Math.round(e.pageY - $image.eq(0).offset().top);
50115
50116				});
50117
50118				$(this.document.body).on('mousemove', $.proxy(function(e)
50119				{
50120					if (isResizing)
50121					{
50122						var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x;
50123						var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y;
50124
50125						var div_h = $image.height();
50126
50127						var new_h = parseInt(div_h, 10) + mouse_y;
50128						var new_w = Math.round(new_h * ratio);
50129
50130						if (new_w > min_w)
50131						{
50132							$image.width(new_w);
50133
50134							if (new_w < 100)
50135							{
50136								this.imageEditter.css({
50137									marginTop: '-7px',
50138									marginLeft: '-13px',
50139									fontSize: '9px',
50140									padding: '3px 5px'
50141								});
50142							}
50143							else
50144							{
50145								this.imageEditter.css({
50146									marginTop: '-11px',
50147									marginLeft: '-18px',
50148									fontSize: '11px',
50149									padding: '7px 10px'
50150								});
50151							}
50152						}
50153
50154						start_x = Math.round(e.pageX - $image.eq(0).offset().left);
50155						start_y = Math.round(e.pageY - $image.eq(0).offset().top);
50156
50157						this.sync()
50158					}
50159				}, this)).on('mouseup', function()
50160				{
50161					isResizing = false;
50162				});
50163
50164
50165				this.$editor.on('keydown.redactor-image-delete', $.proxy(function(e)
50166				{
50167					var key = e.which;
50168
50169					if (this.keyCode.BACKSPACE == key || this.keyCode.DELETE == key)
50170					{
50171						this.imageResizeHide(false);
50172						this.imageRemove($image);
50173					}
50174
50175				}, this));
50176
50177				$(document).on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
50178				this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
50179
50180
50181			}, this));
50182		},
50183		imageResizeControls: function($image)
50184		{
50185			var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
50186			imageBox.css({
50187				position: 'relative',
50188				display: 'inline-block',
50189				lineHeight: 0,
50190				outline: '1px dashed rgba(0, 0, 0, .6)',
50191				'float': $image.css('float')
50192			});
50193			imageBox.attr('contenteditable', false);
50194
50195			var margin = $image[0].style.margin;
50196			if (margin != '0px')
50197			{
50198				imageBox.css('margin', margin);
50199				$image.css('margin', '');
50200			}
50201
50202			$image.css('opacity', .5).after(imageBox);
50203
50204			// editter
50205			this.imageEditter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.opts.curLang.edit + '</span>');
50206			this.imageEditter.css({
50207				position: 'absolute',
50208				zIndex: 2,
50209				top: '50%',
50210				left: '50%',
50211				marginTop: '-11px',
50212				marginLeft: '-18px',
50213				lineHeight: 1,
50214				backgroundColor: '#000',
50215				color: '#fff',
50216				fontSize: '11px',
50217				padding: '7px 10px',
50218				cursor: 'pointer'
50219			});
50220			this.imageEditter.attr('contenteditable', false);
50221			this.imageEditter.on('click', $.proxy(function()
50222			{
50223				this.imageEdit($image);
50224			}, this));
50225			imageBox.append(this.imageEditter);
50226
50227			// resizer
50228			var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
50229			imageResizer.css({
50230				position: 'absolute',
50231				zIndex: 2,
50232				lineHeight: 1,
50233				cursor: 'nw-resize',
50234				bottom: '-4px',
50235				right: '-5px',
50236				border: '1px solid #fff',
50237				backgroundColor: '#000',
50238				width: '8px',
50239				height: '8px'
50240			});
50241			imageResizer.attr('contenteditable', false);
50242			imageBox.append(imageResizer);
50243
50244			imageBox.append($image);
50245
50246			return imageResizer;
50247		},
50248		imageThumbClick: function(e)
50249		{
50250			var img = '<img id="image-marker" src="' + $(e.target).attr('rel') + '" alt="' + $(e.target).attr('title') + '" />';
50251
50252			if (this.opts.paragraphy) img = '<p>' + img + '</p>';
50253
50254			this.imageInsert(img, true);
50255		},
50256		imageCallbackLink: function()
50257		{
50258			var val = $('#redactor_file_link').val();
50259
50260			if (val !== '')
50261			{
50262				var data = '<img id="image-marker" src="' + val + '" />';
50263				if (this.opts.linebreaks === false) data = '<p>' + data + '</p>';
50264
50265				this.imageInsert(data, true);
50266
50267			}
50268			else this.modalClose();
50269		},
50270		imageCallback: function(data)
50271		{
50272			this.imageInsert(data);
50273		},
50274		imageInsert: function(json, link)
50275		{
50276			this.selectionRestore();
50277
50278			if (json !== false)
50279			{
50280				var html = '';
50281				if (link !== true)
50282				{
50283					html = '<img id="image-marker" src="' + json.filelink + '" />';
50284					if (this.opts.paragraphy) html = '<p>' + html + '</p>';
50285				}
50286				else
50287				{
50288					html = json;
50289				}
50290
50291				this.execCommand('inserthtml', html, false);
50292
50293				var image = $(this.$editor.find('img#image-marker'));
50294
50295				if (image.length) image.removeAttr('id');
50296				else image = false;
50297
50298				this.sync();
50299
50300				// upload image callback
50301				link !== true && this.callback('imageUpload', image, json);
50302			}
50303
50304			this.modalClose();
50305			this.observeImages();
50306		},
50307
50308		// MODAL
50309		modalTemplatesInit: function()
50310		{
50311			$.extend( this.opts,
50312			{
50313				modal_file: String()
50314				+ '<section>'
50315					+ '<div id="redactor-progress" class="redactor-progress redactor-progress-striped" style="display: none;">'
50316							+ '<div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div>'
50317					+ '</div>'
50318					+ '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">'
50319						+ '<label>' + this.opts.curLang.filename + '</label>'
50320						+ '<input type="text" id="redactor_filename" class="redactor_input" />'
50321						+ '<div style="margin-top: 7px;">'
50322							+ '<input type="file" id="redactor_file" name="' + this.opts.fileUploadParam + '" />'
50323						+ '</div>'
50324					+ '</form>'
50325				+ '</section>',
50326
50327				modal_image_edit: String()
50328				+ '<section>'
50329					+ '<label>' + this.opts.curLang.title + '</label>'
50330					+ '<input id="redactor_file_alt" class="redactor_input" />'
50331					+ '<label>' + this.opts.curLang.link + '</label>'
50332					+ '<input id="redactor_file_link" class="redactor_input" />'
50333					+ '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
50334					+ '<label>' + this.opts.curLang.image_position + '</label>'
50335					+ '<select id="redactor_form_image_align">'
50336						+ '<option value="none">' + this.opts.curLang.none + '</option>'
50337						+ '<option value="left">' + this.opts.curLang.left + '</option>'
50338						+ '<option value="right">' + this.opts.curLang.right + '</option>'
50339					+ '</select>'
50340				+ '</section>'
50341				+ '<footer>'
50342					+ '<button id="redactor_image_delete_btn" class="redactor_modal_btn redactor_modal_delete_btn">' + this.opts.curLang._delete + '</button>&nbsp;&nbsp;&nbsp;'
50343					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
50344					+ '<input type="button" name="save" class="redactor_modal_btn redactor_modal_action_btn" id="redactorSaveBtn" value="' + this.opts.curLang.save + '" />'
50345				+ '</footer>',
50346
50347				modal_image: String()
50348				+ '<section>'
50349					+ '<div id="redactor_tabs">'
50350						+ '<a href="#" class="redactor_tabs_act">' + this.opts.curLang.upload + '</a>'
50351						+ '<a href="#">' + this.opts.curLang.choose + '</a>'
50352						+ '<a href="#">' + this.opts.curLang.link + '</a>'
50353					+ '</div>'
50354					+ '<div id="redactor-progress" class="redactor-progress redactor-progress-striped" style="display: none;">'
50355							+ '<div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div>'
50356					+ '</div>'
50357					+ '<form id="redactorInsertImageForm" method="post" action="" enctype="multipart/form-data">'
50358						+ '<div id="redactor_tab1" class="redactor_tab">'
50359							+ '<input type="file" id="redactor_file" name="' + this.opts.imageUploadParam + '" />'
50360						+ '</div>'
50361						+ '<div id="redactor_tab2" class="redactor_tab" style="display: none;">'
50362							+ '<div id="redactor_image_box"></div>'
50363						+ '</div>'
50364					+ '</form>'
50365					+ '<div id="redactor_tab3" class="redactor_tab" style="display: none;">'
50366						+ '<label>' + this.opts.curLang.image_web_link + '</label>'
50367						+ '<input type="text" name="redactor_file_link" id="redactor_file_link" class="redactor_input"  />'
50368					+ '</div>'
50369				+ '</section>'
50370				+ '<footer>'
50371					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
50372					+ '<input type="button" name="upload" class="redactor_modal_btn redactor_modal_action_btn" id="redactor_upload_btn" value="' + this.opts.curLang.insert + '" />'
50373				+ '</footer>',
50374
50375				modal_link: String()
50376				+ '<section>'
50377					+ '<form id="redactorInsertLinkForm" method="post" action="">'
50378						+ '<div id="redactor_tabs">'
50379							+ '<a href="#" class="redactor_tabs_act">URL</a>'
50380							+ '<a href="#">Email</a>'
50381							+ '<a href="#">
50381' + this.opts.curLang.anchor + '</a>'
50382						+ '</div>'
50383						+ '<input type="hidden" id="redactor_tab_selected" value="1" />'
50384						+ '<div class="redactor_tab" id="redactor_tab1">'
50385							+ '<label>URL</label>'
50386							+ '<input type="text" id="redactor_link_url" class="redactor_input"  />'
50387							+ '<label>' + this.opts.curLang.text + '</label>'
50388							+ '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_url_text" />'
50389							+ '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
50390						+ '</div>'
50391						+ '<div class="redactor_tab" id="redactor_tab2" style="display: none;">'
50392							+ '<label>Email</label>'
50393							+ '<input type="text" id="redactor_link_mailto" class="redactor_input" />'
50394							+ '<label>' + this.opts.curLang.text + '</label>'
50395							+ '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_mailto_text" />'
50396						+ '</div>'
50397						+ '<div class="redactor_tab" id="redactor_tab3" style="display: none;">'
50398							+ '<label>' + this.opts.curLang.anchor + '</label>'
50399							+ '<input type="text" class="redactor_input" id="redactor_link_anchor"  />'
50400							+ '<label>' + this.opts.curLang.text + '</label>'
50401							+ '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_anchor_text" />'
50402						+ '</div>'
50403					+ '</form>'
50404				+ '</section>'
50405				+ '<footer>'
50406					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
50407					+ '<input type="button" class="redactor_modal_btn redactor_modal_action_btn" id="redactor_insert_link_btn" value="' + this.opts.curLang.insert + '" />'
50408				+ '</footer>',
50409
50410				modal_table: String()
50411				+ '<section>'
50412					+ '<label>' + this.opts.curLang.rows + '</label>'
50413					+ '<input type="text" size="5" value="2" id="redactor_table_rows" />'
50414					+ '<label>' + this.opts.curLang.columns + '</label>'
50415					+ '<input type="text" size="5" value="3" id="redactor_table_columns" />'
50416				+ '</section>'
50417				+ '<footer>'
50418					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
50419					+ '<input type="button" name="upload" class="redactor_modal_btn redactor_modal_action_btn" id="redactor_insert_table_btn" value="' + this.opts.curLang.insert + '" />'
50420				+ '</footer>',
50421
50422				modal_video: String()
50423				+ '<section>'
50424					+ '<form id="redactorInsertVideoForm">'
50425						+ '<label>' + this.opts.curLang.video_html_code + '</label>'
50426						+ '<textarea id="redactor_insert_video_area" style="width: 99%; height: 160px;"></textarea>'
50427					+ '</form>'
50428				+ '</section>'
50429				+ '<footer>'
50430					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
50431					+ '<input type="button" class="redactor_modal_btn redactor_modal_action_btn" id="redactor_insert_video_btn" value="' + this.opts.curLang.insert + '" />'
50432				+ '</footer>'
50433
50434			});
50435		},
50436		modalInit: function(title, content, width, callback)
50437		{
50438			var $redactorModalOverlay = $('#redactor_modal_overlay');
50439
50440			// modal overlay
50441			if (!$redactorModalOverlay.length)
50442			{
50443				this.$overlay = $redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>');
50444				$('body').prepend(this.$overlay);
50445			}
50446
50447			if (this.opts.modalOverlay)
50448			{
50449				$redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this));
50450			}
50451
50452			var $redactorModal = $('#redactor_modal');
50453
50454			if (!$redactorModal.length)
50455			{
50456				this.$modal = $redactorModal = $('<div id="redactor_modal" style="display: none;"><div id="redactor_modal_close">&times;</div><header id="redactor_modal_header"></header><div id="redactor_modal_inner"></div></div>');
50457				$('body').append(this.$modal);
50458			}
50459
50460			$('#redactor_modal_close').on('click', $.proxy(this.modalClose, this));
50461
50462			this.hdlModalClose = $.proxy(function(e)
50463			{
50464				if (e.keyCode === this.keyCode.ESC)
50465				{
50466					this.modalClose();
50467					return false;
50468				}
50469
50470			}, this);
50471
50472			$(document).keyup(this.hdlModalClose);
50473			this.$editor.keyup(this.hdlModalClose);
50474
50475			// set content
50476			this.modalcontent = false;
50477			if (content.indexOf('#') == 0)
50478			{
50479				this.modalcontent = $(content);
50480				$('#redactor_modal_inner').empty().append(this.modalcontent.html());
50481				this.modalcontent.html('');
50482
50483			}
50484			else
50485			{
50486				$('#redactor_modal_inner').empty().append(content);
50487			}
50488
50489			$redactorModal.find('#redactor_modal_header').html(title);
50490
50491			// draggable
50492			if (typeof $.fn.draggable !== 'undefined')
50493			{
50494				$redactorModal.draggable({ handle: '#redactor_modal_header' });
50495				$redactorModal.find('#redactor_modal_header').css('cursor', 'move');
50496			}
50497
50498			var $redactor_tabs = $('#redactor_tabs');
50499
50500			// tabs
50501			if ($redactor_tabs.length )
50502			{
50503				var that = this;
50504				$redactor_tabs.find('a').each(function(i, s)
50505				{
50506					i++;
50507					$(s).on('click', function(e)
50508					{
50509						e.preventDefault();
50510
50511						$redactor_tabs.find('a').removeClass('redactor_tabs_act');
50512						$(this).addClass('redactor_tabs_act');
50513						$('.redactor_tab').hide();
50514						$('#redactor_tab' + i ).show();
50515						$('#redactor_tab_selected').val(i);
50516
50517						if (that.isMobile() === false)
50518						{
50519							var height = $redactorModal.outerHeight();
50520							$redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px');
50521						}
50522					});
50523				});
50524			}
50525
50526			$redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this));
50527
50528			// save scroll
50529			if (this.opts.autoresize === true) this.saveModalScroll = this.document.body.scrollTop;
50530
50531			if (this.isMobile() === false)
50532			{
50533				$redactorModal.css({
50534					position: 'fixed',
50535					top: '-2000px',
50536					left: '50%',
50537					width: width + 'px',
50538					marginLeft: '-' + (width + 60) / 2 + 'px'
50539				}).show();
50540
50541				this.modalSaveBodyOveflow = $(document.body).css('overflow');
50542				$(document.body).css('overflow', 'hidden');
50543
50544			}
50545			else
50546			{
50547				$redactorModal.css({
50548					position: 'fixed',
50549					width: '100%',
50550					height: '100%',
50551					top: '0',
50552					left: '0',
50553					margin: '0',
50554					minHeight: '300px'
50555				}).show();
50556			}
50557
50558			// callback
50559			if (typeof callback === 'function') callback();
50560
50561			if (this.isMobile() === false)
50562			{
50563				setTimeout(function()
50564				{
50565					var height = $redactorModal.outerHeight();
50566					$redactorModal.css({
50567						top: '50%',
50568						height: 'auto',
50569						minHeight: 'auto',
50570						marginTop: '-' + (height + 10) / 2 + 'px'
50571					});
50572				}, 10);
50573			}
50574
50575		},
50576		modalClose: function()
50577		{
50578			$('#redactor_modal_close').off('click', this.modalClose );
50579			$('#redactor_modal').fadeOut('fast', $.proxy(function()
50580			{
50581				var redactorModalInner = $('#redactor_modal_inner');
50582
50583				if (this.modalcontent !== false)
50584				{
50585					this.modalcontent.html(redactorModalInner.html());
50586					this.modalcontent = false;
50587				}
50588
50589				redactorModalInner.html('');
50590
50591				if (this.opts.modalOverlay)
50592				{
50593					$('#redactor_modal_overlay').hide().off('click', this.modalClose);
50594				}
50595
50596				$(document).unbind('keyup', this.hdlModalClose);
50597				this.$editor.unbind('keyup', this.hdlModalClose);
50598
50599				this.selectionRestore();
50600
50601				// restore scroll
50602				if (this.opts.autoresize && this.saveModalScroll) $(this.document.body).scrollTop(this.saveModalScroll);
50603
50604			}, this));
50605
50606
50607			if (this.isMobile() === false)
50608			{
50609				$(document.body).css('overflow', this.modalSaveBodyOveflow ? this.modalSaveBodyOveflow : 'visible');
50610			}
50611
50612			return false;
50613		},
50614		modalSetTab: function(num)
50615		{
50616			$('.redactor_tab').hide();
50617			$('#redactor_tabs').find('a').removeClass('redactor_tabs_act').eq(num - 1).addClass('redactor_tabs_act');
50618			$('#redactor_tab' + num).show();
50619		},
50620
50621		// S3
50622		s3handleFileSelect: function(e)
50623		{
50624			var files = e.target.files;
50625
50626			for (var i = 0, f; f = files[i]; i++)
50627			{
50628				this.s3uploadFile(f);
50629			}
50630		},
50631		s3uploadFile: function(file)
50632		{
50633			this.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
50634			{
50635				this.s3uploadToS3(file, signedURL);
50636			}, this));
50637		},
50638		s3executeOnSignedUrl: function(file, callback)
50639		{
50640			var xhr = new XMLHttpRequest();
50641			xhr.open('GET', this.opts.s3 + '?name=' + file.name + '&type=' + file.type, true);
50642
50643			// Hack to pass bytes through unprocessed.
50644			xhr.overrideMimeType('text/plain; charset=x-user-defined');
50645
50646			xhr.onreadystatechange = function(e)
50647			{
50648				if (this.readyState == 4 && this.status == 200)
50649				{
50650					$('#redactor-progress').fadeIn();
50651					try {
50652						callback(decodeURIComponent(this.responseText));
50653					} catch (e) {
50654						if (typeof window !== 'undefined' && window.Sentry && window.Sentry.captureException) {
50655							window.Sentry.captureException(new Error('Redactor response decoding failed'), {
50656								tags: {
50657									error_type: 'decode_failure',
50658									library: 'redactor',
50659									version: '9.1.6'
50660								},
50661								extra: {
50662									response_text: this.responseText,
50663									error_message: e.message,
50664									stack_trace: e.stack,
50665									timestamp: new Date().toISOString(),
50666									page_url: typeof window !== 'undefined' ? window.location.href : 'unknown'
50667								},
50668								level: 'warning'
50669							});
50670						}
50671						callback(this.responseText);
50672					}
50673				}
50674				else if(this.readyState == 4 && this.status != 200)
50675				{
50676					//setProgress(0, 'Could not contact signing script. Status = ' + this.status);
50677				}
50678			};
50679
50680			xhr.send();
50681		},
50682		s3createCORSRequest: function(method, url)
50683		{
50684			var xhr = new XMLHttpRequest();
50685			if ("withCredentials" in xhr)
50686			{
50687				xhr.open(method, url, true);
50688			}
50689			else if (typeof XDomainRequest != "undefined")
50690			{
50691				xhr = new XDomainRequest();
50692				xhr.open(method, url);
50693			}
50694			else
50695			{
50696				xhr = null;
50697			}
50698
50699			return xhr;
50700		},
50701		s3uploadToS3: function(file, url)
50702		{
50703			var xhr = this.s3createCORSRequest('PUT', url);
50704			if (!xhr)
50705			{
50706				//setProgress(0, 'CORS not supported');
50707			}
50708			else
50709			{
50710				xhr.onload = $.proxy(function()
50711				{
50712					if (xhr.status == 200)
50713					{
50714						//setProgress(100, 'Upload completed.');
50715
50716						$('#redactor-progress').hide();
50717
50718						var s3image = url.split('?');
50719
50720						if (!s3image[0])
50721						{
50722							 // url parsing is fail
50723							 return false;
50724						}
50725
50726						this.selectionRestore();
50727
50728						var html = '';
50729						html = '<img id="image-marker" src="' + s3image[0] + '" />';
50730						if (this.opts.paragraphy) html = '<p>' + html + '</p>';
50731
50732						this.execCommand('inserthtml', html, false);
50733
50734						var image = $(this.$editor.find('img#image-marker'));
50735
50736						if (image.length) image.removeAttr('id');
50737						else image = false;
50738
50739						this.sync();
50740
50741						// upload image callback
50742						this.callback('imageUpload', image, false);
50743
50744						this.modalClose();
50745						this.observeImages();
50746
50747					}
50748					else
50749					{
50750						//setProgress(0, 'Upload error: ' + xhr.status);
50751					}
50752				}, this);
50753
50754				xhr.onerror = function()
50755				{
50756					//setProgress(0, 'XHR error.');
50757				};
50758
50759				xhr.upload.onprogress = function(e)
50760				{
50761					/*
50762					if (e.lengthComputable)
50763					{
50764						var percentLoaded = Math.round((e.loaded / e.total) * 100);
50765						setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
50766					}
50767					*/
50768				};
50769
50770				xhr.setRequestHeader('Content-Type', file.type);
50771				xhr.setRequestHeader('x-amz-acl', 'public-read');
50772
50773				xhr.send(file);
50774			}
50775		},
50776
50777		// UPLOAD
50778		uploadInit: function(el, options)
50779		{
50780			this.uploadOptions = {
50781				url: false,
50782				success: false,
50783				error: false,
50784				start: false,
50785				trigger: false,
50786				auto: false,
50787				input: false
50788			};
50789
50790			$.extend(this.uploadOptions, options);
50791
50792			var $el = $('#' + el);
50793
50794			// Test input or form
50795			if ($el.length && $el[0].tagName === 'INPUT')
50796			{
50797				this.uploadOptions.input = $el;
50798				this.el = $($el[0].form);
50799			}
50800			else this.el = $el;
50801
50802			this.element_action = this.el.attr('action');
50803
50804			// Auto or trigger
50805			if (this.uploadOptions.auto)
50806			{
50807				$(this.uploadOptions.input).change($.proxy(function(e)
50808				{
50809					this.el.submit(function(e)
50810					{
50811						return false;
50812					});
50813
50814					this.uploadSubmit(e);
50815
50816				}, this));
50817
50818			}
50819			else if (this.uploadOptions.trigger)
50820			{
50821				$('#' + this.uploadOptions.trigger).click($.proxy(this.uploadSubmit, this));
50822			}
50823		},
50824		uploadSubmit: function(e)
50825		{
50826			$('#redactor-progress').fadeIn();
50827			this.uploadForm(this.element, this.uploadFrame());
50828		},
50829		uploadFrame: function()
50830		{
50831			this.id = 'f' + Math.floor(Math.random() * 99999);
50832
50833			var d = this.document.createElement('div');
50834			var iframe = '<iframe style="display:none" id="' + this.id + '" name="' + this.id + '"></iframe>';
50835
50836			d.innerHTML = iframe;
50837			$(d).appendTo("body");
50838
50839			// Start
50840			if (this.uploadOptions.start) this.uploadOptions.start();
50841
50842			$( '#' + this.id ).load($.proxy(this.uploadLoaded, this));
50843
50844			return this.id;
50845		},
50846		uploadForm: function(f, name)
50847		{
50848			if (this.uploadOptions.input)
50849			{
50850				var formId = 'redactorUploadForm' + this.id,
50851					fileId = 'redactorUploadFile' + this.id;
50852
50853				this.form = $('<form  action="' + this.uploadOptions.url + '" method="POST" target="' + name + '" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data" />');
50854
50855				// append hidden fields
50856				if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
50857				{
50858					$.each(this.opts.uploadFields, $.proxy(function(k, v)
50859					{
50860						if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
50861
50862						var hidden = $('<input/>', {
50863							'type': "hidden",
50864							'name': k,
50865							'value': v
50866						});
50867
50868						$(this.form).append(hidden);
50869
50870					}, this));
50871				}
50872
50873				var oldElement = this.uploadOptions.input;
50874				var newElement = $(oldElement).clone();
50875
50876				$(oldElement).attr('id', fileId).before(newElement).appendTo(this.form);
50877
50878				$(this.form).css('position', 'absolute')
50879						.css('top', '-2000px')
50880						.css('left', '-2000px')
50881						.appendTo('body');
50882
50883				this.form.submit();
50884
50885			}
50886			else
50887			{
50888				f.attr('target', name)
50889					.attr('method', 'POST')
50890					.attr('enctype', 'multipart/form-data')
50891					.attr('action', this.uploadOptions.url);
50892
50893				this.element.submit();
50894			}
50895		},
50896		uploadLoaded: function()
50897		{
50898			var i = $( '#' + this.id)[0], d;
50899
50900			if (i.contentDocument) d = i.contentDocument;
50901			else if (i.contentWindow) d = i.contentWindow.document;
50902			else d = window.frames[this.id].document;
50903
50904			// Success
50905			if (this.uploadOptions.success)
50906			{
50907				$('#redactor-progress').hide();
50908
50909				if (typeof d !== 'undefined')
50910				{
50911					// Remove bizarre <pre> tag wrappers around our json data:
50912					var rawString = d.body.innerHTML;
50913					var jsonString = rawString.match(/\{(.|\n)*\}/)[0];
50914
50915					jsonString = jsonString.replace(/^\[/, '');
50916					jsonString = jsonString.replace(/\]$/, '');
50917
50918					var json = $.parseJSON(jsonString);
50919
50920					if (typeof json.error == 'undefined') this.uploadOptions.success(json);
50921					else
50922					{
50923						this.uploadOptions.error(this, json);
50924						this.modalClose();
50925					}
50926				}
50927				else
50928				{
50929					this.modalClose();
50930					alert('Upload failed!');
50931				}
50932			}
50933
50934			this.el.attr('action', this.element_action);
50935			this.el.attr('target', '');
50936		},
50937
50938		// DRAGUPLOAD
50939		draguploadInit: function (el, options)
50940		{
50941			this.draguploadOptions = $.extend({
50942				url: false,
50943				success: false,
50944				error: false,
50945				preview: false,
50946				uploadFields: false,
50947				text: this.opts.curLang.drop_file_here,
50948				atext: this.opts.curLang.or_choose,
50949				uploadParam: false
50950			}, options);
50951
50952			if (window.FormData === undefined) return false;
50953
50954			this.droparea = $('<div class="redactor_droparea"></div>');
50955			this.dropareabox = $('<div class="redactor_dropareabox">' + this.draguploadOptions.text + '</div>');
50956			this.dropalternative = $('<div class="redactor_dropalternative">' + this.draguploadOptions.atext + '</div>');
50957
50958			this.droparea.append(this.dropareabox);
50959
50960			$(el).before(this.droparea);
50961			$(el).before(this.dropalternative);
50962
50963			// drag over
50964			this.dropareabox.on('dragover', $.proxy(function()
50965			{
50966				return this.draguploadOndrag();
50967
50968			}, this));
50969
50970			// drag leave
50971			this.dropareabox.on('dragleave', $.proxy(function()
50972			{
50973				return this.draguploadOndragleave();
50974
50975			}, this));
50976
50977			// drop
50978			this.dropareabox.get(0).ondrop = $.proxy(function(e)
50979			{
50980				e.preventDefault();
50981
50982				this.dropareabox.removeClass('hover').addClass('drop');
50983
50984				this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false, false, false, this.draguploadOptions.uploadParam);
50985
50986			}, this );
50987		},
50988		dragUploadAjax: function(url, file, directupload, progress, e, uploadParam)
50989		{
50990
50991
50992			if (!directupload)
50993			{
50994				var xhr = $.ajaxSettings.xhr();
50995				if (xhr.upload)
50996				{
50997					xhr.upload.addEventListener('progress', $.proxy(this.uploadProgress, this), false);
50998				}
50999
51000				$.ajaxSetup({
51001				  xhr: function () { return xhr; }
51002				});
51003			}
51004
51005			var fd = new FormData();
51006
51007			// append file data
51008			if (uploadParam !== false)
51009			{
51010				fd.append(uploadParam, file);
51011			}
51012			else
51013			{
51014				fd.append('file', file);
51015			}
51016
51017			// append hidden fields
51018			if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
51019			{
51020				$.each(this.opts.uploadFields, $.proxy(function(k, v)
51021				{
51022					if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
51023					fd.append(k, v);
51024
51025				}, this));
51026			}
51027
51028			$.ajax({
51029				url: url,
51030				dataType: 'html',
51031				data: fd,
51032				cache: false,
51033				contentType: false,
51034				processData: false,
51035				type: 'POST',
51036				success: $.proxy(function(data)
51037				{
51038					data = data.replace(/^\[/, '');
51039					data = data.replace(/\]$/, '');
51040
51041					var json = (typeof data === 'string' ? $.parseJSON(data) : data);
51042
51043					if (directupload)
51044					{
51045						progress.fadeOut('slow', function()
51046						{
51047							$(this).remove();
51048						});
51049
51050					    var $img = $('<img>');
51051						$img.attr('src', json.filelink).attr('id', 'drag-image-marker');
51052
51053						this.insertNodeToCaretPositionFromPoint(e, $img[0]);
51054
51055						var image = $(this.$editor.find('img#drag-image-marker'));
51056						if (image.length) image.removeAttr('id');
51057						else image = false;
51058
51059						this.sync();
51060						this.observeImages();
51061
51062						// upload callback
51063						if (image) this.callback('imageUpload', image, json);
51064
51065						// error callback
51066						if (typeof json.error !== 'undefined') this.callback('imageUploadError', json);
51067					}
51068					else
51069					{
51070						if (typeof json.error == 'undefined')
51071						{
51072							this.draguploadOptions.success(json);
51073						}
51074						else
51075						{
51076							this.draguploadOptions.error(this, json);
51077							this.draguploadOptions.success(false);
51078						}
51079					}
51080
51081				}, this)
51082			});
51083		},
51084		draguploadOndrag: function()
51085		{
51086			this.dropareabox.addClass('hover');
51087			return false;
51088		},
51089		draguploadOndragleave: function()
51090		{
51091			this.dropareabox.removeClass('hover');
51092			return false;
51093		},
51094		uploadProgress: function(e, text)
51095		{
51096			var percent = e.loaded ? parseInt(e.loaded / e.total * 100, 10) : e;
51097			this.dropareabox.text('Loading ' + percent + '% ' + (text || ''));
51098		},
51099
51100		// UTILS
51101		isMobile: function()
51102		{
51103			return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
51104		},
51105		normalize: function(str)
51106		{
51107			if (typeof(str) === 'undefined') return 0;
51108			return parseInt(str.replace('px',''), 10);
51109		},
51110		outerHtml: function(el)
51111		{
51112			return $('<div>').append($(el).eq(0).clone()).html();
51113		},
51114		isString: function(obj)
51115		{
51116			return Object.prototype.toString.call(obj) == '[object String]';
51117		},
51118		isEmpty: function(html)
51119		{
51120			html = html.replace(/&#x200b;|<br>|<br\/>|&nbsp;/gi, '');
51121			html = html.replace(/\s/g, '');
51122			html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
51123
51124
51125			return html == '';
51126		},
51127		browser: function(browser)
51128		{
51129			var ua = navigator.userAgent.toLowerCase();
51130			var match = /(chrome)[ \/]([\w.]+)/.exec(ua)
51131					|| /(webkit)[ \/]([\w.]+)/.exec(ua)
51132					|| /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua)
51133					|| /(msie) ([\w.]+)/.exec(ua)
51134					|| ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)
51135					|| [];
51136
51137			if (browser == 'version') return match[2];
51138			if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'webkit');
51139
51140			return match[1] == browser;
51141		},
51142		oldIE: function()
51143		{
51144			if (this.browser('msie') && parseInt(this.browser('version'), 10) < 9) return true;
51145			return false;
51146		},
51147		getFragmentHtml: function (fragment)
51148		{
51149			var cloned = fragment.cloneNode(true);
51150			var div = this.document.createElement('div');
51151
51152			div.appendChild(cloned);
51153			return div.innerHTML;
51154		},
51155		extractContent: function()
51156		{
51157			var node = this.$editor[0];
51158			var frag = this.document.createDocumentFragment();
51159			var child;
51160
51161			while ((child = node.firstChild))
51162			{
51163				frag.appendChild(child);
51164			}
51165
51166			return frag;
51167		},
51168		isParentRedactor: function(el)
51169		{
51170			if (!el) return false;
51171			if (this.opts.iframe) return el;
51172
51173			if ($(el).parents('div.redactor_editor').length == 0 || $(el).hasClass('redactor_editor')) return false;
51174			else return el;
51175		},
51176		currentOrParentIs: function(tagName)
51177		{
51178			var parent = this.getParent(), current = this.getCurrent();
51179			return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
51180		},
51181		isEndOfElement: function()
51182		{
51183			var current = this.getBlock();
51184			var offset = this.getCaretOffset(current);
51185
51186			var text = $.trim($(current).text()).replace(/\n\r\n/g, '');
51187
51188			var len = text.length;
51189
51190			if (offset == len) return true;
51191			else return false;
51192		},
51193		isFocused: function()
51194		{
51195			var el, sel = this.getSelection();
51196
51197			if (sel && sel.rangeCount && sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;
51198			if (!el) return false;
51199			if (this.opts.iframe)
51200			{
51201				if (this.getCaretOffsetRange().equals()) return !this.$editor.is(el);
51202				else return true;
51203			}
51204
51205			return $(el).closest('div.redactor_editor').length != 0;
51206		},
51207		removeEmptyAttr: function (el, attr)
51208		{
51209			if ($(el).attr(attr) == '') $(el).removeAttr(attr);
51210		},
51211		removeFromArrayByValue: function(array, value)
51212		{
51213			var index = null;
51214
51215			while ((index = array.indexOf(value)) !== -1)
51216			{
51217				array.splice(index, 1);
51218			}
51219
51220			return array;
51221		}
51222
51223	};
51224
51225	// constructor
51226	Redactor.prototype.init.prototype = Redactor.prototype;
51227
51228	// LINKIFY
51229	$.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertImageLinks, convertVideoLinks, linkSize)
51230	{
51231		var url1 = /(^|&lt;|\s)(www\..+?\..+?)(\s|&gt;|$)/g,
51232			url2 = /(^|&lt;|\s)(((https?|ftp):\/\/|mailto:).+?)(\s|&gt;|$)/g,
51233			urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi,
51234			urlYoutube = /^.*(youtu.be\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/,
51235			urlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
51236
51237		var childNodes = (this.$editor ? this.$editor.get(0) : this).childNodes, i = childNodes.length;
51238		while (i--)
51239		{
51240			var n = childNodes[i];
51241			if (n.nodeType === 3)
51242			{
51243				var html = n.nodeValue;
51244
51245				// youtube & vimeo
51246				if (convertVideoLinks && html)
51247				{
51248					var iframeStart = '<iframe width="500" height="281" src="',
51249						iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
51250
51251					if (html.match(urlYoutube))
51252					{
51253						html = html.replace(urlYoutube, iframeStart + '//www.youtube.com/embed/$2' + iframeEnd);
51254						$(n).after(html).remove();
51255					}
51256					else if (html.match(urlVimeo))
51257					{
51258						html = html.replace(urlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
51259						$(n).after(html).remove();
51260					}
51261				}
51262
51263				// image
51264				if (convertImageLinks && html && html.match(urlImage))
51265				{
51266					html = html.replace(urlImage, '<img src="$1">');
51267
51268					$(n).after(html).remove();
51269				}
51270
51271				// link
51272				if (convertLinks && html && (html.match(url1) || html.match(url2)))
51273				{
51274					var href = (html.match(url1) || html.match(url2));
51275					href = href[0];
51276					if (href.length > linkSize) href = href.substring(0, linkSize) + '...';
51277
51278					html = html.replace(/&/g, '&amp;')
51279					.replace(/</g, '&lt;')
51280					.replace(/>/g, '&gt;')
51281					.replace(url1, '$1<a href="' + protocol + '$2">' + href + '</a>$3')
51282					.replace(url2, '$1<a href="$2">' + href + '</a>$5');
51283
51284
51285					$(n).after(html).remove();
51286				}
51287			}
51288			else if (n.nodeType === 1 && !/^(a|button|textarea)$/i.test(n.tagName))
51289			{
51290				$.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertImageLinks, convertVideoLinks, linkSize);
51291			}
51292		}
51293	};
51294
51295})(jQuery);
51296(function() {
51297  var base,
51298    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
51299    hasProp = {}.hasOwnProperty;
51300
51301  if ((base = App.Pages).Forum == null) {
51302    base.Forum = {};
51303  }
51304
51305  App.Pages.Forum.TopicsNew = (function(superClass) {
51306    extend(TopicsNew, superClass);
51307
51308    function TopicsNew() {
51309      return TopicsNew.__super__.constructor.apply(this, arguments);
51310    }
51311
51312    TopicsNew.prototype.getForm = function() {
51313      return $('#new_forum_topic');
51314    };
51315
51316    TopicsNew.prototype.initialize = function() {
51317      this.$form = this.getForm();
51318      $('#forum_topic_content').redactor({
51319        buttons: ['bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', '|', 'image', 'link'],
51320        minHeight: 300,
51321        imageUpload: "/forum/topics/upload_image?authenticity_token=" + ($('meta[name="csrf-token"]').attr('content')),
51322        imageUploadErrorCallback: function(e, json) {
51323          return alert(json.error);
51324        }
51325      });
51326      $('#forum_topic_tags').tagsInput({
51327        delimiter: ' ',
51328        width: '100%',
51329        height: 'auto'
51330      });
51331      return this.initValidation();
51332    };
51333
51334    TopicsNew.prototype.validationSettings = function() {
51335      return {
51336        ignore: [],
51337        rules: {
51338          'forum_topic[board_id]': {
51339            required: true
51340          },
51341          'forum_topic[title]': {
51342            required: true,
51343            onlyLatinSymbols: true,
51344            noEmoji: true
51345          },
51346          'forum_topic[content]': {
51347            required: true,
51348            noEmoji: true
51349          }
51350        },
51351        errorPlacement: function(error, element) {
51352          switch (element.attr('name')) {
51353            case 'forum_topic[board_id]':
51354              return error.insertAfter('#forum_topic_board_id_chosen');
51355            case 'forum_topic[content]':
51356              return error.insertAfter('.redactor_box');
51357            default:
51358              return error.insertAfter(element);
51359          }
51360        }
51361      };
51362    };
51363
51364    TopicsNew.prototype.initValidation = function() {
51365      return this.$form.validate(this.validationSettings());
51366    };
51367
51368    return TopicsNew;
51369
51370  })(App.Core.Page);
51371
51372}).call(this);
51373(function() {
51374  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
51375    hasProp = {}.hasOwnProperty;
51376
51377  App.Pages.Forum.TopicsEdit = (function(superClass) {
51378    extend(TopicsEdit, superClass);
51379
51380    function TopicsEdit() {
51381      return TopicsEdit.__super__.constructor.apply(this, arguments);
51382    }
51383
51384    TopicsEdit.prototype.getForm = function() {
51385      return $('.edit_forum_topic');
51386    };
51387
51388    return TopicsEdit;
51389
51390  })(App.Pages.Forum.TopicsNew);
51391
51392}).call(this);
51393(function() {
51394  var base,
51395    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
51396    hasProp = {}.hasOwnProperty;
51397
51398  if ((base = App.Pages).Forum == null) {
51399    base.Forum = {};
51400  }
51401
51402  App.Pages.Forum.TopicsShow = (function(superClass) {
51403    extend(TopicsShow, superClass);
51404
51405    function TopicsShow() {
51406      return TopicsShow.__super__.constructor.apply(this, arguments);
51407    }
51408
51409    TopicsShow.prototype.initialize = function() {
51410      this.$newForumPostForm = $('#new_forum_post');
51411      this.initComments();
51412      this.bindEvents();
51413      return this.initValidation();
51414    };
51415
51416    TopicsShow.prototype.initComments = function() {
51417      return $('.comments').each(function() {
51418        var widget;
51419        widget = new App.Widgets.Comments({
51420          el: $(this),
51421          placement: 'append'
51422        });
51423        widget.initialize();
51424        return $(this).find('.comment-form-container').addClass('is-hidden');
51425      });
51426    };
51427
51428    TopicsShow.prototype.bindEvents = function() {
51429      $('.js-delete-answer').each((function(_this) {
51430        return function(index, el) {
51431          return $(el).on('click', _this.onDeleteClick);
51432        };
51433      })(this));
51434      $('.js-reply-to-answer').each((function(_this) {
51435        return function(index, el) {
51436          return $(el).on('click', _this.onReplyClick);
51437        };
51438      })(this));
51439      $('.js-reply-to-topic').on('click', this.onReplyToTopicClick);
51440      this.$newForumPostForm.submit(function() {
51441        if ($('#new_forum_post').valid()) {
51442          $('.js-disable-answer-button').attr('disabled', true);
51443        }
51444        return true;
51445      });
51446      $('.vote-block a').on('ajax:before', this.onVoteBefore);
51447      return $('.vote-block a').on('ajax:success', this.onVoteSuccess);
51448    };
51449
51450    TopicsShow.prototype.onDeleteClick = function(e) {
51451      var $el;
51452      $el = $(e.currentTarget).parents('.forum-answer');
51453      return $.ajax({
51454        url: "/forum/posts/" + $el[0].id,
51455        type: 'DELETE',
51456        success: function() {
51457          return $el.remove();
51458        }
51459      });
51460    };
51461
51462    TopicsShow.prototype.onReplyClick = function(e) {
51463      var $el, $form;
51464      $el = $(e.currentTarget).parents('.forum-answer').find('.comments');
51465      $form = $el.find('.comment-form-container');
51466      $form.removeClass('is-hidden');
51467      return $('body, html').stop(true, true).animate({
51468        scrollTop: $form.offset().top + $form.height() - $(window).height()
51469      }, 300);
51470    };
51471
51472    TopicsShow.prototype.onReplyToTopicClick = function(e) {
51473      return App.events.publish('app:element:focus', $('#forum-answer-form'));
51474    };
51475
51476    TopicsShow.prototype.onVoteBefore = function(e) {
51477      var $block, $this;
51478      $this = $(e.currentTarget);
51479      if ($this.data('disabled')) {
51480        return false;
51481      }
51482      $block = $this.parents('.vote-block');
51483      return $block.find('.vote-block__action a').data('disabled', true);
51484    };
51485
51486    TopicsShow.prototype.onVoteSuccess = function(e) {
51487      var $block, $this, score, value;
51488      $this = $(e.currentTarget);
51489      $this.parent().addClass('is-active');
51490      $block = $this.parents('.vote-block');
51491      value = parseInt($this.data('value'), 10);
51492      score = parseInt($block.find('.vote-block__score').text(), 10);
51493      $block.addClass('is-voted');
51494      $block.find('.vote-block__action a').data('disabled', true);
51495      return $block.find('.vote-block__score').text(score + value);
51496    };
51497
51498    TopicsShow.prototype.validationSettings = function() {
51499      return {
51500        ignore: [],
51501        rules: {
51502          'forum_post[content]': {
51503            noEmoji: true
51504          }
51505        }
51506      };
51507    };
51508
51509    TopicsShow.prototype.initValidation = function() {
51510      return this.$newForumPostForm.validate(this.validationSettings());
51511    };
51512
51513    return TopicsShow;
51514
51515  })(App.Core.Page);
51516
51517}).call(this);
51518/*!
51519 * Masonry PACKAGED v3.1.5
51520 * Cascading grid layout library
51521 * http://masonry.desandro.com
51522 * MIT License
51523 * by David DeSandro
51524 */
51525
51526/**
51527 * Bridget makes jQuery widgets
51528 * v1.0.1
51529 */
51530
51531
51532( function( window ) {
51533
51534
51535
51536// -------------------------- utils -------------------------- //
51537
51538var slice = Array.prototype.slice;
51539
51540function noop() {}
51541
51542// -------------------------- definition -------------------------- //
51543
51544function defineBridget( $ ) {
51545
51546// bail if no jQuery
51547if ( !$ ) {
51548  return;
51549}
51550
51551// -------------------------- addOptionMethod -------------------------- //
51552
51553/**
51554 * adds option method -> $().plugin('option', {...})
51555 * @param {Function} PluginClass - constructor class
51556 */
51557function addOptionMethod( PluginClass ) {
51558  // don't overwrite original option method
51559  if ( PluginClass.prototype.option ) {
51560    return;
51561  }
51562
51563  // option setter
51564  PluginClass.prototype.option = function( opts ) {
51565    // bail out if not an object
51566    if ( !$.isPlainObject( opts ) ){
51567      return;
51568    }
51569    this.options = $.extend( true, this.options, opts );
51570  };
51571}
51572
51573
51574// -------------------------- plugin bridge -------------------------- //
51575
51576// helper function for logging errors
51577// $.error breaks jQuery chaining
51578var logError = typeof console === 'undefined' ? noop :
51579  function( message ) {
51580    console.error( message );
51581  };
51582
51583/**
51584 * jQuery plugin bridge, access methods like $elem.plugin('method')
51585 * @param {String} namespace - plugin name
51586 * @param {Function} PluginClass - constructor class
51587 */
51588function bridge( namespace, PluginClass ) {
51589  // add to jQuery fn namespace
51590  $.fn[ namespace ] = function( options ) {
51591    if ( typeof options === 'string' ) {
51592      // call plugin method when first argument is a string
51593      // get arguments for method
51594      var args = slice.call( arguments, 1 );
51595
51596      for ( var i=0, len = this.length; i < len; i++ ) {
51597        var elem = this[i];
51598        var instance = $.data( elem, namespace );
51599        if ( !instance ) {
51600          logError( "cannot call methods on " + namespace + " prior to initialization; " +
51601            "attempted to call '" + options + "'" );
51602          continue;
51603        }
51604        if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) {
51605          logError( "no such method '" + options + "' for " + namespace + " instance" );
51606          continue;
51607        }
51608
51609        // trigger method with arguments
51610        var returnValue = instance[ options ].apply( instance, args );
51611
51612        // break look and return first value if provided
51613        if ( returnValue !== undefined ) {
51614          return returnValue;
51615        }
51616      }
51617      // return this if no return value
51618      return this;
51619    } else {
51620      return this.each( function() {
51621        var instance = $.data( this, namespace );
51622        if ( instance ) {
51623          // apply options & init
51624          instance.option( options );
51625          instance._init();
51626        } else {
51627          // initialize new instance
51628          instance = new PluginClass( this, options );
51629          $.data( this, namespace, instance );
51630        }
51631      });
51632    }
51633  };
51634
51635}
51636
51637// -------------------------- bridget -------------------------- //
51638
51639/**
51640 * converts a Prototypical class into a proper jQuery plugin
51641 *   the class must have a ._init method
51642 * @param {String} namespace - plugin name, used in $().pluginName
51643 * @param {Function} PluginClass - constructor class
51644 */
51645$.bridget = function( namespace, PluginClass ) {
51646  addOptionMethod( PluginClass );
51647  bridge( namespace, PluginClass );
51648};
51649
51650return $.bridget;
51651
51652}
51653
51654// transport
51655if ( typeof define === 'function' && define.amd ) {
51656  // AMD
51657  define( 'jquery-bridget/jquery.bridget',[ 'jquery' ], defineBridget );
51658} else {
51659  // get jquery from browser global
51660  defineBridget( window.jQuery );
51661}
51662
51663})( window );
51664
51665/*!
51666 * eventie v1.0.5
51667 * event binding helper
51668 *   eventie.bind( elem, 'click', myFn )
51669 *   eventie.unbind( elem, 'click', myFn )
51670 * MIT license
51671 */
51672
51673/*jshint browser: true, undef: true, unused: true */
51674/*global define: false, module: false */
51675
51676( function( window ) {
51677
51678
51679
51680var docElem = document.documentElement;
51681
51682var bind = function() {};
51683
51684function getIEEvent( obj ) {
51685  var event = window.event;
51686  // add event.target
51687  event.target = event.target || event.srcElement || obj;
51688  return event;
51689}
51690
51691if ( docElem.addEventListener ) {
51692  bind = function( obj, type, fn ) {
51693    obj.addEventListener( type, fn, false );
51694  };
51695} else if ( docElem.attachEvent ) {
51696  bind = function( obj, type, fn ) {
51697    obj[ type + fn ] = fn.handleEvent ?
51698      function() {
51699        var event = getIEEvent( obj );
51700        fn.handleEvent.call( fn, event );
51701      } :
51702      function() {
51703        var event = getIEEvent( obj );
51704        fn.call( obj, event );
51705      };
51706    obj.attachEvent( "on" + type, obj[ type + fn ] );
51707  };
51708}
51709
51710var unbind = function() {};
51711
51712if ( docElem.removeEventListener ) {
51713  unbind = function( obj, type, fn ) {
51714    obj.removeEventListener( type, fn, false );
51715  };
51716} else if ( docElem.detachEvent ) {
51717  unbind = function( obj, type, fn ) {
51718    obj.detachEvent( "on" + type, obj[ type + fn ] );
51719    try {
51720      delete obj[ type + fn ];
51721    } catch ( err ) {
51722      // can't delete window object properties
51723      obj[ type + fn ] = undefined;
51724    }
51725  };
51726}
51727
51728var eventie = {
51729  bind: bind,
51730  unbind: unbind
51731};
51732
51733// ----- module definition ----- //
51734
51735if ( typeof define === 'function' && define.amd ) {
51736  // AMD
51737  define( 'eventie/eventie',eventie );
51738} else if ( typeof exports === 'object' ) {
51739  // CommonJS
51740  module.exports = eventie;
51741} else {
51742  // browser global
51743  window.eventie = eventie;
51744}
51745
51746})( this );
51747
51748/*!
51749 * docReady
51750 * Cross browser DOMContentLoaded event emitter
51751 */
51752
51753/*jshint browser: true, strict: true, undef: true, unused: true*/
51754/*global define: false */
51755
51756( function( window ) {
51757
51758
51759
51760var document = window.document;
51761// collection of functions to be triggered on ready
51762var queue = [];
51763
51764function docReady( fn ) {
51765  // throw out non-functions
51766  if ( typeof fn !== 'function' ) {
51767    return;
51768  }
51769
51770  if ( docReady.isReady ) {
51771    // ready now, hit it
51772    fn();
51773  } else {
51774    // queue function when ready
51775    queue.push( fn );
51776  }
51777}
51778
51779docReady.isReady = false;
51780
51781// triggered on various doc ready events
51782function init( event ) {
51783  // bail if IE8 document is not ready just yet
51784  var isIE8NotReady = event.type === 'readystatechange' && document.readyState !== 'complete';
51785  if ( docReady.isReady || isIE8NotReady ) {
51786    return;
51787  }
51788  docReady.isReady = true;
51789
51790  // process queue
51791  for ( var i=0, len = queue.length; i < len; i++ ) {
51792    var fn = queue[i];
51793    fn();
51794  }
51795}
51796
51797function defineDocReady( eventie ) {
51798  eventie.bind( document, 'DOMContentLoaded', init );
51799  eventie.bind( document, 'readystatechange', init );
51800  eventie.bind( window, 'load', init );
51801
51802  return docReady;
51803}
51804
51805// transport
51806if ( typeof define === 'function' && define.amd ) {
51807  // AMD
51808  // if RequireJS, then doc is already ready
51809  docReady.isReady = typeof requirejs === 'function';
51810  define( 'doc-ready/doc-ready',[ 'eventie/eventie' ], defineDocReady );
51811} else {
51812  // browser global
51813  window.docReady = defineDocReady( window.eventie );
51814}
51815
51816})( this );
51817
51818/*!
51819 * EventEmitter v4.2.7 - git.io/ee
51820 * Oliver Caldwell
51821 * MIT license
51822 * @preserve
51823 */
51824
51825(function () {
51826	
51827
51828	/**
51829	 * Class for managing events.
51830	 * Can be extended to provide event functionality in other classes.
51831	 *
51832	 * @class EventEmitter Manages event registering and emitting.
51833	 */
51834	function EventEmitter() {}
51835
51836	// Shortcuts to improve speed and size
51837	var proto = EventEmitter.prototype;
51838	var exports = this;
51839	var originalGlobalValue = exports.EventEmitter;
51840
51841	/**
51842	 * Finds the index of the listener for the event in it's storage array.
51843	 *
51844	 * @param {Function[]} listeners Array of listeners to search through.
51845	 * @param {Function} listener Method to look for.
51846	 * @return {Number} Index of the specified listener, -1 if not found
51847	 * @api private
51848	 */
51849	function indexOfListener(listeners, listener) {
51850		var i = listeners.length;
51851		while (i--) {
51852			if (listeners[i].listener === listener) {
51853				return i;
51854			}
51855		}
51856
51857		return -1;
51858	}
51859
51860	/**
51861	 * Alias a method while keeping the context correct, to allow for overwriting of target method.
51862	 *
51863	 * @param {String} name The name of the target method.
51864	 * @return {Function} The aliased method
51865	 * @api private
51866	 */
51867	function alias(name) {
51868		return function aliasClosure() {
51869			return this[name].apply(this, arguments);
51870		};
51871	}
51872
51873	/**
51874	 * Returns the listener array for the specified event.
51875	 * Will initialise the event object and listener arrays if required.
51876	 * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object c
51876ontaining bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
51877	 * Each property in the object response is an array of listener functions.
51878	 *
51879	 * @param {String|RegExp} evt Name of the event to return the listeners from.
51880	 * @return {Function[]|Object} All listener functions for the event.
51881	 */
51882	proto.getListeners = function getListeners(evt) {
51883		var events = this._getEvents();
51884		var response;
51885		var key;
51886
51887		// Return a concatenated array of all matching events if
51888		// the selector is a regular expression.
51889		if (evt instanceof RegExp) {
51890			response = {};
51891			for (key in events) {
51892				if (events.hasOwnProperty(key) && evt.test(key)) {
51893					response[key] = events[key];
51894				}
51895			}
51896		}
51897		else {
51898			response = events[evt] || (events[evt] = []);
51899		}
51900
51901		return response;
51902	};
51903
51904	/**
51905	 * Takes a list of listener objects and flattens it into a list of listener functions.
51906	 *
51907	 * @param {Object[]} listeners Raw listener objects.
51908	 * @return {Function[]} Just the listener functions.
51909	 */
51910	proto.flattenListeners = function flattenListeners(listeners) {
51911		var flatListeners = [];
51912		var i;
51913
51914		for (i = 0; i < listeners.length; i += 1) {
51915			flatListeners.push(listeners[i].listener);
51916		}
51917
51918		return flatListeners;
51919	};
51920
51921	/**
51922	 * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
51923	 *
51924	 * @param {String|RegExp} evt Name of the event to return the listeners from.
51925	 * @return {Object} All listener functions for an event in an object.
51926	 */
51927	proto.getListenersAsObject = function getListenersAsObject(evt) {
51928		var listeners = this.getListeners(evt);
51929		var response;
51930
51931		if (listeners instanceof Array) {
51932			response = {};
51933			response[evt] = listeners;
51934		}
51935
51936		return response || listeners;
51937	};
51938
51939	/**
51940	 * Adds a listener function to the specified event.
51941	 * The listener will not be added if it is a duplicate.
51942	 * If the listener returns true then it will be removed after it is called.
51943	 * If you pass a regular expression as the event name then the listener will be added to all events that match it.
51944	 *
51945	 * @param {String|RegExp} evt Name of the event to attach the listener to.
51946	 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
51947	 * @return {Object} Current instance of EventEmitter for chaining.
51948	 */
51949	proto.addListener = function addListener(evt, listener) {
51950		var listeners = this.getListenersAsObject(evt);
51951		var listenerIsWrapped = typeof listener === 'object';
51952		var key;
51953
51954		for (key in listeners) {
51955			if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
51956				listeners[key].push(listenerIsWrapped ? listener : {
51957					listener: listener,
51958					once: false
51959				});
51960			}
51961		}
51962
51963		return this;
51964	};
51965
51966	/**
51967	 * Alias of addListener
51968	 */
51969	proto.on = alias('addListener');
51970
51971	/**
51972	 * Semi-alias of addListener. It will add a listener that will be
51973	 * automatically removed after it's first execution.
51974	 *
51975	 * @param {String|RegExp} evt Name of the event to attach the listener to.
51976	 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
51977	 * @return {Object} Current instance of EventEmitter for chaining.
51978	 */
51979	proto.addOnceListener = function addOnceListener(evt, listener) {
51980		return this.addListener(evt, {
51981			listener: listener,
51982			once: true
51983		});
51984	};
51985
51986	/**
51987	 * Alias of addOnceListener.
51988	 */
51989	proto.once = alias('addOnceListener');
51990
51991	/**
51992	 * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
51993	 * You need to tell it what event names should be matched by a regex.
51994	 *
51995	 * @param {String} evt Name of the event to create.
51996	 * @return {Object} Current instance of EventEmitter for chaining.
51997	 */
51998	proto.defineEvent = function defineEvent(evt) {
51999		this.getListeners(evt);
52000		return this;
52001	};
52002
52003	/**
52004	 * Uses defineEvent to define multiple events.
52005	 *
52006	 * @param {String[]} evts An array of event names to define.
52007	 * @return {Object} Current instance of EventEmitter for chaining.
52008	 */
52009	proto.defineEvents = function defineEvents(evts) {
52010		for (var i = 0; i < evts.length; i += 1) {
52011			this.defineEvent(evts[i]);
52012		}
52013		return this;
52014	};
52015
52016	/**
52017	 * Removes a listener function from the specified event.
52018	 * When passed a regular expression as the event name, it will remove the listener from all events that match it.
52019	 *
52020	 * @param {String|RegExp} evt Name of the event to remove the listener from.
52021	 * @param {Function} listener Method to remove from the event.
52022	 * @return {Object} Current instance of EventEmitter for chaining.
52023	 */
52024	proto.removeListener = function removeListener(evt, listener) {
52025		var listeners = this.getListenersAsObject(evt);
52026		var index;
52027		var key;
52028
52029		for (key in listeners) {
52030			if (listeners.hasOwnProperty(key)) {
52031				index = indexOfListener(listeners[key], listener);
52032
52033				if (index !== -1) {
52034					listeners[key].splice(index, 1);
52035				}
52036			}
52037		}
52038
52039		return this;
52040	};
52041
52042	/**
52043	 * Alias of removeListener
52044	 */
52045	proto.off = alias('removeListener');
52046
52047	/**
52048	 * Adds listeners in bulk using the manipulateListeners method.
52049	 * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
52050	 * You can also pass it a regular expression to add the array of listeners to all events that match it.
52051	 * Yeah, this function does quite a bit. That's probably a bad thing.
52052	 *
52053	 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
52054	 * @param {Function[]} [listeners] An optional array of listener functions to add.
52055	 * @return {Object} Current instance of EventEmitter for chaining.
52056	 */
52057	proto.addListeners = function addListeners(evt, listeners) {
52058		// Pass through to manipulateListeners
52059		return this.manipulateListeners(false, evt, listeners);
52060	};
52061
52062	/**
52063	 * Removes listeners in bulk using the manipulateListeners method.
52064	 * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
52065	 * You can also pass it an event name and an array of listeners to be removed.
52066	 * You can also pass it a regular expression to remove the listeners from all events that match it.
52067	 *
52068	 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
52069	 * @param {Function[]} [listeners] An optional array of listener functions to remove.
52070	 * @return {Object} Current instance of EventEmitter for chaining.
52071	 */
52072	proto.removeListeners = function removeListeners(evt, listeners) {
52073		// Pass through to manipulateListeners
52074		return this.manipulateListeners(true, evt, listeners);
52075	};
52076
52077	/**
52078	 * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
52079	 * The first argument will determine if the listeners are removed (true) or added (false).
52080	 * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
52081	 * You can also pass it an event name and an array of listeners to be added/removed.
52082	 * You can also pass it a regular expression to manipulate the listeners of all events that match it.
52083	 *
52084	 * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
52085	 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
52086	 * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
52087	 * @return {Object} Current instance of EventEmitter for chaining.
52088	 */
52089	proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
52090		var i;
52091		var value;
52092		var single = remove ? this.removeListener : this.addListener;
52093		var multiple = remove ? this.removeListeners : this.addListeners;
52094
52095		// If evt is an object then pass each of it's properties to this method
52096		if (typeof evt === 'object' && !(evt instanceof RegExp)) {
52097			for (i in evt) {
52098				if (evt.hasOwnProperty(i) && (value = evt[i])) {
52099					// Pass the single listener straight through to the singular method
52100					if (typeof value === 'function') {
52101						single.call(this, i, value);
52102					}
52103					else {
52104						// Otherwise pass back to the multiple function
52105						multiple.call(this, i, value);
52106					}
52107				}
52108			}
52109		}
52110		else {
52111			// So evt must be a string
52112			// And listeners must be an array of listeners
52113			// Loop over it and pass each one to the multiple method
52114			i = listeners.length;
52115			while (i--) {
52116				single.call(this, evt, listeners[i]);
52117			}
52118		}
52119
52120		return this;
52121	};
52122
52123	/**
52124	 * Removes all listeners from a specified event.
52125	 * If you do not specify an event then all listeners will be removed.
52126	 * That means every event will be emptied.
52127	 * You can also pass a regex to remove all events that match it.
52128	 *
52129	 * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
52130	 * @return {Object} Current instance of EventEmitter for chaining.
52131	 */
52132	proto.removeEvent = function removeEvent(evt) {
52133		var type = typeof evt;
52134		var events = this._getEvents();
52135		var key;
52136
52137		// Remove different things depending on the state of evt
52138		if (type === 'string') {
52139			// Remove all listeners for the specified event
52140			delete events[evt];
52141		}
52142		else if (evt instanceof RegExp) {
52143			// Remove all events matching the regex.
52144			for (key in events) {
52145				if (events.hasOwnProperty(key) && evt.test(key)) {
52146					delete events[key];
52147				}
52148			}
52149		}
52150		else {
52151			// Remove all listeners in all events
52152			delete this._events;
52153		}
52154
52155		return this;
52156	};
52157
52158	/**
52159	 * Alias of removeEvent.
52160	 *
52161	 * Added to mirror the node API.
52162	 */
52163	proto.removeAllListeners = alias('removeEvent');
52164
52165	/**
52166	 * Emits an event of your choice.
52167	 * When emitted, every listener attached to that event will be executed.
52168	 * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
52169	 * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
52170	 * So they will not arrive within the array on the other side, they will be separate.
52171	 * You can also pass a regular expression to emit to all events that match it.
52172	 *
52173	 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
52174	 * @param {Array} [args] Optional array of arguments to be passed to each listener.
52175	 * @return {Object} Current instance of EventEmitter for chaining.
52176	 */
52177	proto.emitEvent = function emitEvent(evt, args) {
52178		var listeners = this.getListenersAsObject(evt);
52179		var listener;
52180		var i;
52181		var key;
52182		var response;
52183
52184		for (key in listeners) {
52185			if (listeners.hasOwnProperty(key)) {
52186				i = listeners[key].length;
52187
52188				while (i--) {
52189					// If the listener returns true then it shall be removed from the event
52190					// The function is executed either with a basic call or an apply if there is an args array
52191					listener = listeners[key][i];
52192
52193					if (listener.once === true) {
52194						this.removeListener(evt, listener.listener);
52195					}
52196
52197					response = listener.listener.apply(this, args || []);
52198
52199					if (response === this._getOnceReturnValue()) {
52200						this.removeListener(evt, listener.listener);
52201					}
52202				}
52203			}
52204		}
52205
52206		return this;
52207	};
52208
52209	/**
52210	 * Alias of emitEvent
52211	 */
52212	proto.trigger = alias('emitEvent');
52213
52214	/**
52215	 * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
52216	 * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
52217	 *
52218	 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
52219	 * @param {...*} Optional additional arguments to be passed to each listener.
52220	 * @return {Object} Current instance of EventEmitter for chaining.
52221	 */
52222	proto.emit = function emit(evt) {
52223		var args = Array.prototype.slice.call(arguments, 1);
52224		return this.emitEvent(evt, args);
52225	};
52226
52227	/**
52228	 * Sets the current value to check against when executing listeners. If a
52229	 * listeners return value matches the one set here then it will be removed
52230	 * after execution. This value defaults to true.
52231	 *
52232	 * @param {*} value The new value to check for when executing listeners.
52233	 * @return {Object} Current instance of EventEmitter for chaining.
52234	 */
52235	proto.setOnceReturnValue = function setOnceReturnValue(value) {
52236		this._onceReturnValue = value;
52237		return this;
52238	};
52239
52240	/**
52241	 * Fetches the current value to check against when executing listeners. If
52242	 * the listeners return value matches this one then it should be removed
52243	 * automatically. It will return true by default.
52244	 *
52245	 * @return {*|Boolean} The current value to check for or the default, true.
52246	 * @api private
52247	 */
52248	proto._getOnceReturnValue = function _getOnceReturnValue() {
52249		if (this.hasOwnProperty('_onceReturnValue')) {
52250			return this._onceReturnValue;
52251		}
52252		else {
52253			return true;
52254		}
52255	};
52256
52257	/**
52258	 * Fetches the events object and creates one if required.
52259	 *
52260	 * @return {Object} The events storage object.
52261	 * @api private
52262	 */
52263	proto._getEvents = function _getEvents() {
52264		return this._events || (this._events = {});
52265	};
52266
52267	/**
52268	 * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
52269	 *
52270	 * @return {Function} Non conflicting EventEmitter class.
52271	 */
52272	EventEmitter.noConflict = function noConflict() {
52273		exports.EventEmitter = originalGlobalValue;
52274		return EventEmitter;
52275	};
52276
52277	// Expose the class either via AMD, CommonJS or the global object
52278	if (typeof define === 'function' && define.amd) {
52279		define('eventEmitter/EventEmitter',[],function () {
52280			return EventEmitter;
52281		});
52282	}
52283	else if (typeof module === 'object' && module.exports){
52284		module.exports = EventEmitter;
52285	}
52286	else {
52287		this.EventEmitter = EventEmitter;
52288	}
52289}.call(this));
52290
52291/*!
52292 * getStyleProperty v1.0.3
52293 * original by kangax
52294 * http://perfectionkills.com/feature-testing-css-properties/
52295 */
52296
52297/*jshint browser: true, strict: true, undef: true */
52298/*global define: false, exports: false, module: false */
52299
52300( function( window ) {
52301
52302
52303
52304var prefixes = 'Webkit Moz ms Ms O'.split(' ');
52305var docElemStyle = document.documentElement.style;
52306
52307function getStyleProperty( propName ) {
52308  if ( !propName ) {
52309    return;
52310  }
52311
52312  // test standard property first
52313  if ( typeof docElemStyle[ propName ] === 'string' ) {
52314    return propName;
52315  }
52316
52317  // capitalize
52318  propName = propName.charAt(0).toUpperCase() + propName.slice(1);
52319
52320  // test vendor specific properties
52321  var prefixed;
52322  for ( var i=0, len = prefixes.length; i < len; i++ ) {
52323    prefixed = prefixes[i] + propName;
52324    if ( typeof docElemStyle[ prefixed ] === 'string' ) {
52325      return prefixed;
52326    }
52327  }
52328}
52329
52330// transport
52331if ( typeof define === 'function' && define.amd ) {
52332  // AMD
52333  define( 'get-style-property/get-style-property',[],function() {
52334    return getStyleProperty;
52335  });
52336} else if ( typeof exports === 'object' ) {
52337  // CommonJS for Component
52338  module.exports = getStyleProperty;
52339} else {
52340  // browser global
52341  window.getStyleProperty = getStyleProperty;
52342}
52343
52344})( window );
52345
52346/**
52347 * getSize v1.1.7
52348 * measure size of elements
52349 */
52350
52351/*jshint browser: true, strict: true, undef: true, unused: true */
52352/*global define: false, exports: false, require: false, module: false */
52353
52354( function( window, undefined ) {
52355
52356
52357
52358// -------------------------- helpers -------------------------- //
52359
52360var getComputedStyle = window.getComputedStyle;
52361var getStyle = getComputedStyle ?
52362  function( elem ) {
52363    return getComputedStyle( elem, null );
52364  } :
52365  function( elem ) {
52366    return elem.currentStyle;
52367  };
52368
52369// get a number from a string, not a percentage
52370function getStyleSize( value ) {
52371  var num = parseFloat( value );
52372  // not a percent like '100%', and a number
52373  var isValid = value.indexOf('%') === -1 && !isNaN( num );
52374  return isValid && num;
52375}
52376
52377// -------------------------- measurements -------------------------- //
52378
52379var measurements = [
52380  'paddingLeft',
52381  'paddingRight',
52382  'paddingTop',
52383  'paddingBottom',
52384  'marginLeft',
52385  'marginRight',
52386  'marginTop',
52387  'marginBottom',
52388  'borderLeftWidth',
52389  'borderRightWidth',
52390  'borderTopWidth',
52391  'borderBottomWidth'
52392];
52393
52394function getZeroSize() {
52395  var size = {
52396    width: 0,
52397    height: 0,
52398    innerWidth: 0,
52399    innerHeight: 0,
52400    outerWidth: 0,
52401    outerHeight: 0
52402  };
52403  for ( var i=0, len = measurements.length; i < len; i++ ) {
52404    var measurement = measurements[i];
52405    size[ measurement ] = 0;
52406  }
52407  return size;
52408}
52409
52410
52411
52412function defineGetSize( getStyleProperty ) {
52413
52414// -------------------------- box sizing -------------------------- //
52415
52416var boxSizingProp = getStyleProperty('boxSizing');
52417var isBoxSizeOuter;
52418
52419/**
52420 * WebKit measures the outer-width on style.width on border-box elems
52421 * IE & Firefox measures the inner-width
52422 */
52423( function() {
52424  if ( !boxSizingProp ) {
52425    return;
52426  }
52427
52428  var div = document.createElement('div');
52429  div.style.width = '200px';
52430  div.style.padding = '1px 2px 3px 4px';
52431  div.style.borderStyle = 'solid';
52432  div.style.borderWidth = '1px 2px 3px 4px';
52433  div.style[ boxSizingProp ] = 'border-box';
52434
52435  var body = document.body || document.documentElement;
52436  body.appendChild( div );
52437  var style = getStyle( div );
52438
52439  isBoxSizeOuter = getStyleSize( style.width ) === 200;
52440  body.removeChild( div );
52441})();
52442
52443
52444// -------------------------- getSize -------------------------- //
52445
52446function getSize( elem ) {
52447  // use querySeletor if elem is string
52448  if ( typeof elem === 'string' ) {
52449    elem = document.querySelector( elem );
52450  }
52451
52452  // do not proceed on non-objects
52453  if ( !elem || typeof elem !== 'object' || !elem.nodeType ) {
52454    return;
52455  }
52456
52457  var style = getStyle( elem );
52458
52459  // if hidden, everything is 0
52460  if ( style.display === 'none' ) {
52461    return getZeroSize();
52462  }
52463
52464  var size = {};
52465  size.width = elem.offsetWidth;
52466  size.height = elem.offsetHeight;
52467
52468  var isBorderBox = size.isBorderBox = !!( boxSizingProp &&
52469    style[ boxSizingProp ] && style[ boxSizingProp ] === 'border-box' );
52470
52471  // get all measurements
52472  for ( var i=0, len = measurements.length; i < len; i++ ) {
52473    var measurement = measurements[i];
52474    var value = style[ measurement ];
52475    value = mungeNonPixel( elem, value );
52476    var num = parseFloat( value );
52477    // any 'auto', 'medium' value will be 0
52478    size[ measurement ] = !isNaN( num ) ? num : 0;
52479  }
52480
52481  var paddingWidth = size.paddingLeft + size.paddingRight;
52482  var paddingHeight = size.paddingTop + size.paddingBottom;
52483  var marginWidth = size.marginLeft + size.marginRight;
52484  var marginHeight = size.marginTop + size.marginBottom;
52485  var borderWidth = size.borderLeftWidth + size.borderRightWidth;
52486  var borderHeight = size.borderTopWidth + size.borderBottomWidth;
52487
52488  var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;
52489
52490  // overwrite width and height if we can get it from style
52491  var styleWidth = getStyleSize( style.width );
52492  if ( styleWidth !== false ) {
52493    size.width = styleWidth +
52494      // add padding and border unless it's already including it
52495      ( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth );
52496  }
52497
52498  var styleHeight = getStyleSize( style.height );
52499  if ( styleHeight !== false ) {
52500    size.height = styleHeight +
52501      // add padding and border unless it's already including it
52502      ( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight );
52503  }
52504
52505  size.innerWidth = size.width - ( paddingWidth + borderWidth );
52506  size.innerHeight = size.height - ( paddingHeight + borderHeight );
52507
52508  size.outerWidth = size.width + marginWidth;
52509  size.outerHeight = size.height + marginHeight;
52510
52511  return size;
52512}
52513
52514// IE8 returns percent values, not pixels
52515// taken from jQuery's curCSS
52516function mungeNonPixel( elem, value ) {
52517  // IE8 and has percent value
52518  if ( getComputedStyle || value.indexOf('%') === -1 ) {
52519    return value;
52520  }
52521  var style = elem.style;
52522  // Remember the original values
52523  var left = style.left;
52524  var rs = elem.runtimeStyle;
52525  var rsLeft = rs && rs.left;
52526
52527  // Put in the new values to get a computed value out
52528  if ( rsLeft ) {
52529    rs.left = elem.currentStyle.left;
52530  }
52531  style.left = value;
52532  value = style.pixelLeft;
52533
52534  // Revert the changed values
52535  style.left = left;
52536  if ( rsLeft ) {
52537    rs.left = rsLeft;
52538  }
52539
52540  return value;
52541}
52542
52543return getSize;
52544
52545}
52546
52547// transport
52548if ( typeof define === 'function' && define.amd ) {
52549  // AMD for RequireJS
52550  define( 'get-size/get-size',[ 'get-style-property/get-style-property' ], defineGetSize );
52551} else if ( typeof exports === 'object' ) {
52552  // CommonJS for Component
52553  module.exports = defineGetSize( require('get-style-property') );
52554} else {
52555  // browser global
52556  window.getSize = defineGetSize( window.getStyleProperty );
52557}
52558
52559})( window );
52560
52561/**
52562 * matchesSelector helper v1.0.1
52563 *
52564 * @name matchesSelector
52565 *   @param {Element} elem
52566 *   @param {String} selector
52567 */
52568
52569/*jshint browser: true, strict: true, undef: true, unused: true */
52570/*global define: false */
52571
52572( function( global, ElemProto ) {
52573
52574  
52575
52576  var matchesMethod = ( function() {
52577    // check un-prefixed
52578    if ( ElemProto.matchesSelector ) {
52579      return 'matchesSelector';
52580    }
52581    // check vendor prefixes
52582    var prefixes = [ 'webkit', 'moz', 'ms', 'o' ];
52583
52584    for ( var i=0, len = prefixes.length; i < len; i++ ) {
52585      var prefix = prefixes[i];
52586      var method = prefix + 'MatchesSelector';
52587      if ( ElemProto[ method ] ) {
52588        return method;
52589      }
52590    }
52591  })();
52592
52593  // ----- match ----- //
52594
52595  function match( elem, selector ) {
52596    return elem[ matchesMethod ]( selector );
52597  }
52598
52599  // ----- appendToFragment ----- //
52600
52601  function checkParent( elem ) {
52602    // not needed if already has parent
52603    if ( elem.parentNode ) {
52604      return;
52605    }
52606    var fragment = document.createDocumentFragment();
52607    fragment.appendChild( elem );
52608  }
52609
52610  // ----- query ----- //
52611
52612  // fall back to using QSA
52613  // thx @jonathantneal https://gist.github.com/3062955
52614  function query( elem, selector ) {
52615    // append to fragment if no parent
52616    checkParent( elem );
52617
52618    // match elem with all selected elems of parent
52619    var elems = elem.parentNode.querySelectorAll( selector );
52620    for ( var i=0, len = elems.length; i < len; i++ ) {
52621      // return true if match
52622      if ( elems[i] === elem ) {
52623        return true;
52624      }
52625    }
52626    // otherwise return false
52627    return false;
52628  }
52629
52630  // ----- matchChild ----- //
52631
52632  function matchChild( elem, selector ) {
52633    checkParent( elem );
52634    return match( elem, selector );
52635  }
52636
52637  // ----- matchesSelector ----- //
52638
52639  var matchesSelector;
52640
52641  if ( matchesMethod ) {
52642    // IE9 supports matchesSelector, but doesn't work on orphaned elems
52643    // check for that
52644    var div = document.createElement('div');
52645    var supportsOrphans = match( div, 'div' );
52646    matchesSelector = supportsOrphans ? match : matchChild;
52647  } else {
52648    matchesSelector = query;
52649  }
52650
52651  // transport
52652  if ( typeof define === 'function' && define.amd ) {
52653    // AMD
52654    define( 'matches-selector/matches-selector',[],function() {
52655      return matchesSelector;
52656    });
52657  } else {
52658    // browser global
52659    window.matchesSelector = matchesSelector;
52660  }
52661
52662})( this, Element.prototype );
52663
52664/**
52665 * Outlayer Item
52666 */
52667
52668( function( window ) {
52669
52670
52671
52672// ----- get style ----- //
52673
52674var getComputedStyle = window.getComputedStyle;
52675var getStyle = getComputedStyle ?
52676  function( elem ) {
52677    return getComputedStyle( elem, null );
52678  } :
52679  function( elem ) {
52680    return elem.currentStyle;
52681  };
52682
52683
52684// extend objects
52685function extend( a, b ) {
52686  for ( var prop in b ) {
52687    a[ prop ] = b[ prop ];
52688  }
52689  return a;
52690}
52691
52692function isEmptyObj( obj ) {
52693  for ( var prop in obj ) {
52694    return false;
52695  }
52696  prop = null;
52697  return true;
52698}
52699
52700// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
52701function toDash( str ) {
52702  return str.replace( /([A-Z])/g, function( $1 ){
52703    return '-' + $1.toLowerCase();
52704  });
52705}
52706
52707// -------------------------- Outlayer definition -------------------------- //
52708
52709function outlayerItemDefinition( EventEmitter, getSize, getStyleProperty ) {
52710
52711// -------------------------- CSS3 support -------------------------- //
52712
52713var transitionProperty = getStyleProperty('transition');
52714var transformProperty = getStyleProperty('transform');
52715var supportsCSS3 = transitionProperty && transformProperty;
52716var is3d = !!getStyleProperty('perspective');
52717
52718var transitionEndEvent = {
52719  WebkitTransition: 'webkitTransitionEnd',
52720  MozTransition: 'transitionend',
52721  OTransition: 'otransitionend',
52722  transition: 'transitionend'
52723}[ transitionProperty ];
52724
52725// properties that could have vendor prefix
52726var prefixableProperties = [
52727  'transform',
52728  'transition',
52729  'transitionDuration',
52730  'transitionProperty'
52731];
52732
52733// cache all vendor properties
52734var vendorProperties = ( function() {
52735  var cache = {};
52736  for ( var i=0, len = prefixableProperties.length; i < len; i++ ) {
52737    var prop = prefixableProperties[i];
52738    var supportedProp = getStyleProperty( prop );
52739    if ( supportedProp && supportedProp !== prop ) {
52740      cache[ prop ] = supportedProp;
52741    }
52742  }
52743  return cache;
52744})();
52745
52746// -------------------------- Item -------------------------- //
52747
52748function Item( element, layout ) {
52749  if ( !element ) {
52750    return;
52751  }
52752
52753  this.element = element;
52754  // parent layout class, i.e. Masonry, Isotope, or Packery
52755  this.layout = layout;
52756  this.position = {
52757    x: 0,
52758    y: 0
52759  };
52760
52761  this._create();
52762}
52763
52764// inherit EventEmitter
52765extend( Item.prototype, EventEmitter.prototype );
52766
52767Item.prototype._create = function() {
52768  // transition objects
52769  this._transn = {
52770    ingProperties: {},
52771    clean: {},
52772    onEnd: {}
52773  };
52774
52775  this.css({
52776    position: 'absolute'
52777  });
52778};
52779
52780// trigger specified handler for event type
52781Item.prototype.handleEvent = function( event ) {
52782  var method = 'on' + event.type;
52783  if ( this[ method ] ) {
52784    this[ method ]( event );
52785  }
52786};
52787
52788Item.prototype.getSize = function() {
52789  this.size = getSize( this.element );
52790};
52791
52792/**
52793 * apply CSS styles to element
52794 * @param {Object} style
52795 */
52796Item.prototype.css = function( style ) {
52797  var elemStyle = this.element.style;
52798
52799  for ( var prop in style ) {
52800    // use vendor property if available
52801    var supportedProp = vendorProperties[ prop ] || prop;
52802    elemStyle[ supportedProp ] = style[ prop ];
52803  }
52804};
52805
52806 // measure position, and sets it
52807Item.prototype.getPosition = function() {
52808  var style = getStyle( this.element );
52809  var layoutOptions = this.layout.options;
52810  var isOriginLeft = layoutOptions.isOriginLeft;
52811  var isOriginTop = layoutOptions.isOriginTop;
52812  var x = parseInt( style[ isOriginLeft ? 'left' : 'right' ], 10 );
52813  var y = parseInt( style[ isOriginTop ? 'top' : 'bottom' ], 10 );
52814
52815  // clean up 'auto' or other non-integer values
52816  x = isNaN( x ) ? 0 : x;
52817  y = isNaN( y ) ? 0 : y;
52818  // remove padding from measurement
52819  var layoutSize = this.layout.size;
52820  x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight;
52821  y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom;
52822
52823  this.position.x = x;
52824  this.position.y = y;
52825};
52826
52827// set settled position, apply padding
52828Item.prototype.layoutPosition = function() {
52829  var layoutSize = this.layout.size;
52830  var layoutOptions = this.layout.options;
52831  var style = {};
52832
52833  if ( layoutOptions.isOriginLeft ) {
52834    style.left = ( this.position.x + layoutSize.paddingLeft ) + 'px';
52835    // reset other property
52836    style.right = '';
52837  } else {
52838    style.right = ( this.position.x + layoutSize.paddingRight ) + 'px';
52839    style.left = '';
52840  }
52841
52842  if ( layoutOptions.isOriginTop ) {
52843    style.top = ( this.position.y + layoutSize.paddingTop ) + 'px';
52844    style.bottom = '';
52845  } else {
52846    style.bottom = ( this.position.y + layoutSize.paddingBottom ) + 'px';
52847    style.top = '';
52848  }
52849
52850  this.css( style );
52851  this.emitEvent( 'layout', [ this ] );
52852};
52853
52854
52855// transform translate function
52856var translate = is3d ?
52857  function( x, y ) {
52858    return 'translate3d(' + x + 'px, ' + y + 'px, 0)';
52859  } :
52860  function( x, y ) {
52861    return 'translate(' + x + 'px, ' + y + 'px)';
52862  };
52863
52864
52865Item.prototype._transitionTo = function( x, y ) {
52866  this.getPosition();
52867  // get current x & y from top/left
52868  var curX = this.position.x;
52869  var curY = this.position.y;
52870
52871  var compareX = parseInt( x, 10 );
52872  var compareY = parseInt( y, 10 );
52873  var didNotMove = compareX === this.position.x && compareY === this.position.y;
52874
52875  // save end position
52876  this.setPosition( x, y );
52877
52878  // if did not move and not transitioning, just go to layout
52879  if ( didNotMove && !this.isTransitioning ) {
52880    this.layoutPosition();
52881    return;
52882  }
52883
52884  var transX = x - curX;
52885  var transY = y - curY;
52886  var transitionStyle = {};
52887  // flip cooridinates if origin on right or bottom
52888  var layoutOptions = this.layout.options;
52889  transX = layoutOptions.isOriginLeft ? transX : -transX;
52890  transY = layoutOptions.isOriginTop ? transY : -transY;
52891  transitionStyle.transform = translate( transX, transY );
52892
52893  this.transition({
52894    to: transitionStyle,
52895    onTransitionEnd: {
52896      transform: this.layoutPosition
52897    },
52898    isCleaning: true
52899  });
52900};
52901
52902// non transition + transform support
52903Item.prototype.goTo = function( x, y ) {
52904  this.setPosition( x, y );
52905  this.layoutPosition();
52906};
52907
52908// use transition and transforms if supported
52909Item.prototype.moveTo = supportsCSS3 ?
52910  Item.prototype._transitionTo : Item.prototype.goTo;
52911
52912Item.prototype.setPosition = function( x, y ) {
52913  this.position.x = parseInt( x, 10 );
52914  this.position.y = parseInt( y, 10 );
52915};
52916
52917// ----- transition ----- //
52918
52919/**
52920 * @param {Object} style - CSS
52921 * @param {Function} onTransitionEnd
52922 */
52923
52924// non transition, just trigger callback
52925Item.prototype._nonTransition = function( args ) {
52926  this.css( args.to );
52927  if ( args.isCleaning ) {
52928    this._removeStyles( args.to );
52929  }
52930  for ( var prop in args.onTransitionEnd ) {
52931    args.onTransitionEnd[ prop ].call( this );
52932  }
52933};
52934
52935/**
52936 * proper transition
52937 * @param {Object} args - arguments
52938 *   @param {Object} to - style to transition to
52939 *   @param {Object} from - style to start transition from
52940 *   @param {Boolean} isCleaning - removes transition styles after transition
52941 *   @param {Function} onTransitionEnd - callback
52942 */
52943Item.prototype._transition = function( args ) {
52944  // redirect to nonTransition if no transition duration
52945  if ( !parseFloat( this.layout.options.transitionDuration ) ) {
52946    this._nonTransition( args );
52947    return;
52948  }
52949
52950  var _transition = this._transn;
52951  // keep track of onTransitionEnd callback by css property
52952  for ( var prop in args.onTransitionEnd ) {
52953    _transition.onEnd[ prop ] = args.onTransitionEnd[ prop ];
52954  }
52955  // keep track of properties that are transitioning
52956  for ( prop in args.to ) {
52957    _transition.ingProperties[ prop ] = true;
52958    // keep track of properties to clean up when transition is done
52959    if ( args.isCleaning ) {
52960      _transition.clean[ prop ] = true;
52961    }
52962  }
52963
52964  // set from styles
52965  if ( args.from ) {
52966    this.css( args.from );
52967    // force redraw. http://blog.alexmaccaw.com/css-transitions
52968    var h = this.element.offsetHeight;
52969    // hack for JSHint to hush about unused var
52970    h = null;
52971  }
52972  // enable transition
52973  this.enableTransition( args.to );
52974  // set styles that are transitioning
52975  this.css( args.to );
52976
52977  this.isTransitioning = true;
52978
52979};
52980
52981var itemTransitionProperties = transformProperty && ( toDash( transformProperty ) +
52982  ',opacity' );
52983
52984Item.prototype.enableTransition = function(/* style */) {
52985  // only enable if not already transitioning
52986  // bug in IE10 were re-setting transition style will prevent
52987  // transitionend event from triggering
52988  if ( this.isTransitioning ) {
52989    return;
52990  }
52991
52992  // make transition: foo, bar, baz from style object
52993  // TODO uncomment this bit when IE10 bug is resolved
52994  // var transitionValue = [];
52995  // for ( var prop in style ) {
52996  //   // dash-ify camelCased properties like WebkitTransition
52997  //   transitionValue.push( toDash( prop ) );
52998  // }
52999  // enable transition styles
53000  // HACK always enable transform,opacity for IE10
53001  this.css({
53002    transitionProperty: itemTransitionProperties,
53003    transitionDuration: this.layout.options.transitionDuration
53004  });
53005  // listen for transition end event
53006  this.element.addEventListener( transitionEndEvent, this, false );
53007};
53008
53009Item.prototype.transition = Item.prototype[ transitionProperty ? '_transition' : '_nonTransition' ];
53010
53011// ----- events ----- //
53012
53013Item.prototype.onwebkitTransitionEnd = function( event ) {
53014  this.ontransitionend( event );
53015};
53016
53017Item.prototype.onotransitionend = function( event ) {
53018  this.ontransitionend( event );
53019};
53020
53021// properties that I munge to make my life easier
53022var dashedVendorProperties = {
53023  '-webkit-transform': 'transform',
53024  '-moz-transform': 'transform',
53025  '-o-transform': 'transform'
53026};
53027
53028Item.prototype.ontransitionend = function( event ) {
53029  // disregard bubbled events from children
53030  if ( event.target !== this.element ) {
53031    return;
53032  }
53033  var _transition = this._transn;
53034  // get property name of transitioned property, convert to prefix-free
53035  var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName;
53036
53037  // remove property that has completed transitioning
53038  delete _transition.ingProperties[ propertyName ];
53039  // check if any properties are still transitioning
53040  if ( isEmptyObj( _transition.ingProperties ) ) {
53041    // all properties have completed transitioning
53042    this.disableTransition();
53043  }
53044  // clean style
53045  if ( propertyName in _transition.clean ) {
53046    // clean up style
53047    this.element.style[ event.propertyName ] = '';
53048    delete _transition.clean[ propertyName ];
53049  }
53050  // trigger onTransitionEnd callback
53051  if ( propertyName in _transition.onEnd ) {
53052    var onTransitionEnd = _transition.onEnd[ propertyName ];
53053    onTransitionEnd.call( this );
53054    delete _transition.onEnd[ propertyName ];
53055  }
53056
53057  this.emitEvent( 'transitionEnd', [ this ] );
53058};
53059
53060Item.prototype.disableTransition = function() {
53061  this.removeTransitionStyles();
53062  this.element.removeEventListener( transitionEndEvent, this, false );
53063  this.isTransitioning = false;
53064};
53065
53066/**
53067 * removes style property from element
53068 * @param {Object} style
53069**/
53070Item.prototype._removeStyles = function( style ) {
53071  // clean up transition styles
53072  var cleanStyle = {};
53073  for ( var prop in style ) {
53074    cleanStyle[ prop ] = '';
53075  }
53076  this.css( cleanStyle );
53077};
53078
53079var cleanTransitionStyle = {
53080  transitionProperty: '',
53081  transitionDuration: ''
53082};
53083
53084Item.prototype.removeTransitionStyles = function() {
53085  // remove transition
53086  this.css( cleanTransitionStyle );
53087};
53088
53089// ----- show/hide/remove ----- //
53090
53091// remove element from DOM
53092Item.prototype.removeElem = function() {
53093  this.element.parentNode.removeChild( this.element );
53094  this.emitEvent( 'remove', [ this ] );
53095};
53096
53097Item.prototype.remove = function() {
53098  // just remove element if no transition support or no transition
53099  if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) {
53100    this.removeElem();
53101    return;
53102  }
53103
53104  // start transition
53105  var _this = this;
53106  this.on( 'transitionEnd', function() {
53107    _this.removeElem();
53108    return true; // bind once
53109  });
53110  this.hide();
53111};
53112
53113Item.prototype.reveal = function() {
53114  delete this.isHidden;
53115  // remove display: none
53116  this.css({ display: '' });
53117
53118  var options = this.layout.options;
53119  this.transition({
53120    from: options.hiddenStyle,
53121    to: options.visibleStyle,
53122    isCleaning: true
53123  });
53124};
53125
53126Item.prototype.hide = function() {
53127  // set flag
53128  this.isHidden = true;
53129  // remove display: none
53130  this.css({ display: '' });
53131
53132  var options = this.layout.options;
53133  this.transition({
53134    from: options.visibleStyle,
53135    to: options.hiddenStyle,
53136    // keep hidden stuff hidden
53137    isCleaning: true,
53138    onTransitionEnd: {
53139      opacity: function() {
53140        // check if still hidden
53141        // during transition, item may have been un-hidden
53142        if ( this.isHidden ) {
53143          this.css({ display: 'none' });
53144        }
53145      }
53146    }
53147  });
53148};
53149
53150Item.prototype.destroy = function() {
53151  this.css({
53152    position: '',
53153    left: '',
53154    right: '',
53155    top: '',
53156    bottom: '',
53157    transition: '',
53158    transform: ''
53159  });
53160};
53161
53162return Item;
53163
53164}
53165
53166// -------------------------- transport -------------------------- //
53167
53168if ( typeof define === 'function' && define.amd ) {
53169  // AMD
53170  define( 'outlayer/item',[
53171      'eventEmitter/EventEmitter',
53172      'get-size/get-size',
53173      'get-style-property/get-style-property'
53174    ],
53175    outlayerItemDefinition );
53176} else {
53177  // browser global
53178  window.Outlayer = {};
53179  window.Outlayer.Item = outlayerItemDefinition(
53180    window.EventEmitter,
53181    window.getSize,
53182    window.getStyleProperty
53183  );
53184}
53185
53186})( window );
53187
53188/*!
53189 * Outlayer v1.2.0
53190 * the brains and guts of a layout library
53191 * MIT license
53192 */
53193
53194( function( window ) {
53195
53196
53197
53198// ----- vars ----- //
53199
53200var document = window.document;
53201var console = window.console;
53202var jQuery = window.jQuery;
53203
53204var noop = function() {};
53205
53206// -------------------------- helpers -------------------------- //
53207
53208// extend objects
53209function extend( a, b ) {
53210  for ( var prop in b ) {
53211    a[ prop ] = b[ prop ];
53212  }
53213  return a;
53214}
53215
53216
53217var objToString = Object.prototype.toString;
53218function isArray( obj ) {
53219  return objToString.call( obj ) === '[object Array]';
53220}
53221
53222// turn element or nodeList into an array
53223function makeArray( obj ) {
53224  var ary = [];
53225  if ( isArray( obj ) ) {
53226    // use object if already an array
53227    ary = obj;
53228  } else if ( obj && typeof obj.length === 'number' ) {
53229    // convert nodeList to array
53230    for ( var i=0, len = obj.length; i < len; i++ ) {
53231      ary.push( obj[i] );
53232    }
53233  } else {
53234    // array of single index
53235    ary.push( obj );
53236  }
53237  return ary;
53238}
53239
53240// http://stackoverflow.com/a/384380/182183
53241var isElement = ( typeof HTMLElement === 'object' ) ?
53242  function isElementDOM2( obj ) {
53243    return obj instanceof HTMLElement;
53244  } :
53245  function isElementQuirky( obj ) {
53246    return obj && typeof obj === 'object' &&
53247      obj.nodeType === 1 && typeof obj.nodeName === 'string';
53248  };
53249
53250// index of helper cause IE8
53251var indexOf = Array.prototype.indexOf ? function( ary, obj ) {
53252    return ary.indexOf( obj );
53253  } : function( ary, obj ) {
53254    for ( var i=0, len = ary.length; i < len; i++ ) {
53255      if ( ary[i] === obj ) {
53256        return i;
53257      }
53258    }
53259    return -1;
53260  };
53261
53262function removeFrom( obj, ary ) {
53263  var index = indexOf( ary, obj );
53264  if ( index !== -1 ) {
53265    ary.splice( index, 1 );
53266  }
53267}
53268
53269// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
53270function toDashed( str ) {
53271  return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
53272    return $1 + '-' + $2;
53273  }).toLowerCase();
53274}
53275
53276
53277function outlayerDefinition( eventie, docReady, EventEmitter, getSize, matchesSelector, Item ) {
53278
53279// -------------------------- Outlayer -------------------------- //
53280
53281// globally unique identifiers
53282var GUID = 0;
53283// internal store of all Outlayer intances
53284var instances = {};
53285
53286
53287/**
53288 * @param {Element, String} element
53289 * @param {Object} options
53290 * @constructor
53291 */
53292function Outlayer( element, options ) {
53293  // use element as selector string
53294  if ( typeof element === 'string' ) {
53295    element = document.querySelector( element );
53296  }
53297
53298  // bail out if not proper element
53299  if ( !element || !isElement( element ) ) {
53300    if ( console ) {
53301      console.error( 'Bad ' + this.constructor.namespace + ' element: ' + element );
53302    }
53303    return;
53304  }
53305
53306  this.element = element;
53307
53308  // options
53309  this.options = extend( {}, this.constructor.defaults );
53310  this.option( options );
53311
53312  // add id for Outlayer.getFromElement
53313  var id = ++GUID;
53314  this.element.outlayerGUID = id; // expando
53315  instances[ id ] = this; // associate via id
53316
53317  // kick it off
53318  this._create();
53319
53320  if ( this.options.isInitLayout ) {
53321    this.layout();
53322  }
53323}
53324
53325// settings are for internal use only
53326Outlayer.namespace = 'outlayer';
53327Outlayer.Item = Item;
53328
53329// default options
53330Outlayer.defaults = {
53331  containerStyle: {
53332    position: 'relative'
53333  },
53334  isInitLayout: true,
53335  isOriginLeft: true,
53336  isOriginTop: true,
53337  isResizeBound: true,
53338  isResizingContainer: true,
53339  // item options
53340  transitionDuration: '0.4s',
53341  hiddenStyle: {
53342    opacity: 0,
53343    transform: 'scale(0.001)'
53344  },
53345  visibleStyle: {
53346    opacity: 1,
53347    transform: 'scale(1)'
53348  }
53349};
53350
53351// inherit EventEmitter
53352extend( Outlayer.prototype, EventEmitter.prototype );
53353
53354/**
53355 * set options
53356 * @param {Object} opts
53357 */
53358Outlayer.prototype.option = function( opts ) {
53359  extend( this.options, opts );
53360};
53361
53362Outlayer.prototype._create = function() {
53363  // get items from children
53364  this.reloadItems();
53365  // elements that affect layout, but are not laid out
53366  this.stamps = [];
53367  this.stamp( this.options.stamp );
53368  // set container style
53369  extend( this.element.style, this.options.containerStyle );
53370
53371  // bind resize method
53372  if ( this.options.isResizeBound ) {
53373    this.bindResize();
53374  }
53375};
53376
53377// goes through all children again and gets bricks in proper order
53378Outlayer.prototype.reloadItems = function() {
53379  // collection of item elements
53380  this.items = this._itemize( this.element.children );
53381};
53382
53383
53384/**
53385 * turn elements into Outlayer.Items to be used in layout
53386 * @param {Array or NodeList or HTMLElement} elems
53387 * @returns {Array} items - collection of new Outlayer Items
53388 */
53389Outlayer.prototype._itemize = function( elems ) {
53390
53391  var itemElems = this._filterFindItemElements( elems );
53392  var Item = this.constructor.Item;
53393
53394  // create new Outlayer Items for collection
53395  var items = [];
53396  for ( var i=0, len = itemElems.length; i < len; i++ ) {
53397    var elem = itemElems[i];
53398    var item = new Item( elem, this );
53399    items.push( item );
53400  }
53401
53402  return items;
53403};
53404
53405/**
53406 * get item elements to be used in layout
53407 * @param {Array or NodeList or HTMLElement} elems
53408 * @returns {Array} items - item elements
53409 */
53410Outlayer.prototype._filterFindItemElements = function( elems ) {
53411  // make array of elems
53412  elems = makeArray( elems );
53413  var itemSelector = this.options.itemSelector;
53414  var itemElems = [];
53415
53416  for ( var i=0, len = elems.length; i < len; i++ ) {
53417    var elem = elems[i];
53418    // check that elem is an actual element
53419    if ( !isElement( elem ) ) {
53420      continue;
53421    }
53422    // filter & find items if we have an item selector
53423    if ( itemSelector ) {
53424      // filter siblings
53425      if ( matchesSelector( elem, itemSelector ) ) {
53426        itemElems.push( elem );
53427      }
53428      // find children
53429      var childElems = elem.querySelectorAll( itemSelector );
53430      // concat childElems to filterFound array
53431      for ( var j=0, jLen = childElems.length; j < jLen; j++ ) {
53432        itemElems.push( childElems[j] );
53433      }
53434    } else {
53435      itemElems.push( elem );
53436    }
53437  }
53438
53439  return itemElems;
53440};
53441
53442/**
53443 * getter method for getting item elements
53444 * @returns {Array} elems - collection of item elements
53445 */
53446Outlayer.prototype.getItemElements = function() {
53447  var elems = [];
53448  for ( var i=0, len = this.items.length; i < len; i++ ) {
53449    elems.push( this.items[i].element );
53450  }
53451  return elems;
53452};
53453
53454// ----- init & layout ----- //
53455
53456/**
53457 * lays out all items
53458 */
53459Outlayer.prototype.layout = function() {
53460  this._resetLayout();
53461  this._manageStamps();
53462
53463  // don't animate first layout
53464  var isInstant = this.options.isLayoutInstant !== undefined ?
53465    this.options.isLayoutInstant : !this._isLayoutInited;
53466  this.layoutItems( this.items, isInstant );
53467
53468  // flag for initalized
53469  this._isLayoutInited = true;
53470};
53471
53472// _init is alias for layout
53473Outlayer.prototype._init = Outlayer.prototype.layout;
53474
53475/**
53476 * logic before any new layout
53477 */
53478Outlayer.prototype._resetLayout = function() {
53479  this.getSize();
53480};
53481
53482
53483Outlayer.prototype.getSize = function() {
53484  this.size = getSize( this.element );
53485};
53486
53487/**
53488 * get measurement from option, for columnWidth, rowHeight, gutter
53489 * if option is String -> get element from selector string, & get size of element
53490 * if option is Element -> get size of element
53491 * else use option as a number
53492 *
53493 * @param {String} measurement
53494 * @param {String} size - width or height
53495 * @private
53496 */
53497Outlayer.prototype._getMeasurement = function( measurement, size ) {
53498  var option = this.options[ measurement ];
53499  var elem;
53500  if ( !option ) {
53501    // default to 0
53502    this[ measurement ] = 0;
53503  } else {
53504    // use option as an element
53505    if ( typeof option === 'string' ) {
53506      elem = this.element.querySelector( option );
53507    } else if ( isElement( option ) ) {
53508      elem = option;
53509    }
53510    // use size of element, if element
53511    this[ measurement ] = elem ? getSize( elem )[ size ] : option;
53512  }
53513};
53514
53515/**
53516 * layout a collection of item elements
53517 * @api public
53518 */
53519Outlayer.prototype.layoutItems = function( items, isInstant ) {
53520  items = this._getItemsForLayout( items );
53521
53522  this._layoutItems( items, isInstant );
53523
53524  this._postLayout();
53525};
53526
53527/**
53528 * get the items to be laid out
53529 * you may want to skip over some items
53530 * @param {Array} items
53531 * @returns {Array} items
53532 */
53533Outlayer.prototype._getItemsForLayout = function( items ) {
53534  var layoutItems = [];
53535  for ( var i=0, len = items.length; i < len; i++ ) {
53536    var item = items[i];
53537    if ( !item.isIgnored ) {
53538      layoutItems.push( item );
53539    }
53540  }
53541  return layoutItems;
53542};
53543
53544/**
53545 * layout items
53546 * @param {Array} items
53547 * @param {Boolean} isInstant
53548 */
53549Outlayer.prototype._layoutItems = function( items, isInstant ) {
53550  var _this = this;
53551  function onItemsLayout() {
53552    _this.emitEvent( 'layoutComplete', [ _this, items ] );
53553  }
53554
53555  if ( !items || !items.length ) {
53556    // no items, emit event with empty array
53557    onItemsLayout();
53558    return;
53559  }
53560
53561  // emit layoutComplete when done
53562  this._itemsOn( items, 'layout', onItemsLayout );
53563
53564  var queue = [];
53565
53566  for ( var i=0, len = items.length; i < len; i++ ) {
53567    var item = items[i];
53568    // get x/y object from method
53569    var position = this._getItemLayoutPosition( item );
53570    // enqueue
53571    position.item = item;
53572    position.isInstant = isInstant || item.isLayoutInstant;
53573    queue.push( position );
53574  }
53575
53576  this._processLayoutQueue( queue );
53577};
53578
53579/**
53580 * get item layout position
53581 * @param {Outlayer.Item} item
53582 * @returns {Object} x and y position
53583 */
53584Outlayer.prototype._getItemLayoutPosition = function( /* item */ ) {
53585  return {
53586    x: 0,
53587    y: 0
53588  };
53589};
53590
53591/**
53592 * iterate over array and position each item
53593 * Reason being - separating this logic prevents 'layout invalidation'
53594 * thx @paul_irish
53595 * @param {Array} queue
53596 */
53597Outlayer.prototype._processLayoutQueue = function( queue ) {
53598  for ( var i=0, len = queue.length; i < len; i++ ) {
53599    var obj = queue[i];
53600    this._positionItem( obj.item, obj.x, obj.y, obj.isInstant );
53601  }
53602};
53603
53604/**
53605 * Sets position of item in DOM
53606 * @param {Outlayer.Item} item
53607 * @param {Number} x - horizontal position
53608 * @param {Number} y - vertical position
53609 * @param {Boolean} isInstant - disables transitions
53610 */
53611Outlayer.prototype._positionItem = function( item, x, y, isInstant ) {
53612  if ( isInstant ) {
53613    // if not transition, just set CSS
53614    item.goTo( x, y );
53615  } else {
53616    item.moveTo( x, y );
53617  }
53618};
53619
53620/**
53621 * Any logic you want to do after each layout,
53622 * i.e. size the container
53623 */
53624Outlayer.prototype._postLayout = function() {
53625  this.resizeContainer();
53626};
53627
53628Outlayer.prototype.resizeContainer = function() {
53629  if ( !this.options.isResizingContainer ) {
53630    return;
53631  }
53632  var size = this._getContainerSize();
53633  if ( size ) {
53634    this._setContainerMeasure( size.width, true );
53635    this._setContainerMeasure( size.height, false );
53636  }
53637};
53638
53639/**
53640 * Sets width or height of container if returned
53641 * @returns {Object} size
53642 *   @param {Number} width
53643 *   @param {Number} height
53644 */
53645Outlayer.prototype._getContainerSize = noop;
53646
53647/**
53648 * @param {Number} measure - size of width or height
53649 * @param {Boolean} isWidth
53650 */
53651Outlayer.prototype._setContainerMeasure = function( measure, isWidth ) {
53652  if ( measure === undefined ) {
53653    return;
53654  }
53655
53656  var elemSize = this.size;
53657  // add padding and border width if border box
53658  if ( elemSize.isBorderBox ) {
53659    measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight +
53660      elemSize.borderLeftWidth + elemSize.borderRightWidth :
53661      elemSize.paddingBottom + elemSize.paddingTop +
53662      elemSize.borderTopWidth + elemSize.borderBottomWidth;
53663  }
53664
53665  measure = Math.max( measure, 0 );
53666  this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px';
53667};
53668
53669/**
53670 * trigger a callback for a collection of items events
53671 * @param {Array} items - Outlayer.Items
53672 * @param {String} eventName
53673 * @param {Function} callback
53674 */
53675Outlayer.prototype._itemsOn = function( items, eventName, callback ) {
53676  var doneCount = 0;
53677  var count = items.length;
53678  // event callback
53679  var _this = this;
53680  function tick() {
53681    doneCount++;
53682    if ( doneCount === count ) {
53683      callback.call( _this );
53684    }
53685    return true; // bind once
53686  }
53687  // bind callback
53688  for ( var i=0, len = items.length; i < len; i++ ) {
53689    var item = items[i];
53690    item.on( eventName, tick );
53691  }
53692};
53693
53694// -------------------------- ignore & stamps -------------------------- //
53695
53696
53697/**
53698 * keep item in collection, but do not lay it out
53699 * ignored items do not get skipped in layout
53700 * @param {Element} elem
53701 */
53702Outlayer.prototype.ignore = function( elem ) {
53703  var item = this.getItem( elem );
53704  if ( item ) {
53705    item.isIgnored = true;
53706  }
53707};
53708
53709/**
53710 * return item to layout collection
53711 * @param {Element} elem
53712 */
53713Outlayer.prototype.unignore = function( elem ) {
53714  var item = this.getItem( elem );
53715  if ( item ) {
53716    delete item.isIgnored;
53717  }
53718};
53719
53720/**
53721 * adds elements to stamps
53722 * @param {NodeList, Array, Element, or String} elems
53723 */
53724Outlayer.prototype.stamp = function( elems ) {
53725  elems = this._find( elems );
53726  if ( !elems ) {
53727    return;
53728  }
53729
53730  this.stamps = this.stamps.concat( elems );
53731  // ignore
53732  for ( var i=0, len = elems.length; i < len; i++ ) {
53733    var elem = elems[i];
53734    this.ignore( elem );
53735  }
53736};
53737
53738/**
53739 * removes elements to stamps
53740 * @param {NodeList, Array, or Element} elems
53741 */
53742Outlayer.prototype.unstamp = function( elems ) {
53743  elems = this._find( elems );
53744  if ( !elems ){
53745    return;
53746  }
53747
53748  for ( var i=0, len = elems.length; i < len; i++ ) {
53749    var elem = elems[i];
53750    // filter out removed stamp elements
53751    removeFrom( elem, this.stamps );
53752    this.unignore( elem );
53753  }
53754
53755};
53756
53757/**
53758 * finds child elements
53759 * @param {NodeList, Array, Element, or String} elems
53760 * @returns {Array} elems
53761 */
53762Outlayer.prototype._find = function( elems ) {
53763  if ( !elems ) {
53764    return;
53765  }
53766  // if string, use argument as selector string
53767  if ( typeof elems === 'string' ) {
53768    elems = this.element.querySelectorAll( elems );
53769  }
53770  elems = makeArray( elems );
53771  return elems;
53772};
53773
53774Outlayer.prototype._manageStamps = function() {
53775  if ( !this.stamps || !this.stamps.length ) {
53776    return;
53777  }
53778
53779  this._getBoundingRect();
53780
53781  for ( var i=0, len = this.stamps.length; i < len; i++ ) {
53782    var stamp = this.stamps[i];
53783    this._manageStamp( stamp );
53784  }
53785};
53786
53787// update boundingLeft / Top
53788Outlayer.prototype._getBoundingRect = function() {
53789  // get bounding rect for container element
53790  var boundingRect = this.element.getBoundingClientRect();
53791  var size = this.size;
53792  this._boundingRect = {
53793    left: boundingRect.left + size.paddingLeft + size.borderLeftWidth,
53794    top: boundingRect.top + size.paddingTop + size.borderTopWidth,
53795    right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ),
53796    bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth )
53797  };
53798};
53799
53800/**
53801 * @param {Element} stamp
53802**/
53803Outlayer.prototype._manageStamp = noop;
53804
53805/**
53806 * get x/y position of element relative to container element
53807 * @param {Element} elem
53808 * @returns {Object} offset - has left, top, right, bottom
53809 */
53810Outlayer.prototype._getElementOffset = function( elem ) {
53811  var boundingRect = elem.getBoundingClientRect();
53812  var thisRect = this._boundingRect;
53813  var size = getSize( elem );
53814  var offset = {
53815    left: boundingRect.left - thisRect.left - size.marginLeft,
53816    top: boundingRect.top - thisRect.top - size.marginTop,
53817    right: thisRect.right - boundingRect.right - size.marginRight,
53818    bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom
53819  };
53820  return offset;
53821};
53822
53823// -------------------------- resize -------------------------- //
53824
53825// enable event handlers for listeners
53826// i.e. resize -> onresize
53827Outlayer.prototype.handleEvent = function( event ) {
53828  var method = 'on' + event.type;
53829  if ( this[ method ] ) {
53830    this[ method ]( event );
53831  }
53832};
53833
53834/**
53835 * Bind layout to window resizing
53836 */
53837Outlayer.prototype.bindResize = function() {
53838  // bind just one listener
53839  if ( this.isResizeBound ) {
53840    return;
53841  }
53842  eventie.bind( window, 'resize', this );
53843  this.isResizeBound = true;
53844};
53845
53846/**
53847 * Unbind layout to window resizing
53848 */
53849Outlayer.prototype.unbindResize = function() {
53850  if ( this.isResizeBound ) {
53851    eventie.unbind( window, 'resize', this );
53852  }
53853  this.isResizeBound = false;
53854};
53855
53856// original debounce by John Hann
53857// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
53858
53859// this fires every resize
53860Outlayer.prototype.onresize = function() {
53861  if ( this.resizeTimeout ) {
53862    clearTimeout( this.resizeTimeout );
53863  }
53864
53865  var _this = this;
53866  function delayed() {
53867    _this.resize();
53868    delete _this.resizeTimeout;
53869  }
53870
53871  this.resizeTimeout = setTimeout( delayed, 100 );
53872};
53873
53874// debounced, layout on resize
53875Outlayer.prototype.resize = function() {
53876  // don't trigger if size did not change
53877  // or if resize was unbound. See #9
53878
53879  if ( !this.isResizeBound || !this.needsResizeLayout() ) {
53880    return;
53881  }
53882
53883  this.layout();
53884};
53885
53886/**
53887 * check if layout is needed post layout
53888 * @returns Boolean
53889 */
53890Outlayer.prototype.needsResizeLayout = function() {
53891  var size = getSize( this.element );
53892  // check that this.size and size are there
53893  // IE8 triggers resize on body size change, so they might not be
53894  var hasSizes = this.size && size;
53895  return hasSizes && size.innerWidth !== this.size.innerWidth;
53896};
53897
53898// -------------------------- methods -------------------------- //
53899
53900/**
53901 * add items to Outlayer instance
53902 * @param {Array or NodeList or Element} elems
53903 * @returns {Array} items - Outlayer.Items
53904**/
53905Outlayer.prototype.addItems = function( elems ) {
53906  var items = this._itemize( elems );
53907  // add items to collection
53908  if ( items.length ) {
53909    this.items = this.items.concat( items );
53910  }
53911  return items;
53912};
53913
53914/**
53915 * Layout newly-appended item elements
53916 * @param {Array or NodeList or Element} elems
53917 */
53918Outlayer.prototype.appended = function( elems ) {
53919  var items = this.addItems( elems );
53920  if ( !items.length ) {
53921    return;
53922  }
53923  // layout and reveal just the new items
53924  this.layoutItems( items, true );
53925  this.reveal( items );
53926};
53927
53928/**
53929 * Layout prepended elements
53930 * @param {Array or NodeList or Element} elems
53931 */
53932Outlayer.prototype.prepended = function( elems ) {
53933  var items = this._itemize( elems );
53934  if ( !items.length ) {
53935    return;
53936  }
53937  // add items to beginning of collection
53938  var previousItems = this.items.slice(0);
53939  this.items = items.concat( previousItems );
53940  // start new layout
53941  this._resetLayout();
53942  this._manageStamps();
53943  // layout new stuff without transition
53944  this.layoutItems( items, true );
53945  this.reveal( items );
53946  // layout previous items
53947  this.layoutItems( previousItems );
53948};
53949
53950/**
53951 * reveal a collection of items
53952 * @param {Array of Outlayer.Items} items
53953 */
53954Outlayer.prototype.reveal = function( items ) {
53955  var len = items && items.length;
53956  if ( !len ) {
53957    return;
53958  }
53959  for ( var i=0; i < len; i++ ) {
53960    var item = items[i];
53961    item.reveal();
53962  }
53963};
53964
53965/**
53966 * hide a collection of items
53967 * @param {Array of Outlayer.Items} items
53968 */
53969Outlayer.prototype.hide = function( items ) {
53970  var len = items && items.length;
53971  if ( !len ) {
53972    return;
53973  }
53974  for ( var i=0; i < len; i++ ) {
53975    var item = items[i];
53976    item.hide();
53977  }
53978};
53979
53980/**
53981 * get Outlayer.Item, given an Element
53982 * @param {Element} elem
53983 * @param {Function} callback
53984 * @returns {Outlayer.Item} item
53985 */
53986Outlayer.prototype.getItem = function( elem ) {
53987  // loop through items to get the one that matches
53988  for ( var i=0, len = this.items.length; i < len; i++ ) {
53989    var item = this.items[i];
53990    if ( item.element === elem ) {
53991      // return item
53992      return item;
53993    }
53994  }
53995};
53996
53997/**
53998 * get collection of Outlayer.Items, given Elements
53999 * @param {Array} elems
54000 * @returns {Array} items - Outlayer.Items
54001 */
54002Outlayer.prototype.getItems = function( elems ) {
54003  if ( !elems || !elems.length ) {
54004    return;
54005  }
54006  var items = [];
54007  for ( var i=0, len = elems.length; i < len; i++ ) {
54008    var elem = elems[i];
54009    var item = this.getItem( elem );
54010    if ( item ) {
54011      items.push( item );
54012    }
54013  }
54014
54015  return items;
54016};
54017
54018/**
54019 * remove element(s) from instance and DOM
54020 * @param {Array or NodeList or Element} elems
54021 */
54022Outlayer.prototype.remove = function( elems ) {
54023  elems = makeArray( elems );
54024
54025  var removeItems = this.getItems( elems );
54026  // bail if no items to remove
54027  if ( !removeItems || !removeItems.length ) {
54028    return;
54029  }
54030
54031  this._itemsOn( removeItems, 'remove', function() {
54032    this.emitEvent( 'removeComplete', [ this, removeItems ] );
54033  });
54034
54035  for ( var i=0, len = removeItems.length; i < len; i++ ) {
54036    var item = removeItems[i];
54037    item.remove();
54038    // remove item from collection
54039    removeFrom( item, this.items );
54040  }
54041};
54042
54043// ----- destroy ----- //
54044
54045// remove and disable Outlayer instance
54046Outlayer.prototype.destroy = function() {
54047  // clean up dynamic styles
54048  var style = this.element.style;
54049  style.height = '';
54050  style.position = '';
54051  style.width = '';
54052  // destroy items
54053  for ( var i=0, len = this.items.length; i < len; i++ ) {
54054    var item = this.items[i];
54055    item.destroy();
54056  }
54057
54058  this.unbindResize();
54059
54060  delete this.element.outlayerGUID;
54061  // remove data for jQuery
54062  if ( jQuery ) {
54063    jQuery.removeData( this.element, this.constructor.namespace );
54064  }
54065
54066};
54067
54068// -------------------------- data -------------------------- //
54069
54070/**
54071 * get Outlayer instance from element
54072 * @param {Element} elem
54073 * @returns {Outlayer}
54074 */
54075Outlayer.data = function( elem ) {
54076  var id = elem && elem.outlayerGUID;
54077  return id && instances[ id ];
54078};
54079
54080
54081// -------------------------- create Outlayer class -------------------------- //
54082
54083/**
54084 * create a layout class
54085 * @param {String} namespace
54086 */
54087Outlayer.create = function( namespace, options ) {
54088  // sub-class Outlayer
54089  function Layout() {
54090    Outlayer.apply( this, arguments );
54091  }
54092  // inherit Outlayer prototype, use Object.create if there
54093  if ( Object.create ) {
54094    Layout.prototype = Object.create( Outlayer.prototype );
54095  } else {
54096    extend( Layout.prototype, Outlayer.prototype );
54097  }
54098  // set contructor, used for namespace and Item
54099  Layout.prototype.constructor = Layout;
54100
54101  Layout.defaults = extend( {}, Outlayer.defaults );
54102  // apply new options
54103  extend( Layout.defaults, options );
54104  // keep prototype.settings for backwards compatibility (Packery v1.2.0)
54105  Layout.prototype.settings = {};
54106
54107  Layout.namespace = namespace;
54108
54109  Layout.data = Outlayer.data;
54110
54111  // sub-class Item
54112  Layout.Item = function LayoutItem() {
54113    Item.apply( this, arguments );
54114  };
54115
54116  Layout.Item.prototype = new Item();
54117
54118  // -------------------------- declarative -------------------------- //
54119
54120  /**
54121   * allow user to initialize Outlayer via .js-namespace class
54122   * options are parsed from data-namespace-option attribute
54123   */
54124  docReady( function() {
54125    var dashedNamespace = toDashed( namespace );
54126    var elems = document.querySelectorAll( '.js-' + dashedNamespace );
54127    var dataAttr = 'data-' + dashedNamespace + '-options';
54128
54129    for ( var i=0, len = elems.length; i < len; i++ ) {
54130      var elem = elems[i];
54131      var attr = elem.getAttribute( dataAttr );
54132      var options;
54133      try {
54134        options = attr && JSON.parse( attr );
54135      } catch ( error ) {
54136        // log error, do not initialize
54137        if ( console ) {
54138          console.error( 'Error parsing ' + dataAttr + ' on ' +
54139            elem.nodeName.toLowerCase() + ( elem.id ? '#' + elem.id : '' ) + ': ' +
54140            error );
54141        }
54142        continue;
54143      }
54144      // initialize
54145      var instance = new Layout( elem, options );
54146      // make available via $().data('layoutname')
54147      if ( jQuery ) {
54148        jQuery.data( elem, namespace, instance );
54149      }
54150    }
54151  });
54152
54153  // -------------------------- jQuery bridge -------------------------- //
54154
54155  // make into jQuery plugin
54156  if ( jQuery && jQuery.bridget ) {
54157    jQuery.bridget( namespace, Layout );
54158  }
54159
54160  return Layout;
54161};
54162
54163// ----- fin ----- //
54164
54165// back in global
54166Outlayer.Item = Item;
54167
54168return Outlayer;
54169
54170}
54171
54172// -------------------------- transport -------------------------- //
54173
54174if ( typeof define === 'function' && define.amd ) {
54175  // AMD
54176  define( 'outlayer/outlayer',[
54177      'eventie/eventie',
54178      'doc-ready/doc-ready',
54179      'eventEmitter/EventEmitter',
54180      'get-size/get-size',
54181      'matches-selector/matches-selector',
54182      './item'
54183    ],
54184    outlayerDefinition );
54185} else {
54186  // browser global
54187  window.Outlayer = outlayerDefinition(
54188    window.eventie,
54189    window.docReady,
54190    window.EventEmitter,
54191    window.getSize,
54192    window.matchesSelector,
54193    window.Outlayer.Item
54194  );
54195}
54196
54197})( window );
54198
54199/*!
54200 * Masonry v3.1.5
54201 * Cascading grid layout library
54202 * http://masonry.desandro.com
54203 * MIT License
54204 * by David DeSandro
54205 */
54206
54207( function( window ) {
54208
54209
54210
54211// -------------------------- helpers -------------------------- //
54212
54213var indexOf = Array.prototype.indexOf ?
54214  function( items, value ) {
54215    return items.indexOf( value );
54216  } :
54217  function ( items, value ) {
54218    for ( var i=0, len = items.length; i < len; i++ ) {
54219      var item = items[i];
54220      if ( item === value ) {
54221        return i;
54222      }
54223    }
54224    return -1;
54225  };
54226
54227// -------------------------- masonryDefinition -------------------------- //
54228
54229// used for AMD definition and requires
54230function masonryDefinition( Outlayer, getSize ) {
54231  // create an Outlayer layout class
54232  var Masonry = Outlayer.create('masonry');
54233
54234  Masonry.prototype._resetLayout = function() {
54235    this.getSize();
54236    this._getMeasurement( 'columnWidth', 'outerWidth' );
54237    this._getMeasurement( 'gutter', 'outerWidth' );
54238    this.measureColumns();
54239
54240    // reset column Y
54241    var i = this.cols;
54242    this.colYs = [];
54243    while (i--) {
54244      this.colYs.push( 0 );
54245    }
54246
54247    this.maxY = 0;
54248  };
54249
54250  Masonry.prototype.measureColumns = function() {
54251    this.getContainerWidth();
54252    // if columnWidth is 0, default to outerWidth of first item
54253    if ( !this.columnWidth ) {
54254      var firstItem = this.items[0];
54255      var firstItemElem = firstItem && firstItem.element;
54256      // columnWidth fall back to item of first element
54257      this.columnWidth = firstItemElem && getSize( firstItemElem ).outerWidth ||
54258        // if first elem has no width, default to size of container
54259        this.containerWidth;
54260    }
54261
54262    this.columnWidth += this.gutter;
54263
54264    this.cols = Math.floor( ( this.containerWidth + this.gutter ) / this.columnWidth );
54265    this.cols = Math.max( this.cols, 1 );
54266  };
54267
54268  Masonry.prototype.getContainerWidth = function() {
54269    // container is parent if fit width
54270    var container = this.options.isFitWidth ? this.element.parentNode : this.element;
54271    // check that this.size and size are there
54272    // IE8 triggers resize on body size change, so they might not be
54273    var size = getSize( container );
54274    this.containerWidth = size && size.innerWidth;
54275  };
54276
54277  Masonry.prototype._getItemLayoutPosition = function( item ) {
54278    item.getSize();
54279    // how many columns does this brick span
54280    var remainder = item.size.outerWidth % this.columnWidth;
54281    var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
54282    // round if off by 1 pixel, otherwise use ceil
54283    var colSpan = Math[ mathMethod ]( item.size.outerWidth / this.columnWidth );
54284    colSpan = Math.min( colSpan, this.cols );
54285
54286    var colGroup = this._getColGroup( colSpan );
54287    // get the minimum Y value from the columns
54288    var minimumY = Math.min.apply( Math, colGroup );
54289    var shortColIndex = indexOf( colGroup, minimumY );
54290
54291    // position the brick
54292    var position = {
54293      x: this.columnWidth * shortColIndex,
54294      y: minimumY
54295    };
54296
54297    // apply setHeight to necessary columns
54298    var setHeight = minimumY + item.size.outerHeight;
54299    var setSpan = this.cols + 1 - colGroup.length;
54300    for ( var i = 0; i < setSpan; i++ ) {
54301      this.colYs[ shortColIndex + i ] = setHeight;
54302    }
54303
54304    return position;
54305  };
54306
54307  /**
54308   * @param {Number} colSpan - number of columns the element spans
54309   * @returns {Array} colGroup
54310   */
54311  Masonry.prototype._getColGroup = function( colSpan ) {
54312    if ( colSpan < 2 ) {
54313      // if brick spans only one column, use all the column Ys
54314      return this.colYs;
54315    }
54316
54317    var colGroup = [];
54318    // how many different places could this brick fit horizontally
54319    var groupCount = this.cols + 1 - colSpan;
54320    // for each group potential horizontal position
54321    for ( var i = 0; i < groupCount; i++ ) {
54322      // make an array of colY values for that one group
54323      var groupColYs = this.colYs.slice( i, i + colSpan );
54324      // and get the max value of the array
54325      colGroup[i] = Math.max.apply( Math, groupColYs );
54326    }
54327    return colGroup;
54328  };
54329
54330  Masonry.prototype._manageStamp = function( stamp ) {
54331    var stampSize = getSize( stamp );
54332    var offset = this._getElementOffset( stamp );
54333    // get the columns that this stamp affects
54334    var firstX = this.options.isOriginLeft ? offset.left : offset.right;
54335    var lastX = firstX + stampSize.outerWidth;
54336    var firstCol = Math.floor( firstX / this.columnWidth );
54337    firstCol = Math.max( 0, firstCol );
54338    var lastCol = Math.floor( lastX / this.columnWidth );
54339    // lastCol should not go over if multiple of columnWidth #425
54340    lastCol -= lastX % this.columnWidth ? 0 : 1;
54341    lastCol = Math.min( this.cols - 1, lastCol );
54342    // set colYs to bottom of the stamp
54343    var stampMaxY = ( this.options.isOriginTop ? offset.top : offset.bottom ) +
54344      stampSize.outerHeight;
54345    for ( var i = firstCol; i <= lastCol; i++ ) {
54346      this.colYs[i] = Math.max( stampMaxY, this.colYs[i] );
54347    }
54348  };
54349
54350  Masonry.prototype._getContainerSize = function() {
54351    this.maxY = Math.max.apply( Math, this.colYs );
54352    var size = {
54353      height: this.maxY
54354    };
54355
54356    if ( this.options.isFitWidth ) {
54357      size.width = this._getContainerFitWidth();
54358    }
54359
54360    return size;
54361  };
54362
54363  Masonry.prototype._getContainerFitWidth = function() {
54364    var unusedCols = 0;
54365    // count unused columns
54366    var i = this.cols;
54367    while ( --i ) {
54368      if ( this.colYs[i] !== 0 ) {
54369        break;
54370      }
54371      unusedCols++;
54372    }
54373    // fit container to columns that have been used
54374    return ( this.cols - unusedCols ) * this.columnWidth - this.gutter;
54375  };
54376
54377  Masonry.prototype.needsResizeLayout = function() {
54378    var previousWidth = this.containerWidth;
54379    this.getContainerWidth();
54380    return previousWidth !== this.containerWidth;
54381  };
54382
54383  return Masonry;
54384}
54385
54386// -------------------------- transport -------------------------- //
54387
54388if ( typeof define === 'function' && define.amd ) {
54389  // AMD
54390  define( [
54391      'outlayer/outlayer',
54392      'get-size/get-size'
54393    ],
54394    masonryDefinition );
54395} else {
54396  // browser global
54397  window.Masonry = masonryDefinition(
54398    window.Outlayer,
54399    window.getSize
54400  );
54401}
54402
54403})( window );
54404(function() {
54405  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
54406
54407  if (window.App == null) {
54408    window.App = {};
54409  }
54410
54411  if (App.Modules == null) {
54412    App.Modules = {};
54413  }
54414
54415  App.Modules.InfiniteScroller = (function() {
54416    InfiniteScroller.prototype.canFetch = true;
54417
54418    InfiniteScroller.prototype.currentPage = null;
54419
54420    InfiniteScroller.prototype.firstPage = null;
54421
54422    InfiniteScroller.prototype.totalPages = null;
54423
54424    InfiniteScroller.prototype.loopPages = null;
54425
54426    InfiniteScroller.prototype.loadContainer = null;
54427
54428    InfiniteScroller.prototype.loader = null;
54429
54430    InfiniteScroller.prototype.pagesOverflow = false;
54431
54432    function InfiniteScroller(triggerElementSelector) {
54433      this.newPage = bind(this.newPage, this);
54434      this.onScroll = bind(this.onScroll, this);
54435      this.triggerElement = $(triggerElementSelector);
54436      this.currentPage = this.triggerElement.data('current-page');
54437      this.totalPages = this.triggerElement.data('total-pages');
54438      this.loopPages = this.triggerElement.data('loop-pages');
54439      this.firstPage = this.currentPage;
54440      this.loadPath = this.triggerElement.data('load-path');
54441      this.loadContainer = $(this.triggerElement.data('load-container'));
54442      this.loader = $(this.triggerElement.data('loader'));
54443      this.afterFetchCallbacks = [];
54444      $(document).on('scroll', this.onScroll);
54445    }
54446
54447    InfiniteScroller.prototype.onScroll = function() {
54448      if (this.triggerElement.isOnScreen()) {
54449        return this.fetchNewPage();
54450      }
54451    };
54452
54453    InfiniteScroller.prototype.hasNextPage = function() {
54454      if (this.loopPages) {
54455        return !this.pagesOverflow && this.nextPage() > this.currentPage || (this.nextPage() < this.firstPage);
54456      } else {
54457        return (this.totalPages == null) || this.nextPage() <= this.totalPages;
54458      }
54459    };
54460
54461    InfiniteScroller.prototype.nextPage = function() {
54462      if (this.loopPages && (this.totalPages != null) && this.currentPage + 1 > this.totalPages) {
54463        return 1;
54464      } else {
54465        return this.currentPage + 1;
54466      }
54467    };
54468
54469    InfiniteScroller.prototype.fetchNewPage = function() {
54470      if (!(this.canFetch && this.hasNextPage())) {
54471        return;
54472      }
54473      this.loader.fadeIn();
54474      this.canFetch = false;
54475      this.loader.fadeIn();
54476      this.currentPage = this.nextPage();
54477      if (this.currentPage === 1) {
54478        this.pagesOverflow = true;
54479      }
54480      return $.ajax({
54481        url: this.loadPath,
54482        data: {
54483          page: this.currentPage,
54484          skip_layout: true
54485        },
54486        complete: (function(_this) {
54487          return function(data) {
54488            _this.loader.fadeOut();
54489            _.each(_this.afterFetchCallbacks, function(callback) {
54490              return typeof callback === "function" ? callback(data.responseText) : void 0;
54491            });
54492            return _this.canFetch = true;
54493          };
54494        })(this)
54495      });
54496    };
54497
54498    InfiniteScroller.prototype.newPage = function(html) {
54499      _.each(this.afterFetchCallbacks, function(callback) {
54500        return typeof callback === "function" ? callback(html) : void 0;
54501      });
54502      return this.canFetch = true;
54503    };
54504
54505    InfiniteScroller.prototype.afterNewPageFetch = function(callback) {
54506      return this.afterFetchCallbacks.push(callback);
54507    };
54508
54509    return InfiniteScroller;
54510
54511  })();
54512
54513}).call(this);
54514(function() {
54515  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
54516    hasProp = {}.hasOwnProperty;
54517
54518  App.Pages.GalleriesIndex = (function(superClass) {
54519    extend(GalleriesIndex, superClass);
54520
54521    function GalleriesIndex() {
54522      return GalleriesIndex.__super__.constructor.apply(this, arguments);
54523    }
54524
54525    GalleriesIndex.prototype.widgets = ['likes'];
54526
54527    GalleriesIndex.prototype.initialize = function() {
54528      this.infiniteScroll = new App.Modules.InfiniteScroller('.js-infinite-scroll-trigger');
54529      this.initPlugins();
54530      return this.bindEvents();
54531    };
54532
54533    GalleriesIndex.prototype.bindEvents = function() {
54534      $('.js-category-link, .js-tabs li, .js-link-to-content').on('click', this.showUpdatingSpinner);
54535      return this.infiniteScroll.afterNewPageFetch((function(_this) {
54536        return function(html) {
54537          return _this.initializeNewItems(html);
54538        };
54539      })(this));
54540    };
54541
54542    GalleriesIndex.prototype.initPlugins = function() {
54543      this.$container = $('.js-gallery-items');
54544      return imagesLoaded(this.$container, (function(_this) {
54545        return function() {
54546          var items;
54547          _this.$container.masonry({
54548            selector: '.js-gallery-item'
54549          });
54550          items = _this.$container.masonry('getItemElements');
54551          _this.masonryItems = _.map(items, function(item) {
54552            return _this.$container.masonry('getItem', item);
54553          });
54554          return _this.$container.masonry('layout');
54555        };
54556      })(this));
54557    };
54558
54559    GalleriesIndex.prototype.initializeNewItems = function(html) {
54560      var $newItems;
54561      $newItems = $(html);
54562      return imagesLoaded($newItems, (function(_this) {
54563        return function() {
54564          _this.$container.append($newItems).masonry('appended', $newItems, true);
54565          return App.events.publish('app:likes:bind', $newItems);
54566        };
54567      })(this));
54568    };
54569
54570    GalleriesIndex.prototype.showUpdatingSpinner = function() {
54571      return $('.js-main-content').addClass('is-updating-with-spinner spinner--top is-fixed');
54572    };
54573
54574    return GalleriesIndex;
54575
54576  })(App.Core.Page);
54577
54578}).call(this);
54579(function() {
54580  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
54581    hasProp = {}.hasOwnProperty;
54582
54583  App.Pages.GalleriesShow = (function(superClass) {
54584    extend(GalleriesShow, superClass);
54585
54586    function GalleriesShow() {
54587      return GalleriesShow.__super__.constructor.apply(this, arguments);
54588    }
54589
54590    GalleriesShow.prototype.widgets = ['comments', 'likes'];
54591
54592    return GalleriesShow;
54593
54594  })(App.Core.Page);
54595
54596}).call(this);
54597(function() {
54598  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
54599    hasProp = {}.hasOwnProperty;
54600
54601  App.Pages.Global = (function(superClass) {
54602    extend(Global, superClass);
54603
54604    function Global() {
54605      return Global.__super__.constructor.apply(this, arguments);
54606    }
54607
54608    Global.prototype.widgets = ['anchorsManager', 'authControl', 'pinger', 'notification', 'autoDisableLink', 'backendEvents', 'buyButton', 'cookiesPolicy', 'pushNotifications', 'paymentAgreement', 'tipForDesigner', 'topNavigation', 'searchItems', 'potentialBuyerManager', 'translationsNotice'];
54609
54610    return Global;
54611
54612  })(App.Core.Page);
54613
54614}).call(this);
54615(function() {
54616  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
54617    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
54618    hasProp = {}.hasOwnProperty;
54619
54620  App.Pages.HomeIndex = (function(superClass) {
54621    extend(HomeIndex, superClass);
54622
54623    function HomeIndex() {
54624      this.carouselNext = bind(this.carouselNext, this);
54625      this.carouselPrevious = bind(this.carouselPrevious, this);
54626      return HomeIndex.__super__.constructor.apply(this, arguments);
54627    }
54628
54629    HomeIndex.prototype.widgets = ['linkFaker', 'categoriesContainerTracking'];
54630
54631    HomeIndex.prototype.initialize = function() {
54632      this.bindEvents();
54633      this.initCarousel();
54634      this.openSupportWidget();
54635      return new LazyLoad({
54636        elements_selector: '.js-lazy-img'
54637      });
54638    };
54639
54640    HomeIndex.prototype.bindEvents = function() {
54641      $('.js-header-block').hover(this.onHeaderBlockHover, this.onHeaderBlockHoverOut);
54642      $('.js-carousel-previous').click(this.carouselPrevious);
54643      return $('.js-carousel-next').click(this.carouselNext);
54644    };
54645
54646    HomeIndex.prototype.onHeaderBlockHover = function(e) {
54647      var $target;
54648      $target = $($(e.currentTarget).data('target'));
54649      return $target.addClass('is-hover');
54650    };
54651
54652    HomeIndex.prototype.onHeaderBlockHoverOut = function(e) {
54653      var $target;
54654      $target = $($(e.currentTarget).data('target'));
54655      return $target.removeClass('is-hover');
54656    };
54657
54658    HomeIndex.prototype.initCarousel = function() {
54659      var itemWidth;
54660      itemWidth = $('.category-item').width();
54661      return this.properties = {
54662        carouselWidth: itemWidth * $('.category-item').length,
54663        containerWidth: $('.categories-carousel').width(),
54664        items: $('.category-item').length,
54665        itemWidth: itemWidth,
54666        offsetWidth: $('.carousel__nav-left').width(),
54667        left: 0,
54668        step: itemWidth * 3
54669      };
54670    };
54671
54672    HomeIndex.prototype.carouselPrevious = function(e) {
54673      e.preventDefault();
54674      return this.moveCarousel('left');
54675    };
54676
54677    HomeIndex.prototype.carouselNext = function(e) {
54678      e.preventDefault();
54679      return this.moveCarousel('right');
54680    };
54681
54682    HomeIndex.prototype.moveCarousel = function(direction) {
54683      var leftPosition, offset;
54684      offset = 0;
54685      if (-this.properties.left === 0) {
54686        offset = this.properties.offsetWidth;
54687      }
54688      if (direction === 'left') {
54689        if (-this.properties.left > this.properties.step) {
54690          leftPosition = this.properties.left + this.properties.step;
54691        } else {
54692          leftPosition = 0;
54693        }
54694      } else {
54695        if ((this.properties.carouselWidth + this.properties.left) > (this.properties.containerWidth + this.properties.step)) {
54696          leftPosition = this.properties.left - this.properties.step + offset;
54697        } else {
54698          leftPosition = this.properties.left - (this.properties.carouselWidth + this.properties.left - this.properties.containerWidth);
54699        }
54700      }
54701      this.properties.left = leftPosition;
54702      $('.categories-list').css({
54703        left: leftPosition
54704      });
54705      return this.evalCarouselParams();
54706    };
54707
54708    HomeIndex.prototype.evalCarouselParams = function() {
54709      if (this.properties.left < 0) {
54710        $('.carousel__nav-left').css({
54711          display: 'inline'
54712        });
54713      } else {
54714        $('.carousel__nav-left').css({
54715          display: 'none'
54716        });
54717      }
54718      if ((this.properties.carouselWidth + this.properties.left) === this.properties.containerWidth) {
54719        return $('.carousel__nav-right').css({
54720          display: 'none'
54721        });
54722      } else {
54723        return $('.carousel__nav-right').css({
54724          display: 'inline'
54725        });
54726      }
54727    };
54728
54729    HomeIndex.prototype.openSupportWidget = function() {
54730      if (window.location.hash === '#support' && typeof zE !== "undefined") {
54731        return setTimeout(function() {
54732          return zE('webWidget', 'open');
54733        }, 2000);
54734      } else {
54735        return setTimeout((function(_this) {
54736          return function() {
54737            return _this.openSupportWidget();
54738          };
54739        })(this), 1000);
54740      }
54741    };
54742
54743    return HomeIndex;
54744
54745  })(App.Core.Page);
54746
54747}).call(this);
54748(function() {
54749  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
54750    hasProp = {}.hasOwnProperty;
54751
54752  App.Pages.InstantCheckoutPayment = (function(superClass) {
54753    extend(InstantCheckoutPayment, superClass);
54754
54755    function InstantCheckoutPayment() {
54756      return InstantCheckoutPayment.__super__.constructor.apply(this, arguments);
54757    }
54758
54759    InstantCheckoutPayment.prototype.initialize = function() {
54760      this.initBuyerType();
54761      this.bindApplyCompanyInformation();
54762      this.bindTabClick();
54763      this.openRequiredTab();
54764      return this.initCountrySelection();
54765    };
54766
54767    return InstantCheckoutPayment;
54768
54769  })(App.Pages.CheckoutPayment);
54770
54771}).call(this);
54772(function() {
54773  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
54774    hasProp = {}.hasOwnProperty;
54775
54776  App.Pages.InstantCheckoutPlaceOrder = (function(superClass) {
54777    extend(InstantCheckoutPlaceOrder, superClass);
54778
54779    function InstantCheckoutPlaceOrder() {
54780      return InstantCheckoutPlaceOrder.__super__.constructor.apply(this, arguments);
54781    }
54782
54783    return InstantCheckoutPlaceOrder;
54784
54785  })(App.Pages.InstantCheckoutPayment);
54786
54787}).call(this);
54788(function() {
54789  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
54790    hasProp = {}.hasOwnProperty;
54791
54792  App.Pages.ItemFilesIndex = (function(superClass) {
54793    extend(ItemFilesIndex, superClass);
54794
54795    function ItemFilesIndex() {
54796      return ItemFilesIndex.__super__.constructor.apply(this, arguments);
54797    }
54798
54799    ItemFilesIndex.prototype.widgets = ['freeDownloadLink'];
54800
54801    ItemFilesIndex.prototype.initialize = function() {
54802      var $countdown, counter, fadeInDelay, fadeOutDelay;
54803      counter = App.config.environment === 'test' ? 1 : 20;
54804      $countdown = $('.countdown');
54805      fadeOutDelay = App.config.environment === 'test' ? 1000 : 20000;
54806      fadeInDelay = App.config.environment === 'test' ? 1600 : 20600;
54807      setInterval((function() {
54808        counter--;
54809        if (counter < 0) {
54810          clearInterval;
54811        } else {
54812          $countdown.text("Please wait " + counter + " seconds...");
54813        }
54814      }), 1000);
54815      $('.preloader--free-models').delay(fadeOutDelay).fadeOut('slow');
54816      $('.details-box').delay(fadeInDelay).removeClass('is-hidden').fadeIn('slow');
54817      return this.initiateSimilarProductsLoader();
54818    };
54819
54820    ItemFilesIndex.prototype.initiateSimilarProductsLoader = function() {
54821      return $.getScript("/items/" + gon.product_id + "/similar-products");
54822    };
54823
54824    return ItemFilesIndex;
54825
54826  })(App.Core.Page);
54827
54828}).call(this);
54829/*! nouislider - 8.5.1 - 2016-04-24 16:00:29 */
54830
54831
54832(function (factory) {
54833
54834    if ( typeof define === 'function' && define.amd ) {
54835
54836        // AMD. Register as an anonymous module.
54837        define([], factory);
54838
54839    } else if ( typeof exports === 'object' ) {
54840
54841        // Node/CommonJS
54842        module.exports = factory();
54843
54844    } else {
54845
54846        // Browser globals
54847        window.noUiSlider = factory();
54848    }
54849
54850}(function( ){
54851
54852  'use strict';
54853
54854
54855  // Removes duplicates from an array.
54856  function unique(array) {
54857    return array.filter(function(a){
54858      return !this[a] ? this[a] = true : false;
54859    }, {});
54860  }
54861
54862  // Round a value to the closest 'to'.
54863  function closest ( value, to ) {
54864    return Math.round(value / to) * to;
54865  }
54866
54867  // Current position of an element relative to the document.
54868  function offset ( elem ) {
54869
54870  var rect = elem.getBoundingClientRect(),
54871    doc = elem.ownerDocument,
54872    docElem = doc.documentElement,
54873    pageOffset = getPageOffset();
54874
54875    // getBoundingClientRect contains left scroll in Chrome on Android.
54876    // I haven't found a feature detection that proves this. Worst case
54877    // scenario on mis-match: the 'tap' feature on horizontal sliders breaks.
54878    if ( /webkit.*Chrome.*Mobile/i.test(navigator.userAgent) ) {
54879      pageOffset.x = 0;
54880    }
54881
54882    return {
54883      top: rect.top + pageOffset.y - docElem.clientTop,
54884      left: rect.left + pageOffset.x - docElem.clientLeft
54885    };
54886  }
54887
54888  // Checks whether a value is numerical.
54889  function isNumeric ( a ) {
54890    return typeof a === 'number' && !isNaN( a ) && isFinite( a );
54891  }
54892
54893  // Sets a class and removes it after [duration] ms.
54894  function addClassFor ( element, className, duration ) {
54895    addClass(element, className);
54896    setTimeout(function(){
54897      removeClass(element, className);
54898    }, duration);
54899  }
54900
54901  // Limits a value to 0 - 100
54902  function limit ( a ) {
54903    return Math.max(Math.min(a, 100), 0);
54904  }
54905
54906  // Wraps a variable as an array, if it isn't one yet.
54907  function asArray ( a ) {
54908    return Array.isArray(a) ? a : [a];
54909  }
54910
54911  // Counts decimals
54912  function countDecimals ( numStr ) {
54913    var pieces = numStr.split(".");
54914    return pieces.length > 1 ? pieces[1].length : 0;
54915  }
54916
54917  // http://youmightnotneedjquery.com/#add_class
54918  function addClass ( el, className ) {
54919    if ( el.classList ) {
54920      el.classList.add(className);
54921    } else {
54922      el.className += ' ' + className;
54923    }
54924  }
54925
54926  // http://youmightnotneedjquery.com/#remove_class
54927  function removeClass ( el, className ) {
54928    if ( el.classList ) {
54929      el.classList.remove(className);
54930    } else {
54931      el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
54932    }
54933  }
54934
54935  // https://plainjs.com/javascript/attributes/adding-removing-and-testing-for-classes-9/
54936  function hasClass ( el, className ) {
54937    return el.classList ? el.classList.contains(className) : new RegExp('\\b' + className + '\\b').test(el.className);
54938  }
54939
54940  // https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes
54941  function getPageOffset ( ) {
54942
54943    var supportPageOffset = window.pageXOffset !== undefined,
54944      isCSS1Compat = ((document.compatMode || "") === "CSS1Compat"),
54945      x = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft,
54946      y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop;
54947
54948    return {
54949      x: x,
54950      y: y
54951    };
54952  }
54953
54954  // we provide a function to compute constants instead
54955  // of accessing window.* as soon as the module needs it
54956  // so that we do not compute anything if not needed
54957  function getActions ( ) {
54958
54959    // Determine the events to bind. IE11 implements pointerEvents without
54960    // a prefix, which breaks compatibility with the IE10 implementation.
54961    return window.navigator.pointerEnabled ? {
54962      start: 'pointerdown',
54963      move: 'pointermove',
54964      end: 'pointerup'
54965    } : window.navigator.msPointerEnabled ? {
54966      start: 'MSPointerDown',
54967      move: 'MSPointerMove',
54968      end: 'MSPointerUp'
54969    } : {
54970      start: 'mousedown touchstart',
54971      move: 'mousemove touchmove',
54972      end: 'mouseup touchend'
54973    };
54974  }
54975
54976
54977// Value calculation
54978
54979  // Determine the size of a sub-range in relation to a full range.
54980  function subRangeRatio ( pa, pb ) {
54981    return (100 / (pb - pa));
54982  }
54983
54984  // (percentage) How many percent is this value of this range?
54985  function fromPercentage ( range, value ) {
54986    return (value * 100) / ( range[1] - range[0] );
54987  }
54988
54989  // (percentage) Where is this value on this range?
54990  function toPercentage ( range, value ) {
54991    return fromPercentage( range, range[0] < 0 ?
54992      value + Math.abs(range[0]) :
54993        value - range[0] );
54994  }
54995
54996  // (value) How much is this percentage on this range?
54997  function isPercentage ( range, value ) {
54998    return ((value * ( range[1] - range[0] )) / 100) + range[0];
54999  }
55000
55001
55002// Range conversion
55003
55004  function getJ ( value, arr ) {
55005
55006    var j = 1;
55007
55008    while ( value >= arr[j] ){
55009      j += 1;
55010    }
55011
55012    return j;
55013  }
55014
55015  // (percentage) Input a value, find where, on a scale of 0-100, it applies.
55016  function toStepping ( xVal, xPct, value ) {
55017
55018    if ( value >= xVal.slice(-1)[0] ){
55019      return 100;
55020    }
55021
55022    var j = getJ( value, xVal ), va, vb, pa, pb;
55023
55024    va = xVal[j-1];
55025    vb = xVal[j];
55026    pa = xPct[j-1];
55027    pb = xPct[j];
55028
55029    return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb));
55030  }
55031
55032  // (value) Input a percentage, find where it is on the specified range.
55033  function fromStepping ( xVal, xPct, value ) {
55034
55035    // There is no range group that fits 100
55036    if ( value >= 100 ){
55037      return xVal.slice(-1)[0];
55038    }
55039
55040    var j = getJ( value, xPct ), va, vb, pa, pb;
55041
55042    va = xVal[j-1];
55043    vb = xVal[j];
55044    pa = xPct[j-1];
55045    pb = xPct[j];
55046
55047    return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb));
55048  }
55049
55050  // (percentage) Get the step that applies at a certain value.
55051  function getStep ( xPct, xSteps, snap, value ) {
55052
55053    if ( value === 100 ) {
55054      return value;
55055    }
55056
55057    var j = getJ( value, xPct ), a, b;
55058
55059    // If 'snap' is set, steps are used as fixed points on the slider.
55060    if ( snap ) {
55061
55062      a = xPct[j-1];
55063      b = xPct[j];
55064
55065      // Find the closest position, a or b.
55066      if ((value - a) > ((b-a)/2)){
55067        return b;
55068      }
55069
55070      return a;
55071    }
55072
55073    if ( !xSteps[j-1] ){
55074      return value;
55075    }
55076
55077    return xPct[j-1] + closest(
55078      value - xPct[j-1],
55079      xSteps[j-1]
55080    );
55081  }
55082
55083
55084// Entry parsing
55085
55086  function handleEntryPoint ( index, value, that ) {
55087
55088    var percentage;
55089
55090    // Wrap numerical input in an array.
55091    if ( typeof value === "number" ) {
55092      value = [value];
55093    }
55094
55095    // Reject any invalid input, by testing whether value is an array.
55096    if ( Object.prototype.toString.call( value ) !== '[object Array]' ){
55097      throw new Error("noUiSlider: 'range' contains invalid value.");
55098    }
55099
55100    // Covert min/max syntax to 0 and 100.
55101    if ( index === 'min' ) {
55102      percentage = 0;
55103    } else if ( index === 'max' ) {
55104      percentage = 100;
55105    } else {
55106      percentage = parseFloat( index );
55107    }
55108
55109    // Check for correct input.
55110    if ( !isNumeric( percentage ) || !isNumeric( value[0] ) ) {
55111      throw new Error("noUiSlider: 'range' value isn't numeric.");
55112    }
55113
55114    // Store values.
55115    that.xPct.push( percentage );
55116    that.xVal.push( value[0] );
55117
55118    // NaN will evaluate to false too, but to keep
55119    // logging clear, set step explicitly. Make sure
55120    // not to override the 'step' setting with false.
55121    if ( !percentage ) {
55122      if ( !isNaN( value[1] ) ) {
55123        that.xSteps[0] = value[1];
55124      }
55125    } else {
55126      that.xSteps.push( isNaN(value[1]) ? false : value[1] );
55127    }
55128  }
55129
55130  function handleStepPoint ( i, n, that ) {
55131
55132    // Ignore 'false' stepping.
55133    if ( !n ) {
55134      return true;
55135    }
55136
55137    // Factor to range ratio
55138    that.xSteps[i] = fromPercentage([
55139       that.xVal[i]
55140      ,that.xVal[i+1]
55141    ], n) / subRangeRatio (
55142      that.xPct[i],
55143      that.xPct[i+1] );
55144  }
55145
55146
55147// Interface
55148
55149  // The interface to Spectrum handles all direction-based
55150  // conversions, so the above values are unaware.
55151
55152  function Spectrum ( entry, snap, direction, singleStep ) {
55153
55154    this.xPct = [];
55155    this.xVal = [];
55156    this.xSteps = [ singleStep || false ];
55157    this.xNumSteps = [ false ];
55158
55159    this.snap = snap;
55160    this.direction = direction;
55161
55162    var index, ordered = [ /* [0, 'min'], [1, '50%'], [2, 'max'] */ ];
55163
55164    // Map the object keys to an array.
55165    for ( index in entry ) {
55166      if ( entry.hasOwnProperty(index) ) {
55167        ordered.push([entry[index], index]);
55168      }
55169    }
55170
55171    // Sort all entries by value (numeric sort).
55172    if ( ordered.length && typeof ordered[0][0] === "object" ) {
55173      ordered.sort(function(a, b) { return a[0][0] - b[0][0]; });
55174    } else {
55175      ordered.sort(function(a, b) { return a[0] - b[0]; });
55176    }
55177
55178
55179    // Convert all entries to subranges.
55180    for ( index = 0; index < ordered.length; index++ ) {
55181      handleEntryPoint(ordered[index][1], ordered[index][0], this);
55182    }
55183
55184    // Store the actual step values.
55185    // xSteps is sorted in the same order as xPct and xVal.
55186    this.xNumSteps = this.xSteps.slice(0);
55187
55188    // Convert all numeric steps to the percentage of the subrange they represent.
55189    for ( index = 0; index < this.xNumSteps.length; index++ ) {
55190      handleStepPoint(index, this.xNumSteps[index], this);
55191    }
55192  }
55193
55194  Spectrum.prototype.getMargin = function ( value ) {
55195    return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false;
55196  };
55197
55198  Spectrum.prototype.toStepping = function ( value ) {
55199
55200    value = toStepping( this.xVal, this.xPct, value );
55201
55202    // Invert the value if this is a right-to-left slider.
55203    if ( this.direction ) {
55204      value = 100 - value;
55205    }
55206
55207    return value;
55208  };
55209
55210  Spectrum.prototype.fromStepping = function ( value ) {
55211
55212    // Invert the value if this is a right-to-left slider.
55213    if ( this.direction ) {
55214      value = 100 - value;
55215    }
55216
55217    return fromStepping( this.xVal, this.xPct, value );
55218  };
55219
55220  Spectrum.prototype.getStep = function ( value ) {
55221
55222    // Find the proper step for rtl sliders by search in inverse direction.
55223    // Fixes issue #262.
55224    if ( this.direction ) {
55225      value = 100 - value;
55226    }
55227
55228    value = getStep(this.xPct, this.xSteps, this.snap, value );
55229
55230    if ( this.direction ) {
55231      value = 100 - value;
55232    }
55233
55234    return value;
55235  };
55236
55237  Spectrum.prototype.getApplicableStep = function ( value ) {
55238
55239    // If the value is 100%, return the negative step twice.
55240    var j = getJ(value, this.xPct), offset = value === 100 ? 2 : 1;
55241    return [this.xNumSteps[j-2], this.xVal[j-offset], this.xNumSteps[j-offset]];
55242  };
55243
55244  // Outside testing
55245  Spectrum.prototype.convert = function ( value ) {
55246    return this.getStep(this.toStepping(value));
55247  };
55248
55249/*  Every input option is tested and parsed. This'll prevent
55250  endless validation in internal methods. These tests are
55251  structured with an item for every option available. An
55252  option can be marked as required by setting the 'r' flag.
55253  The testing function is provided with three arguments:
55254    - The provided value for the option;
55255    - A reference to the options object;
55256    - The name for the option;
55257
55258  The testing function returns false when an error is detected,
55259  or true when everything is OK. It can also modify the option
55260  object, to make sure all values can be correctly looped elsewhere. */
55261
55262  var defaultFormatter = { 'to': function( value ){
55263    return value !== undefined && value.toFixed(2);
55264  }, 'from': Number };
55265
55266  function testStep ( parsed, entry ) {
55267
55268    if ( !isNumeric( entry ) ) {
55269      throw new Error("noUiSlider: 'step' is not numeric.");
55270    }
55271
55272    // The step option can still be used to set stepping
55273    // for linear sliders. Overwritten if set in 'range'.
55274    parsed.singleStep = entry;
55275  }
55276
55277  function testRange ( parsed, entry ) {
55278
55279    // Filter incorrect input.
55280    if ( typeof entry !== 'object' || Array.isArray(entry) ) {
55281      throw new Error("noUiSlider: 'range' is not an object.");
55282    }
55283
55284    // Catch missing start or end.
55285    if ( entry.min === undefined || entry.max === undefined ) {
55286      throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'.");
55287    }
55288
55289    // Catch equal start or end.
55290    if ( entry.min === entry.max ) {
55291      throw new Error("noUiSlider: 'range' 'min' and 'max' cannot be equal.");
55292    }
55293
55294    parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.dir, parsed.singleStep);
55295  }
55296
55297  function testStart ( parsed, entry ) {
55298
55299    entry = asArray(entry);
55300
55301    // Validate input. Values aren't tested, as the public .val method
55302    // will always provide a valid location.
55303    if ( !Array.isArray( entry ) || !entry.length || entry.length > 2 ) {
55304      throw new Error("noUiSlider: 'start' option is incorrect.");
55305    }
55306
55307    // Store the number of handles.
55308    parsed.handles = entry.length;
55309
55310    // When the slider is initialized, the .val method will
55311    // be called with the start options.
55312    parsed.start = entry;
55313  }
55314
55315  function testSnap ( parsed, entry ) {
55316
55317    // Enforce 100% stepping within subranges.
55318    parsed.snap = entry;
55319
55320    if ( typeof entry !== 'boolean' ){
55321      throw new Error("noUiSlider: 'snap' option must be a boolean.");
55322    }
55323  }
55324
55325  function testAnimate ( parsed, entry ) {
55326
55327    // Enforce 100% stepping within subranges.
55328    parsed.animate = entry;
55329
55330    if ( typeof entry !== 'boolean' ){
55331      throw new Error("noUiSlider: 'animate' option must be a boolean.");
55332    }
55333  }
55334
55335  function testAnimationDuration ( parsed, entry ) {
55336
55337    parsed.animationDuration = entry;
55338
55339    if ( typeof entry !== 'number' ){
55340      throw new Error("noUiSlider: 'animationDuration' option must be a number.");
55341    }
55342  }
55343
55344  function testConnect ( parsed, entry ) {
55345
55346    if ( entry === 'lower' && parsed.handles === 1 ) {
55347      parsed.connect = 1;
55348    } else if ( entry === 'upper' && parsed.handles === 1 ) {
55349      parsed.connect = 2;
55350    } else if ( entry === true && parsed.handles === 2 ) {
55351      parsed.connect = 3;
55352    } else if ( entry === false ) {
55353      parsed.connect = 0;
55354    } else {
55355      throw new Error("noUiSlider: 'connect' option doesn't match handle count.");
55356    }
55357  }
55358
55359  function testOrientation ( parsed, entry ) {
55360
55361    // Set orientation to an a numerical value for easy
55362    // array selection.
55363    switch ( entry ){
55364      case 'horizontal':
55365      parsed.ort = 0;
55366      break;
55367      case 'vertical':
55368      parsed.ort = 1;
55369      break;
55370      default:
55371      throw new Error("noUiSlider: 'orientation' option is invalid.");
55372    }
55373  }
55374
55375  function testMargin ( parsed, entry ) {
55376
55377    if ( !isNumeric(entry) ){
55378      throw new Error("noUiSlider: 'margin' option must be numeric.");
55379    }
55380
55381    // Issue #582
55382    if ( entry === 0 ) {
55383      return;
55384    }
55385
55386    parsed.margin = parsed.spectrum.getMargin(entry);
55387
55388    if ( !parsed.margin ) {
55389      throw new Error("noUiSlider: 'margin' option is only supported on linear sliders.");
55390    }
55391  }
55392
55393  function testLimit ( parsed, entry ) {
55394
55395    if ( !isNumeric(entry) ){
55396      throw new Error("noUiSlider: 'limit' option must be numeric.");
55397    }
55398
55399    parsed.limit = parsed.spectrum.getMargin(entry);
55400
55401    if ( !parsed.limit ) {
55402      throw new Error("noUiSlider: 'limit' option is only supported on linear sliders.");
55403    }
55404  }
55405
55406  function testDirection ( parsed, entry ) {
55407
55408    // Set direction as a numerical value for easy parsing.
55409    // Invert connection for RTL sliders, so that the proper
55410    // handles get the connect/background classes.
55411    switch ( entry ) {
55412      case 'ltr':
55413      parsed.dir = 0;
55414      break;
55415      case 'rtl':
55416      parsed.dir = 1;
55417      parsed.connect = [0,2,1,3][parsed.connect];
55418      break;
55419      default:
55420      throw new Error("noUiSlider: 'direction' option was not recognized.");
55421    }
55422  }
55423
55424  function testBehaviour ( parsed, entry ) {
55425
55426    // Make sure the input is a string.
55427    if ( typeof entry !== 'string' ) {
55428      throw new Error("noUiSlider: 'behaviour' must be a string containing options.");
55429    }
55430
55431    // Check if the string contains any keywords.
55432    // None are required.
55433    var tap = entry.indexOf('tap') >= 0,
55434      drag = entry.indexOf('drag') >= 0,
55435      fixed = entry.indexOf('fixed') >= 0,
55436      snap = entry.indexOf('snap') >= 0,
55437      hover = entry.indexOf('hover') >= 0;
55438
55439    // Fix #472
55440    if ( drag && !parsed.connect ) {
55441      throw new Error("noUiSlider: 'drag' behaviour must be used with 'connect': true.");
55442    }
55443
55444    parsed.events = {
55445      tap: tap || snap,
55446      drag: drag,
55447      fixed: fixed,
55448      snap: snap,
55449      hover: hover
55450    };
55451  }
55452
55453  function testTooltips ( parsed, entry ) {
55454
55455    var i;
55456
55457    if ( entry === false ) {
55458      return;
55459    } else if ( entry === true ) {
55460
55461      parsed.tooltips = [];
55462
55463      for ( i = 0; i < parsed.handles; i++ ) {
55464        parsed.tooltips.push(true);
55465      }
55466
55467    } else {
55468
55469      parsed.tooltips = asArray(entry);
55470
55471      if ( parsed.tooltips.length !== parsed.handles ) {
55472        throw new Error("noUiSlider: must pass a formatter for all handles.");
55473      }
55474
55475      parsed.tooltips.forEach(function(formatter){
55476        if ( typeof formatter !== 'boolean' && (typeof formatter !== 'object' || typeof formatter.to !== 'function') ) {
55477          throw new Error("noUiSlider: 'tooltips' must be passed a formatter or 'false'.");
55478        }
55479      });
55480    }
55481  }
55482
55483  function testFormat ( parsed, entry ) {
55484
55485    parsed.format = entry;
55486
55487    // Any object with a to and from method is supported.
55488    if ( typeof entry.to === 'function' && typeof entry.from === 'function' ) {
55489      return true;
55490    }
55491
55492    throw new Error("noUiSlider: 'format' requires 'to' and 'from' methods.");
55493  }
55494
55495  function testCssPrefix ( parsed, entry ) {
55496
55497    if ( entry !== undefined && typeof entry !== 'string' && entry !== false ) {
55498      throw new Error("noUiSlider: 'cssPrefix' must be a string or `false`.");
55499    }
55500
55501    parsed.cssPrefix = entry;
55502  }
55503
55504  function testCssClasses ( parsed, entry ) {
55505
55506    if ( entry !== undefined && typeof entry !== 'object' ) {
55507      throw new Error("noUiSlider: 'cssClasses' must be an object.");
55508    }
55509
55510    if ( typeof parsed.cssPrefix === 'string' ) {
55511      parsed.cssClasses = {};
55512
55513      for ( var key in entry ) {
55514        if ( !entry.hasOwnProperty(key) ) { continue; }
55515
55516        parsed.cssClasses[key] = parsed.cssPrefix + entry[key];
55517      }
55518    } else {
55519      parsed.cssClasses = entry;
55520    }
55521  }
55522
55523  // Test all developer settings and parse to assumption-safe values.
55524  function testOptions ( options ) {
55525
55526    // To prove a fix for #537, freeze options here.
55527    // If the object is modified, an error will be thrown.
55528    // Object.freeze(options);
55529
55530    var parsed = {
55531      margin: 0,
55532      limit: 0,
55533      animate: true,
55534      animationDuration: 300,
55535      format: defaultFormatter
55536    }, tests;
55537
55538    // Tests are executed in the order they are presented here.
55539    tests = {
55540      'step': { r: false, t: testStep },
55541      'start': { r: true, t: testStart },
55542      'connect': { r: true, t: testConnect },
55543      'direction': { r: true, t: testDirection },
55544      'snap': { r: false, t: testSnap },
55545      'animate': { r: false, t: testAnimate },
55546      'animationDuration': { r: false, t: testAnimationDuration },
55547      'range': { r: true, t: testRange },
55548      'orientation': { r: false, t: testOrientation },
55549      'margin': { r: false, t: testMargin },
55550      'limit': { r: false, t: testLimit },
55551      'behaviour': { r: true, t: testBehaviour },
55552      'format': { r: false, t: testFormat },
55553      'tooltips': { r: false, t: testTooltips },
55554      'cssPrefix': { r: false, t: testCssPrefix },
55555      'cssClasses': { r: false, t: testCssClasses }
55556    };
55557
55558    var defaults = {
55559      'connect': false,
55560      'direction': 'ltr',
55561      'behaviour': 'tap',
55562      'orientation': 'horizontal',
55563      'cssPrefix' : 'noUi-',
55564      'cssClasses': {
55565        target: 'target',
55566        base: 'base',
55567        origin: 'origin',
55568        handle: 'handle',
55569        handleLower: 'handle-lower',
55570        handleUpper: 'handle-upper',
55571        horizontal: 'horizontal',
55572        vertical: 'vertical',
55573        background: 'background',
55574        connect: 'connect',
55575        ltr: 'ltr',
55576        rtl: 'rtl',
55577        draggable: 'draggable',
55578        drag: 'state-drag',
55579        tap: 'state-tap',
55580        active: 'active',
55581        stacking: 'stacking',
55582        tooltip: 'tooltip',
55583        pips: 'pips',
55584        pipsHorizontal: 'pips-horizontal',
55585        pipsVertical: 'pips-vertical',
55586        marker: 'marker',
55587        markerHorizontal: 'marker-horizontal',
55588        markerVertical: 'marker-vertical',
55589        markerNormal: 'marker-normal',
55590        markerLarge: 'marker-large',
55591        markerSub: 'marker-sub',
55592        value: 'value',
55593        valueHorizontal: 'value-horizontal',
55594        valueVertical: 'value-vertical',
55595        valueNormal: 'value-normal',
55596        valueLarge: 'value-large',
55597        valueSub: 'value-sub'
55598      }
55599    };
55600
55601    // Run all options through a testing mechanism to ensure correct
55602    // input. It should be noted that options might get modified to
55603    // be handled properly. E.g. wrapping integers in arrays.
55604    Object.keys(tests).forEach(function( name ){
55605
55606      // If the option isn't set, but it is required, throw an error.
55607      if ( options[name] === undefined && defaults[name] === undefined ) {
55608
55609        if ( tests[name].r ) {
55610          throw new Error("noUiSlider: '" + name + "' is required.");
55611        }
55612
55613        return true;
55614      }
55615
55616      tests[name].t( parsed, options[name] === undefined ? defaults[name] : options[name] );
55617    });
55618
55619    // Forward pips options
55620    parsed.pips = options.pips;
55621
55622    // Pre-define the styles.
55623    parsed.style = parsed.ort ? 'top' : 'left';
55624
55625    return parsed;
55626  }
55627
55628
55629function closure ( target, options, originalOptions ){
55630  var
55631    actions = getActions( ),
55632    // All variables local to 'closure' are prefixed with 'scope_'
55633    scope_Target = target,
55634    scope_Locations = [-1, -1],
55635    scope_Base,
55636    scope_Handles,
55637    scope_Spectrum = options.spectrum,
55638    scope_Values = [],
55639    scope_Events = {},
55640    scope_Self;
55641
55642
55643  // Delimit proposed values for handle positions.
55644  function getPositions ( a, b, delimit ) {
55645
55646    // Add movement to current position.
55647    var c = a + b[0], d = a + b[1];
55648
55649    // Only alter the other position on drag,
55650    // not on standard sliding.
55651    if ( delimit ) {
55652      if ( c < 0 ) {
55653        d += Math.abs(c);
55654      }
55655      if ( d > 100 ) {
55656        c -= ( d - 100 );
55657      }
55658
55659      // Limit values to 0 and 100.
55660      return [limit(c), limit(d)];
55661    }
55662
55663    return [c,d];
55664  }
55665
55666  // Provide a clean event with standardized offset values.
55667  function fixEvent ( e, pageOffset ) {
55668
55669    // Prevent scrolling and panning on touch events, while
55670    // attempting to slide. The tap event also depends on this.
55671    e.preventDefault();
55672
55673    // Filter the event to register the type, which can be
55674    // touch, mouse or pointer. Offset changes need to be
55675    // made on an event specific basis.
55676    var touch = e.type.indexOf('touch') === 0,
55677      mouse = e.type.indexOf('mouse') === 0,
55678      pointer = e.type.indexOf('pointer') === 0,
55679      x,y, event = e;
55680
55681    // IE10 implemented pointer events with a prefix;
55682    if ( e.type.indexOf('MSPointer') === 0 ) {
55683      pointer = true;
55684    }
55685
55686    if ( touch ) {
55687      // noUiSlider supports one movement at a time,
55688      // so we can select the first 'changedTouch'.
55689      x = e.changedTouches[0].pageX;
55690      y = e.changedTouches[0].pageY;
55691    }
55692
55693    pageOffset = pageOffset || getPageOffset();
55694
55695    if ( mouse || pointer ) {
55696      x = e.clientX + pageOffset.x;
55697      y = e.clientY + pageOffset.y;
55698    }
55699
55700    event.pageOffset = pageOffset;
55701    event.points = [x, y];
55702    event.cursor = mouse || pointer; // Fix #435
55703
55704    return event;
55705  }
55706
55707  // Append a handle to the base.
55708  function addHandle ( direction, index ) {
55709
55710    var origin = document.createElement('div'),
55711      handle = document.createElement('div'),
55712      classModifier = [options.cssClasses.handleLower, options.cssClasses.handleUpper];
55713
55714    if ( direction ) {
55715      classModifier.reverse();
55716    }
55717
55718    addClass(handle, options.cssClasses.handle);
55719    addClass(handle, classModifier[index]);
55720
55721    addClass(origin, options.cssClasses.origin);
55722    origin.appendChild(handle);
55723
55724    return origin;
55725  }
55726
55727  // Add the proper connection classes.
55728  function addConnection ( connect, target, handles ) {
55729
55730    // Apply the required connection classes to the elements
55731    // that need them. Some classes are made up for several
55732    // segments listed in the class list, to allow easy
55733    // renaming and provide a minor compression benefit.
55734    switch ( connect ) {
55735      case 1: addClass(target, options.cssClasses.connect);
55736          addClass(handles[0], options.cssClasses.background);
55737          break;
55738      case 3: addClass(handles[1], options.cssClasses.background);
55739          /* falls through */
55740      case 2: addClass(handles[0], options.cssClasses.connect);
55741          /* falls through */
55742      case 0: addClass(target, options.cssClasses.background);
55743          break;
55744    }
55745  }
55746
55747  // Add handles to the slider base.
55748  function addHandles ( nrHandles, direction, base ) {
55749
55750    var index, handles = [];
55751
55752    // Append handles.
55753    for ( index = 0; index < nrHandles; index += 1 ) {
55754
55755      // Keep a list of all added handles.
55756      handles.push( base.appendChild(addHandle( direction, index )) );
55757    }
55758
55759    return handles;
55760  }
55761
55762  // Initialize a single slider.
55763  function addSlider ( direction, orientation, target ) {
55764
55765    // Apply classes and data to the target.
55766    addClass(target, options.cssClasses.target);
55767
55768    if ( direction === 0 ) {
55769      addClass(target, options.cssClasses.ltr);
55770    } else {
55771      addClass(target, options.cssClasses.rtl);
55772    }
55773
55774    if ( orientation === 0 ) {
55775      addClass(target, options.cssClasses.horizontal);
55776    } else {
55777      addClass(target, options.cssClasses.vertical);
55778    }
55779
55780    var div = document.createElement('div');
55781    addClass(div, options.cssClasses.base);
55782    target.appendChild(div);
55783    return div;
55784  }
55785
55786
55787  function addTooltip ( handle, index ) {
55788
55789    if ( !options.tooltips[index] ) {
55790      return false;
55791    }
55792
55793    var element = document.createElement('div');
55794    element.className = options.cssClasses.tooltip;
55795    return handle.firstChild.appendChild(element);
55796  }
55797
55798  // The tooltips option is a shorthand for using the 'update' event.
55799  function tooltips ( ) {
55800
55801    if ( options.dir ) {
55802      options.tooltips.reverse();
55803    }
55804
55805    // Tooltips are added with options.tooltips in original order.
55806    var tips = scope_Handles.map(addTooltip);
55807
55808    if ( options.dir ) {
55809      tips.reverse();
55810      options.tooltips.reverse();
55811    }
55812
55813    bindEvent('update', function(f, o, r) {
55814      if ( tips[o] ) {
55815        tips[o].innerHTML = options.tooltips[o] === true ? f[o] : options.tooltips[o].to(r[o]);
55816      }
55817    });
55818  }
55819
55820
55821  function getGroup ( mode, values, stepped ) {
55822
55823    // Use the range.
55824    if ( mode === 'range' || mode === 'steps' ) {
55825      return scope_Spectrum.xVal;
55826    }
55827
55828    if ( mode === 'count' ) {
55829
55830      // Divide 0 - 100 in 'count' parts.
55831      var spread = ( 100 / (values-1) ), v, i = 0;
55832      values = [];
55833
55834      // List these parts and have them handled as 'positions'.
55835      while ((v=i++*spread) <= 100 ) {
55836        values.push(v);
55837      }
55838
55839      mode = 'positions';
55840    }
55841
55842    if ( mode === 'positions' ) {
55843
55844      // Map all percentages to on-range values.
55845      return values.map(function( value ){
55846        return scope_Spectrum.fromStepping( stepped ? scope_Spectrum.getStep( value ) : value );
55847      });
55848    }
55849
55850    if ( mode === 'values' ) {
55851
55852      // If the value must be stepped, it needs to be converted to a percentage first.
55853      if ( stepped ) {
55854
55855        return values.map(function( value ){
55856
55857          // Convert to percentage, apply step, return to value.
55858          return scope_Spectrum.fromStepping( scope_Spectrum.getStep( scope_Spectrum.toStepping( value ) ) );
55859        });
55860
55861      }
55862
55863      // Otherwise, we can simply use the values.
55864      return values;
55865    }
55866  }
55867
55868  function generateSpread ( density, mode, group ) {
55869
55870    function safeIncrement(value, increment) {
55871      // Avoid floating point variance by dropping the smallest decimal places.
55872      return (value + increment).toFixed(7) / 1;
55873    }
55874
55875    var originalSpectrumDirection = scope_Spectrum.direction,
55876      indexes = {},
55877      firstInRange = scope_Spectrum.xVal[0],
55878      lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length-1],
55879      ignoreFirst = false,
55880      ignoreLast = false,
55881      prevPct = 0;
55882
55883    // This function loops the spectrum in an ltr linear fashion,
55884    // while the toStepping method is direction aware. Trick it into
55885    // believing it is ltr.
55886    scope_Spectrum.direction = 0;
55887
55888    // Create a copy of the group, sort it and filter away all duplicates.
55889    group = unique(group.slice().sort(function(a, b){ return a - b; }));
55890
55891    // Make sure the range starts with the first element.
55892    if ( group[0] !== firstInRange ) {
55893      group.unshift(firstInRange);
55894      ignoreFirst = true;
55895    }
55896
55897    // Likewise for the last one.
55898    if ( group[group.length - 1] !== lastInRange ) {
55899      group.push(lastInRange);
55900      ignoreLast = true;
55901    }
55902
55903    group.forEach(function ( current, index ) {
55904
55905      // Get the current step and the lower + upper positions.
55906      var step, i, q,
55907        low = current,
55908        high = group[index+1],
55909        newPct, pctDifference, pctPos, type,
55910        steps, realSteps, stepsize;
55911
55912      // When using 'steps' mode, use the provided steps.
55913      // Otherwise, we'll step on to the next subrange.
55914      if ( mode === 'steps' ) {
55915        step = scope_Spectrum.xNumSteps[ index ];
55916      }
55917
55918      // Default to a 'full' step.
55919      if ( !step ) {
55920        step = high-low;
55921      }
55922
55923      // Low can be 0, so test for false. If high is undefined,
55924      // we are at the last subrange. Index 0 is already handled.
55925      if ( low === false || high === undefined ) {
55926        return;
55927      }
55928
55929      // Find all steps in the subrange.
55930      for ( i = low; i <= high; i = safeIncrement(i, step) ) {
55931
55932        // Get the percentage value for the current step,
55933        // calculate the size for the subrange.
55934        newPct = scope_Spectrum.toStepping( i );
55935        pctDifference = newPct - prevPct;
55936
55937        steps = pctDifference / density;
55938        realSteps = Math.round(steps);
55939
55940        // This ratio represents the ammount of percentage-space a point indicates.
55941        // For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-devided.
55942        // Round the percentage offset to an even number, then divide by two
55943        // to spread the offset on both sides of the range.
55944        stepsize = pctDifference/realSteps;
55945
55946        // Divide all points evenly, adding the correct number to this subrange.
55947        // Run up to <= so that 100% gets a point, event if ignoreLast is set.
55948        for ( q = 1; q <= realSteps; q += 1 ) {
55949
55950          // The ratio between the rounded value and the actual size might be ~1% off.
55951          // Correct the percentage offset by the number of points
55952          // per subrange. density = 1 will result in 100 points on the
55953          // full range, 2 for 50, 4 for 25, etc.
55954          pctPos = prevPct + ( q * stepsize );
55955          indexes[pctPos.toFixed(5)] = ['x', 0];
55956        }
55957
55958        // Determine the point type.
55959        type = (group.indexOf(i) > -1) ? 1 : ( mode === 'steps' ? 2 : 0 );
55960
55961        // Enforce the 'ignoreFirst' option by overwriting the type for 0.
55962        if ( !index && ignoreFirst ) {
55963          type = 0;
55964        }
55965
55966        if ( !(i === high && ignoreLast)) {
55967          // Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value.
55968          indexes[newPct.toFixed(5)] = [i, type];
55969        }
55970
55971        // Update the percentage count.
55972        prevPct = newPct;
55973      }
55974    });
55975
55976    // Reset the spectrum.
55977    scope_Spectrum.direction = originalSpectrumDirection;
55978
55979    return indexes;
55980  }
55981
55982  function addMarking ( spread, filterFunc, formatter ) {
55983
55984    var element = document.createElement('div'),
55985      out = '',
55986      valueSizeClasses = [
55987        options.cssClasses.valueNormal,
55988        options.cssClasses.valueLarge,
55989        options.cssClasses.valueSub
55990      ],
55991      markerSizeClasses = [
55992        options.cssClasses.markerNormal,
55993        options.cssClasses.markerLarge,
55994        options.cssClasses.markerSub
55995      ],
55996      valueOrientationClasses = [
55997        options.cssClasses.valueHorizontal,
55998        options.cssClasses.valueVertical
55999      ],
56000      markerOrientationClasses = [
56001        options.cssClasses.markerHorizontal,
56002        options.cssClasses.markerVertical
56003      ];
56004
56005    addClass(element, options.cssClasses.pips);
56006    addClass(element, options.ort === 0 ? options.cssClasses.pipsHorizontal : options.cssClasses.pipsVertical);
56007
56008    function getClasses( type, source ){
56009      var a = source === options.cssClasses.value,
56010        orientationClasses = a ? valueOrientationClasses : markerOrientationClasses,
56011        sizeClasses = a ? valueSizeClasses : markerSizeClasses;
56012
56013      return source + ' ' + orientationClasses[options.ort] + ' ' + sizeClasses[type];
56014    }
56015
56016    function getTags( offset, source, values ) {
56017      return 'class="' + getClasses(values[1], source) + '" style="' + options.style + ': ' + offset + '%"';
56018    }
56019
56020    function addSpread ( offset, values ){
56021
56022      if ( scope_Spectrum.direction ) {
56023        offset = 100 - offset;
56024      }
56025
56026      // Apply the filter function, if it is set.
56027      values[1] = (values[1] && filterFunc) ? filterFunc(values[0], values[1]) : values[1];
56028
56029      // Add a marker for every point
56030      out += '<div ' + getTags(offset, options.cssClasses.marker, values) + '></div>';
56031
56032      // Values are only appended for points marked '1' or '2'.
56033      if ( values[1] ) {
56034        out += '<div ' + getTags(offset, options.cssClasses.value, values) + '>' + formatter.to(values[0]) + '</div>';
56035      }
56036    }
56037
56038    // Append all points.
56039    Object.keys(spread).forEach(function(a){
56040      addSpread(a, spread[a]);
56041    });
56042
56043    element.innerHTML = out;
56044
56045    return element;
56046  }
56047
56048  function pips ( grid ) {
56049
56050  var mode = grid.mode,
56051    density = grid.density || 1,
56052    filter = grid.filter || false,
56053    values = grid.values || false,
56054    stepped = grid.stepped || false,
56055    group = getGroup( mode, values, stepped ),
56056    spread = generateSpread( density, mode, group ),
56057    format = grid.format || {
56058      to: Math.round
56059    };
56060
56061    return scope_Target.appendChild(addMarking(
56062      spread,
56063      filter,
56064      format
56065    ));
56066  }
56067
56068
56069  // Shorthand for base dimensions.
56070  function baseSize ( ) {
56071    var rect = scope_Base.getBoundingClientRect(), alt = 'offset' + ['Width', 'Height'][options.ort];
56072    return options.ort === 0 ? (rect.width||scope_Base[alt]) : (rect.height||scope_Base[alt]);
56073  }
56074
56075  // External event handling
56076  function fireEvent ( event, handleNumber, tap ) {
56077
56078    var i;
56079
56080    // During initialization, do not fire events.
56081    for ( i = 0; i < options.handles; i++ ) {
56082      if ( scope_Locations[i] === -1 ) {
56083        return;
56084      }
56085    }
56086
56087    if ( handleNumber !== undefined && options.handles !== 1 ) {
56088      handleNumber = Math.abs(handleNumber - options.dir);
56089    }
56090
56091    Object.keys(scope_Events).forEach(function( targetEvent ) {
56092
56093      var eventType = targetEvent.split('.')[0];
56094
56095      if ( event === eventType ) {
56096        scope_Events[targetEvent].forEach(function( callback ) {
56097
56098          callback.call(
56099            // Use the slider public API as the scope ('this')
56100            scope_Self,
56101            // Return values as array, so arg_1[arg_2] is always valid.
56102            asArray(valueGet()),
56103            // Handle index, 0 or 1
56104            handleNumber,
56105            // Unformatted slider values
56106            asArray(inSliderOrder(Array.prototype.slice.call(scope_Values))),
56107            // Event is fired by tap, true or false
56108            tap || false,
56109            // Left offset of the handle, in relation to the slider
56110            scope_Locations
56111          );
56112        });
56113      }
56114    });
56115  }
56116
56117  // Returns the input array, respecting the slider direction configuration.
56118  function inSliderOrder ( values ) {
56119
56120    // If only one handle is used, return a single value.
56121    if ( values.length === 1 ){
56122      return values[0];
56123    }
56124
56125    if ( options.dir ) {
56126      return values.reverse();
56127    }
56128
56129    return values;
56130  }
56131
56132
56133  // Handler for attaching events trough a proxy.
56134  function attach ( events, element, callback, data ) {
56135
56136    // This function can be used to 'filter' events to the slider.
56137    // element is a node, not a nodeList
56138
56139    var method = function ( e ){
56140
56141      if ( scope_Target.hasAttribute('disabled') ) {
56142        return false;
56143      }
56144
56145      // Stop if an active 'tap' transition is taking place.
56146      if ( hasClass(scope_Target, options.cssClasses.tap) ) {
56147        return false;
56148      }
56149
56150      e = fixEvent(e, data.pageOffset);
56151
56152      // Ignore right or middle clicks on start #454
56153      if ( events === actions.start && e.buttons !== undefined && e.buttons > 1 ) {
56154        return false;
56155      }
56156
56157      // Ignore right or middle clicks on start #454
56158      if ( data.hover && e.buttons ) {
56159        return false;
56160      }
56161
56162      e.calcPoint = e.points[ options.ort ];
56163
56164      // Call the event handler with the event [ and additional data ].
56165      callback ( e, data );
56166
56167    }, methods = [];
56168
56169    // Bind a closure on the target for every event type.
56170    events.split(' ').forEach(function( eventName ){
56171      element.addEventListener(eventName, method, false);
56172      methods.push([eventName, method]);
56173    });
56174
56175    return methods;
56176  }
56177
56178  // Handle movement on document for handle and range drag.
56179  function move ( event, data ) {
56180
56181    // Fix #498
56182    // Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty).
56183    // https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero
56184    // IE9 has .buttons and .which zero on mousemove.
56185    // Firefox breaks the spec MDN defines.
56186    if ( navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0 ) {
56187      return end(event, data);
56188    }
56189
56190    var handles = data.handles || scope_Handles, positions, state = false,
56191      proposal = ((event.calcPoint - data.start) * 100) / data.baseSize,
56192      handleNumber = handles[0] === scope_Handles[0] ? 0 : 1, i;
56193
56194    // Calculate relative positions for the handles.
56195    positions = getPositions( proposal, data.positions, handles.length > 1);
56196
56197    state = setHandle ( handles[0], positions[handleNumber], handles.length === 1 );
56198
56199    if ( handles.length > 1 ) {
56200
56201      state = setHandle ( handles[1], positions[handleNumber?0:1], false ) || state;
56202
56203      if ( state ) {
56204        // fire for both handles
56205        for ( i = 0; i < data.handles.length; i++ ) {
56206          fireEvent('slide', i);
56207        }
56208      }
56209    } else if ( state ) {
56210      // Fire for a single handle
56211      fireEvent('slide', handleNumber);
56212    }
56213  }
56214
56215  // Unbind move events on document, call callbacks.
56216  function end ( event, data ) {
56217
56218    // The handle is no longer active, so remove the class.
56219    var active = scope_Base.querySelector( '.' + options.cssClasses.active ),
56220      handleNumber = data.handles[0] === scope_Handles[0] ? 0 : 1;
56221
56222    if ( active !== null ) {
56223      removeClass(active, options.cssClasses.active);
56224    }
56225
56226    // Remove cursor styles and text-selection events bound to the body.
56227    if ( event.cursor ) {
56228      document.body.style.cursor = '';
56229      document.body.removeEventListener('selectstart', document.body.noUiListener);
56230    }
56231
56232    var d = document.documentElement;
56233
56234    // Unbind the move and end events, which are added on 'start'.
56235    d.noUiListeners.forEach(function( c ) {
56236      d.removeEventListener(c[0], c[1]);
56237    });
56238
56239    // Remove dragging class.
56240    removeClass(scope_Target, options.cssClasses.drag);
56241
56242    // Fire the change and set events.
56243    fireEvent('set', handleNumber);
56244    fireEvent('change', handleNumber);
56245
56246    // If this is a standard handle movement, fire the end event.
56247    if ( data.handleNumber !== undefined ) {
56248      fireEvent('end', data.handleNumber);
56249    }
56250  }
56251
56252  // Fire 'end' when a mouse or pen leaves the document.
56253  function documentLeave ( event, data ) {
56254    if ( event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null ){
56255      end ( event, data );
56256    }
56257  }
56258
56259  // Bind move events on document.
56260  function start ( event, data ) {
56261
56262    var d = document.documentElement;
56263
56264    // Mark the handle as 'active' so it can be styled.
56265    if ( data.handles.length === 1 ) {
56266      // Support 'disabled' handles
56267      if ( data.handles[0].hasAttribute('disabled') ) {
56268        return false;
56269      }
56270
56271      addClass(data.handles[0].children[0], options.cssClasses.active);
56272    }
56273
56274    // Fix #551, where a handle gets selected instead of dragged.
56275    event.preventDefault();
56276
56277    // A drag should never propagate up to the 'tap' event.
56278    event.stopPropagation();
56279
56280    // Attach the move and end events.
56281    var moveEvent = attach(actions.move, d, move, {
56282      start: event.calcPoint,
56283      baseSize: baseSize(),
56284      pageOffset: event.pageOffset,
56285      handles: data.handles,
56286      handleNumber: data.handleNumber,
56287      buttonsProperty: event.buttons,
56288      positions: [
56289        scope_Locations[0],
56290        scope_Locations[scope_Handles.length - 1]
56291      ]
56292    }), endEvent = attach(actions.end, d, end, {
56293      handles: data.handles,
56294      handleNumber: data.handleNumber
56295    });
56296
56297    var outEvent = attach("mouseout", d, documentLeave, {
56298      handles: data.handles,
56299      handleNumber: data.handleNumber
56300    });
56301
56302    d.noUiListeners = moveEvent.concat(endEvent, outEvent);
56303
56304    // Text selection isn't an issue on touch devices,
56305    // so adding cursor styles can be skipped.
56306    if ( event.cursor ) {
56307
56308      // Prevent the 'I' cursor and extend the range-drag cursor.
56309      document.body.style.cursor = getComputedStyle(event.target).cursor;
56310
56311      // Mark the target with a dragging state.
56312      if ( scope_Handles.length > 1 ) {
56313        addClass(scope_Target, options.cssClasses.drag);
56314      }
56315
56316      var f = function(){
56317        return false;
56318      };
56319
56320      document.body.noUiListener = f;
56321
56322      // Prevent text selection when dragging the handles.
56323      document.body.addEventListener('selectstart', f, false);
56324    }
56325
56326    if ( data.handleNumber !== undefined ) {
56327      fireEvent('start', data.handleNumber);
56328    }
56329  }
56330
56331  // Move closest handle to tapped location.
56332  function tap ( event ) {
56333
56334    var location = event.calcPoint, total = 0, handleNumber, to;
56335
56336    // The tap event shouldn't propagate up and cause 'edge' to run.
56337    event.stopPropagation();
56338
56339    // Add up the handle offsets.
56340    scope_Handles.forEach(function(a){
56341      total += offset(a)[ options.style ];
56342    });
56343
56344    // Find the handle closest to the tapped position.
56345    handleNumber = ( location < total/2 || scope_Handles.length === 1 ) ? 0 : 1;
56346
56347    // Check if handler is not disablet if yes set number to the next handler
56348    if (scope_Handles[handleNumber].hasAttribute('disabled')) {
56349      handleNumber = handleNumber ? 0 : 1;
56350    }
56351
56352    location -= offset(scope_Base)[ options.style ];
56353
56354    // Calculate the new position.
56355    to = ( location * 100 ) / baseSize();
56356
56357    if ( !options.events.snap ) {
56358      // Flag the slider as it is now in a transitional state.
56359      // Transition takes a configurable amount of ms (default 300). Re-enable the slider after that.
56360      addClassFor( scope_Target, options.cssClasses.tap, options.animationDuration );
56361    }
56362
56363    // Support 'disabled' handles
56364    if ( scope_Handles[handleNumber].hasAttribute('disabled') ) {
56365      return false;
56366    }
56367
56368    // Find the closest handle and calculate the tapped point.
56369    // The set handle to the new position.
56370    setHandle( scope_Handles[handleNumber], to );
56371
56372    fireEvent('slide', handleNumber, true);
56373    fireEvent('set', handleNumber, true);
56374    fireEvent('change', handleNumber, true);
56375
56376    if ( options.events.snap ) {
56377      start(event, { handles: [scope_Handles[handleNumber]] });
56378    }
56379  }
56380
56381  // Fires a 'hover' event for a hovered mouse/pen position.
56382  function hover ( event ) {
56383
56384    var location = event.calcPoint - offset(scope_Base)[ options.style ],
56385      to = scope_Spectrum.getStep(( location * 100 ) / baseSize()),
56386      value = scope_Spectrum.fromStepping( to );
56387
56388    Object.keys(scope_Events).forEach(function( targetEvent ) {
56389      if ( 'hover' === targetEvent.split('.')[0] ) {
56390        scope_Events[targetEvent].forEach(function( callback ) {
56391          callback.call( scope_Self, value );
56392        });
56393      }
56394    });
56395  }
56396
56397  // Attach events to several slider parts.
56398  function events ( behaviour ) {
56399
56400    // Attach the standard drag event to the handles.
56401    if ( !behaviour.fixed ) {
56402
56403      scope_Handles.forEach(function( handle, index ){
56404
56405        // These events are only bound to the visual handle
56406        // element, not the 'real' origin element.
56407        attach ( actions.start, handle.children[0], start, {
56408          handles: [ handle ],
56409          handleNumber: index
56410        });
56411      });
56412    }
56413
56414    // Attach the tap event to the slider base.
56415    if ( behaviour.tap ) {
56416
56417      attach ( actions.start, scope_Base, tap, {
56418        handles: scope_Handles
56419      });
56420    }
56421
56422    // Fire hover events
56423    if ( behaviour.hover ) {
56424      attach ( actions.move, scope_Base, hover, { hover: true } );
56425    }
56426
56427    // Make the range draggable.
56428    if ( behaviour.drag ){
56429
56430      var drag = [scope_Base.querySelector( '.' + options.cssClasses.connect )];
56431      addClass(drag[0], options.cssClasses.draggable);
56432
56433      // When the range is fixed, the entire range can
56434      // be dragged by the handles. The handle in the first
56435      // origin will propagate the start event upward,
56436      // but it needs to be bound manually on the other.
56437      if ( behaviour.fixed ) {
56438        drag.push(scope_Handles[(drag[0] === scope_Handles[0] ? 1 : 0)].children[0]);
56439      }
56440
56441      drag.forEach(function( element ) {
56442        attach ( actions.start, element, start, {
56443          handles: scope_Handles
56444        });
56445      });
56446    }
56447  }
56448
56449
56450  // Test suggested values and apply margin, step.
56451  function setHandle ( handle, to, noLimitOption ) {
56452
56453    var trigger = handle !== scope_Handles[0] ? 1 : 0,
56454      lowerMargin = scope_Locations[0] + options.margin,
56455      upperMargin = scope_Locations[1] - options.margin,
56456      lowerLimit = scope_Locations[0] + options.limit,
56457      upperLimit = scope_Locations[1] - options.limit;
56458
56459    // For sliders with multiple handles,
56460    // limit movement to the other handle.
56461    // Apply the margin option by adding it to the handle positions.
56462    if ( scope_Handles.length > 1 ) {
56463      to = trigger ? Math.max( to, lowerMargin ) : Math.min( to, upperMargin );
56464    }
56465
56466    // The limit option has the opposite effect, limiting handles to a
56467    // maximum distance from another. Limit must be > 0, as otherwise
56468    // handles would be unmoveable. 'noLimitOption' is set to 'false'
56469    // for the .val() method, except for pass 4/4.
56470    if ( noLimitOption !== false && options.limit && scope_Handles.length > 1 ) {
56471      to = trigger ? Math.min ( to, lowerLimit ) : Math.max( to, upperLimit );
56472    }
56473
56474    // Handle the step option.
56475    to = scope_Spectrum.getStep( to );
56476
56477    // Limit percentage to the 0 - 100 range
56478    to = limit(to);
56479
56480    // Return false if handle can't move
56481    if ( to === scope_Locations[trigger] ) {
56482      return false;
56483    }
56484
56485    // Set the handle to the new position.
56486    // Use requestAnimationFrame for efficient painting.
56487    // No significant effect in Chrome, Edge sees dramatic
56488    // performace improvements.
56489    if ( window.requestAnimationFrame ) {
56490      window.requestAnimationFrame(function(){
56491        handle.style[options.style] = to + '%';
56492      });
56493    } else {
56494      handle.style[options.style] = to + '%';
56495    }
56496
56497    // Force proper handle stacking
56498    if ( !handle.previousSibling ) {
56499      removeClass(handle, options.cssClasses.stacking);
56500      if ( to > 50 ) {
56501        addClass(handle, options.cssClasses.stacking);
56502      }
56503    }
56504
56505    // Update locations.
56506    scope_Locations[trigger] = to;
56507
56508    // Convert the value to the slider stepping/range.
56509    scope_Values[trigger] = scope_Spectrum.fromStepping( to );
56510
56511    fireEvent('update', trigger);
56512
56513    return true;
56514  }
56515
56516  // Loop values from value method and apply them.
56517  function setValues ( count, values ) {
56518
56519    var i, trigger, to;
56520
56521    // With the limit option, we'll need another limiting pass.
56522    if ( options.limit ) {
56523      count += 1;
56524    }
56525
56526    // If there are multiple handles to be set run the setting
56527    // mechanism twice for the first handle, to make sure it
56528    // can be bounced of the second one properly.
56529    for ( i = 0; i < count; i += 1 ) {
56530
56531      trigger = i%2;
56532
56533      // Get the current argument from the array.
56534      to = values[trigger];
56535
56536      // Setting with null indicates an 'ignore'.
56537      // Inputting 'false' is invalid.
56538      if ( to !== null && to !== false ) {
56539
56540        // If a formatted number was passed, attemt to decode it.
56541        if ( typeof to === 'number' ) {
56542          to = String(to);
56543        }
56544
56545        to = options.format.from( to );
56546
56547        // Request an update for all links if the value was invalid.
56548        // Do so too if setting the handle fails.
56549        if ( to === false || isNaN(to) || setHandle( scope_Handles[trigger], scope_Spectrum.toStepping( to ), i === (3 - options.dir) ) === false ) {
56550          fireEvent('update', trigger);
56551        }
56552      }
56553    }
56554  }
56555
56556  // Set the slider value.
56557  function valueSet ( input, fireSetEvent ) {
56558
56559    var count, values = asArray( input ), i;
56560
56561    // Event fires by default
56562    fireSetEvent = (fireSetEvent === undefined ? true : !!fireSetEvent);
56563
56564    // The RTL settings is implemented by reversing the front-end,
56565    // internal mechanisms are the same.
56566    if ( options.dir && options.handles > 1 ) {
56567      values.reverse();
56568    }
56569
56570    // Animation is optional.
56571    // Make sure the initial values where set before using animated placement.
56572    if ( options.animate && scope_Locations[0] !== -1 ) {
56573      addClassFor( scope_Target, options.cssClasses.tap, options.animationDuration );
56574    }
56575
56576    // Determine how often to set the handles.
56577    count = scope_Handles.length > 1 ? 3 : 1;
56578
56579    if ( values.length === 1 ) {
56580      count = 1;
56581    }
56582
56583    setValues ( count, values );
56584
56585    // Fire the 'set' event for both handles.
56586    for ( i = 0; i < scope_Handles.length; i++ ) {
56587
56588      // Fire the event only for handles that received a new value, as per #579
56589      if ( values[i] !== null && fireSetEvent ) {
56590        fireEvent('set', i);
56591      }
56592    }
56593  }
56594
56595  // Get the slider value.
56596  function valueGet ( ) {
56597
56598    var i, retour = [];
56599
56600    // Get the value from all handles.
56601    for ( i = 0; i < options.handles; i += 1 ){
56602      retour[i] = options.format.to( scope_Values[i] );
56603    }
56604
56605    return inSliderOrder( retour );
56606  }
56607
56608  // Removes classes from the root and empties it.
56609  function destroy ( ) {
56610
56611    for ( var key in options.cssClasses ) {
56612      if ( !options.cssClasses.hasOwnProperty(key) ) { continue; }
56613      removeClass(scope_Target, options.cssClasses[key]);
56614    }
56615
56616    while (scope_Target.firstChild) {
56617      scope_Target.removeChild(scope_Target.firstChild);
56618    }
56619
56620    delete scope_Target.noUiSlider;
56621  }
56622
56623  // Get the current step size for the slider.
56624  function getCurrentStep ( ) {
56625
56626    // Check all locations, map them to their stepping point.
56627    // Get the step point, then find it in the input list.
56628    var retour = scope_Locations.map(function( location, index ){
56629
56630      var step = scope_Spectrum.getApplicableStep( location ),
56631
56632        // As per #391, the comparison for the decrement step can have some rounding issues.
56633        // Round the value to the precision used in the step.
56634        stepDecimals = countDecimals(String(step[2])),
56635
56636        // Get the current numeric value
56637        value = scope_Values[index],
56638
56639        // To move the slider 'one step up', the current step value needs to be added.
56640        // Use null if we are at the maximum slider value.
56641        increment = location === 100 ? null : step[2],
56642
56643        // Going 'one step down' might put the slider in a different sub-range, so we
56644        // need to switch between the current or the previous step.
56645        prev = Number((value - step[2]).toFixed(stepDecimals)),
56646
56647        // If the value fits the step, return the current step value. Otherwise, use the
56648        // previous step. Return null if the slider is at its minimum value.
56649        decrement = location === 0 ? null : (prev >= step[1]) ? step[2] : (step[0] || false);
56650
56651      return [decrement, increment];
56652    });
56653
56654    // Return values in the proper order.
56655    return inSliderOrder( retour );
56656  }
56657
56658  // Attach an event to this slider, possibly including a namespace
56659  function bindEvent ( namespacedEvent, callback ) {
56660    scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || [];
56661    scope_Events[namespacedEvent].push(callback);
56662
56663    // If the event bound is 'update,' fire it immediately for all handles.
56664    if ( namespacedEvent.split('.')[0] === 'update' ) {
56665      scope_Handles.forEach(function(a, index){
56666        fireEvent('update', index);
56667      });
56668    }
56669  }
56670
56671  // Undo attachment of event
56672  function removeEvent ( namespacedEvent ) {
56673
56674    var event = namespacedEvent && namespacedEvent.split('.')[0],
56675      namespace = event && namespacedEvent.substring(event.length);
56676
56677    Object.keys(scope_Events).forEach(function( bind ){
56678
56679      var tEvent = bind.split('.')[0],
56680        tNamespace = bind.substring(tEvent.length);
56681
56682      if ( (!event || event === tEvent) && (!namespace || namespace === tNamespace) ) {
56683        delete scope_Events[bind];
56684      }
56685    });
56686  }
56687
56688  // Updateable: margin, limit, step, range, animate, snap
56689  function updateOptions ( optionsToUpdate, fireSetEvent ) {
56690
56691    // Spectrum is created using the range, snap, direction and step options.
56692    // 'snap' and 'step' can be updated, 'direction' cannot, due to event binding.
56693    // If 'snap' and 'step' are not passed, they should remain unchanged.
56694    var v = valueGet(), newOptions = testOptions({
56695      start: [0, 0],
56696      margin: optionsToUpdate.margin,
56697      limit: optionsToUpdate.limit,
56698      step: optionsToUpdate.step === undefined ? options.singleStep : optionsToUpdate.step,
56699      range: optionsToUpdate.range,
56700      animate: optionsToUpdate.animate,
56701      snap: optionsToUpdate.snap === undefined ? options.snap : optionsToUpdate.snap
56702    });
56703
56704    ['margin', 'limit', 'range', 'animate'].forEach(function(name){
56705
56706      // Only change options that we're actually passed to update.
56707      if ( optionsToUpdate[name] !== undefined ) {
56708        options[name] = optionsToUpdate[name];
56709      }
56710    });
56711
56712    // Save current spectrum direction as testOptions in testRange call
56713    // doesn't rely on current direction
56714    newOptions.spectrum.direction = scope_Spectrum.direction;
56715    scope_Spectrum = newOptions.spectrum;
56716
56717    // Invalidate the current positioning so valueSet forces an update.
56718    scope_Locations = [-1, -1];
56719    valueSet(optionsToUpdate.start || v, fireSetEvent);
56720  }
56721
56722
56723  // Throw an error if the slider was already initialized.
56724  if ( scope_Target.noUiSlider ) {
56725    throw new Error('Slider was already initialized.');
56726  }
56727
56728  // Create the base element, initialise HTML and set classes.
56729  // Add handles and links.
56730  scope_Base = addSlider( options.dir, options.ort, scope_Target );
56731  scope_Handles = addHandles( options.handles, options.dir, scope_Base );
56732
56733  // Set the connect classes.
56734  addConnection ( options.connect, scope_Target, scope_Handles );
56735
56736  if ( options.pips ) {
56737    pips(options.pips);
56738  }
56739
56740  if ( options.tooltips ) {
56741    tooltips();
56742  }
56743
56744  scope_Self = {
56745    destroy: destroy,
56746    steps: getCurrentStep,
56747    on: bindEvent,
56748    off: removeEvent,
56749    get: valueGet,
56750    set: valueSet,
56751    updateOptions: updateOptions,
56752    options: originalOptions, // Issue #600
56753    target: scope_Target, // Issue #597
56754    pips: pips // Issue #594
56755  };
56756
56757  // Attach user events.
56758  events( options.events );
56759
56760  return scope_Self;
56761
56762}
56763
56764
56765  // Run the standard initializer
56766  function initialize ( target, originalOptions ) {
56767
56768    if ( !target.nodeName ) {
56769      throw new Error('noUiSlider.create requires a single element.');
56770    }
56771
56772    // Test the options and create the slider environment;
56773    var options = testOptions( originalOptions, target ),
56774      slider = closure( target, options, originalOptions );
56775
56776    // Use the public value method to set the start values.
56777    slider.set(options.start);
56778
56779    target.noUiSlider = slider;
56780    return slider;
56781  }
56782
56783  // Use an object instead of a function for future expansibility;
56784  return {
56785    create: initialize
56786  };
56787
56788}));
56789/*!
56790 * jQuery.scrollTo
56791 * Copyright (c) 2007 Ariel Flesler - aflesler ○ gmail • com | https://github.com/flesler
56792 * Licensed under MIT
56793 * https://github.com/flesler/jquery.scrollTo
56794 * @projectDescription Lightweight, cross-browser and highly customizable animated scrolling with jQuery
56795 * @author Ariel Flesler
56796 * @version 2.1.2
56797 */
56798
56799;(function(factory) {
56800	'use strict';
56801	if (typeof define === 'function' && define.amd) {
56802		// AMD
56803		define(['jquery'], factory);
56804	} else if (typeof module !== 'undefined' && module.exports) {
56805		// CommonJS
56806		module.exports = factory(require('jquery'));
56807	} else {
56808		// Global
56809		factory(jQuery);
56810	}
56811})(function($) {
56812	'use strict';
56813
56814	var $scrollTo = $.scrollTo = function(target, duration, settings) {
56815		return $(window).scrollTo(target, duration, settings);
56816	};
56817
56818	$scrollTo.defaults = {
56819		axis:'xy',
56820		duration: 0,
56821		limit:true
56822	};
56823
56824	function isWin(elem) {
56825		return !elem.nodeName ||
56826			$.inArray(elem.nodeName.toLowerCase(), ['iframe','#document','html','body']) !== -1;
56827	}
56828
56829	$.fn.scrollTo = function(target, duration, settings) {
56830		if (typeof duration === 'object') {
56831			settings = duration;
56832			duration = 0;
56833		}
56834		if (typeof settings === 'function') {
56835			settings = { onAfter:settings };
56836		}
56837		if (target === 'max') {
56838			target = 9e9;
56839		}
56840
56841		settings = $.extend({}, $scrollTo.defaults, settings);
56842		// Speed is still recognized for backwards compatibility
56843		duration = duration || settings.duration;
56844		// Make sure the settings are given right
56845		var queue = settings.queue && settings.axis.length > 1;
56846		if (queue) {
56847			// Let's keep the overall duration
56848			duration /= 2;
56849		}
56850		settings.offset = both(settings.offset);
56851		settings.over = both(settings.over);
56852
56853		return this.each(function() {
56854			// Null target yields nothing, just like jQuery does
56855			if (target === null) return;
56856
56857			var win = isWin(this),
56858				elem = win ? this.contentWindow || window : this,
56859				$elem = $(elem),
56860				targ = target,
56861				attr = {},
56862				toff;
56863
56864			switch (typeof targ) {
56865				// A number will pass the regex
56866				case 'number':
56867				case 'string':
56868					if (/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)) {
56869						targ = both(targ);
56870						// We are done
56871						break;
56872					}
56873					// Relative/Absolute selector
56874					targ = win ? $(targ) : $(targ, elem);
56875					/* falls through */
56876				case 'object':
56877					if (targ.length === 0) return;
56878					// DOMElement / jQuery
56879					if (targ.is || targ.style) {
56880						// Get the real position of the target
56881						toff = (targ = $(targ)).offset();
56882					}
56883			}
56884
56885			var offset = $.isFunction(settings.offset) && settings.offset(elem, targ) || settings.offset;
56886
56887			$.each(settings.axis.split(''), function(i, axis) {
56888				var Pos	= axis === 'x' ? 'Left' : 'Top',
56889					pos = Pos.toLowerCase(),
56890					key = 'scroll' + Pos,
56891					prev = $elem[key](),
56892					max = $scrollTo.max(elem, axis);
56893
56894				if (toff) {// jQuery / DOMElement
56895					attr[key] = toff[pos] + (win ? 0 : prev - $elem.offset()[pos]);
56896
56897					// If it's a dom element, reduce the margin
56898					if (settings.margin) {
56899						attr[key] -= parseInt(targ.css('margin'+Pos), 10) || 0;
56900						attr[key] -= parseInt(targ.css('border'+Pos+'Width'), 10) || 0;
56901					}
56902
56903					attr[key] += offset[pos] || 0;
56904
56905					if (settings.over[pos]) {
56906						// Scroll to a fraction of its width/height
56907						attr[key] += targ[axis === 'x'?'width':'height']() * settings.over[pos];
56908					}
56909				} else {
56910					var val = targ[pos];
56911					// Handle percentage values
56912					attr[key] = val.slice && val.slice(-1) === '%' ?
56913						parseFloat(val) / 100 * max
56914						: val;
56915				}
56916
56917				// Number or 'number'
56918				if (settings.limit && /^\d+$/.test(attr[key])) {
56919					// Check the limits
56920					attr[key] = attr[key] <= 0 ? 0 : Math.min(attr[key], max);
56921				}
56922
56923				// Don't waste time animating, if there's no need.
56924				if (!i && settings.axis.length > 1) {
56925					if (prev === attr[key]) {
56926						// No animation needed
56927						attr = {};
56928					} else if (queue) {
56929						// Intermediate animation
56930						animate(settings.onAfterFirst);
56931						// Don't animate this axis again in the next iteration.
56932						attr = {};
56933					}
56934				}
56935			});
56936
56937			animate(settings.onAfter);
56938
56939			function animate(callback) {
56940				var opts = $.extend({}, settings, {
56941					// The queue setting conflicts with animate()
56942					// Force it to always be true
56943					queue: true,
56944					duration: duration,
56945					complete: callback && function() {
56946						callback.call(elem, targ, settings);
56947					}
56948				});
56949				$elem.animate(attr, opts);
56950			}
56951		});
56952	};
56953
56954	// Max scrolling position, works on quirks mode
56955	// It only fails (not too badly) on IE, quirks mode.
56956	$scrollTo.max = function(elem, axis) {
56957		var Dim = axis === 'x' ? 'Width' : 'Height',
56958			scroll = 'scroll'+Dim;
56959
56960		if (!isWin(elem))
56961			return elem[scroll] - $(elem)[Dim.toLowerCase()]();
56962
56963		var size = 'client' + Dim,
56964			doc = elem.ownerDocument || elem.document,
56965			html = doc.documentElement,
56966			body = doc.body;
56967
56968		return Math.max(html[scroll], body[scroll]) - Math.min(html[size], body[size]);
56969	};
56970
56971	function both(val) {
56972		return $.isFunction(val) || $.isPlainObject(val) ? val : { top:val, left:val };
56973	}
56974
56975	// Add special hooks so that window scroll properties can be animated
56976	$.Tween.propHooks.scrollLeft =
56977	$.Tween.propHooks.scrollTop = {
56978		get: function(t) {
56979			return $(t.elem)[t.prop]();
56980		},
56981		set: function(t) {
56982			var curr = this.get(t);
56983			// If interrupt is true and user scrolled, stop animating
56984			if (t.options.interrupt && t._last && t._last !== curr) {
56985				return $(t.elem).stop();
56986			}
56987			var next = Math.round(t.now);
56988			// Don't waste CPU
56989			// Browsers don't render floating point scroll
56990			if (curr !== next) {
56991				$(t.elem)[t.prop](next);
56992				t._last = this.get(t);
56993			}
56994		}
56995	};
56996
56997	// AMD requirement
56998	return $scrollTo;
56999});
57000(function() {
57001  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
57002    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57003    hasProp = {}.hasOwnProperty;
57004
57005  App.Pages.ItemsIndex = (function(superClass) {
57006    var isTargetPage;
57007
57008    extend(ItemsIndex, superClass);
57009
57010    function ItemsIndex() {
57011      this.onPriceRangeUpdate = bind(this.onPriceRangeUpdate, this);
57012      this.onPriceRangeChanged = bind(this.onPriceRangeChanged, this);
57013      this.initAndBindPriceRangeFilter = bind(this.initAndBindPriceRangeFilter, this);
57014      return ItemsIndex.__super__.constructor.apply(this, arguments);
57015    }
57016
57017    isTargetPage = window.location.pathname.includes('ext/usd');
57018
57019    ItemsIndex.prototype.widgets = ['customItemRequest'];
57020
57021    ItemsIndex.prototype.maxPrice = 500;
57022
57023    ItemsIndex.prototype.minValues = [2, 10, 20, 50, 100, 200, 300, 450];
57024
57025    ItemsIndex.prototype.maxValues = [10, 20, 50, 100, 200, 300, 500];
57026
57027    ItemsIndex.prototype.initialize = function() {
57028      this.bindPaginationTriggers();
57029      this.bindFilters();
57030      this.bindSearchForm();
57031      this.bindActiveFilters();
57032      App.events.subscribe('items:init-price-range-filter', this.initAndBindPriceRangeFilter);
57033      App.events.subscribe('items:set-meta-tags', this.setMetaTags);
57034      App.events.subscribe('items:init-sticky-filters-block', this.initStickyFiltersBlock);
57035      App.events.subscribe('items:init-license-filter-tooltip', this.initAndBindLicenseFilterTooltip);
57036      new LazyLoad({
57037        elements_selector: '.js-lazy-img'
57038      });
57039      return this.initializeRouter();
57040    };
57041
57042    ItemsIndex.prototype.setMetaTags = function(metaData) {
57043      var head, metaDescription, metaKeywords;
57044      document.title = metaData.title;
57045      $('meta[name=description],meta[name=keywords]').remove();
57046      head = document.getElementsByTagName('head')[0];
57047      metaDescription = document.createElement('meta');
57048      metaDescription.name = 'description';
57049      metaDescription.content = metaData.description;
57050      head.appendChild(metaDescription);
57051      metaKeywords = document.createElement('meta');
57052      metaKeywords.name = 'keywords';
57053      metaKeywords.content = metaData.keywords;
57054      return head.appendChild(metaKeywords);
57055    };
57056
57057    ItemsIndex.prototype.handleFreeFilterForTarget = function() {
57058      if (window.location.search) {
57059        return this.router.navigate("" + window.location.pathname + window.location.search + "&swap_models=0", {
57060          trigger: true
57061        });
57062      } else {
57063        return this.router.navigate(window.location.pathname + "?swap_models=0", {
57064          trigger: true
57065        });
57066      }
57067    };
57068
57069    ItemsIndex.prototype.initializeRouter = function() {
57070      this.router = new App.Backbone.Routers.ItemsSearch({
57071        dedicateFileTypeFragments: $('.js-filter-block-container').data()
57072      });
57073      if (!Backbone.history.started) {
57074        Backbone.history.start({
57075          pushState: true,
57076          root: '/'
57077        });
57078        return Backbone.history.started = true;
57079      }
57080    };
57081
57082    ItemsIndex.prototype.bindPaginationTriggers = function() {
57083      $(document).on('click', '.pagination a', (function(_this) {
57084        return function(event) {
57085          mute(event);
57086          return _this.router.navigate("" + event.currentTarget.pathname + event.currentTarget.search, {
57087            trigger: true
57088          });
57089        };
57090      })(this));
57091      $(document).on('keypress', '.custom-page-input', (function(_this) {
57092        return function(event) {
57093          var targetPage;
57094          if (event.which === 13) {
57095            mute(event);
57096            targetPage = $(".custom-page-input")[0].value;
57097            if (!(targetPage === '')) {
57098              return _this.router.applyFilter('page', targetPage);
57099            }
57100          }
57101        };
57102      })(this));
57103      return $(document).on('click', '.js-per-page', (function(_this) {
57104        return function(event) {
57105          var $perPageLink;
57106          mute(event);
57107          $perPageLink = $(event.currentTarget);
57108          $perPageLink.parents('.product-filters-footer__page-size').find('.is-active').removeClass('is-active');
57109          $perPageLink.parent().addClass('is-active');
57110          Cookies.set(_this.paginationCookieKey(), $perPageLink.text());
57111          Cookies.set(gon.cookies_map.per_page_changed, true);
57112          return _this.router.removeFilter('page');
57113        };
57114      })(this));
57115    };
57116
57117    ItemsIndex.prototype.bindFilters = function() {
57118      $(document).on('change', '#sort_by,#polygons,#file_types,#licenses', (function(_this) {
57119        return function(event) {
57120          var name;
57121          mute(event);
57122          name = event.currentTarget.name;
57123          if (name === 'file_types[]') {
57124            name = 'file_types';
57125          }
57126          if (name === 'licenses[]') {
57127            name = 'licenses';
57128          }
57129          _this.router.applyFilter(name, event.currentTarget.value);
57130          if (event.currentTarget.value === '') {
57131            return _this.router.removeFilter(event.currentTarget.name);
57132          }
57133        };
57134      })(this));
57135      $(document).on('change', '.js-filter-checkbox', (function(_this) {
57136        return function(event) {
57137          var $checkbox, isAdult, name;
57138          mute(event);
57139          $checkbox = $(event.currentTarget);
57140          name = event.currentTarget.name;
57141          if (isTargetPage && name === 'free') {
57142            if ($checkbox.is(':checked')) {
57143              _this.router.removeFilter('swap_models');
57144              return _this.router.removeFilter('price');
57145            } else {
57146              return _this.handleFreeFilterForTarget();
57147            }
57148          } else {
57149            if ($checkbox.is(':checked')) {
57150              if (name === 'adult_content') {
57151                isAdult = Cookies.get('i_am_an_adult') === 'true';
57152                if (isAdult) {
57153                  return _this.router.applyFilter(name, '1');
57154                } else {
57155                  _this.router.removeFilter(name);
57156                  _this.addAdultModal();
57157                  $(document).on('click', '.js-set-adult-cookie', function() {
57158                    Cookies.set('i_am_an_adult', true, {
57159                      expires: 365
57160                    });
57161                    _this.router.applyFilter(name, '1');
57162                    return _this.removeAdultModal();
57163                  });
57164                  return $(document).on('click', '.js-close-modal', function() {
57165                    return _this.removeAdultModal();
57166                  });
57167                }
57168              } else {
57169                return _this.router.applyFilter(name, '1');
57170              }
57171            } else {
57172              return _this.router.removeFilter(name);
57173            }
57174          }
57175        };
57176      })(this));
57177      $(document).on('click', '.js-reset-all', (function(_this) {
57178        return function(event) {
57179          var basePath, listingPathname, useLocaleInPath;
57180          useLocaleInPath = I18n.locale !== I18n.defaultLocale;
57181          if (App.config.currentProductType === 'cg') {
57182            basePath = I18n.t('item-search-filters.3d-models-url');
57183          } else {
57184            basePath = I18n.t('item-search-filters.3d-print-models-url');
57185          }
57186          listingPathname = useLocaleInPath ? "/" + I18n.locale + "/" + basePath 
57186: "/" + basePath;
57187          return _this.router.navigate(listingPathname, {
57188            trigger: true
57189          });
57190        };
57191      })(this));
57192      $(document).on('click', '.js-toggle-filters-group', function(event) {
57193        return $(event.currentTarget).parent().toggleClass('is-opened');
57194      });
57195      return $(document).on('change', '#max_price,#min_price', (function(_this) {
57196        return function(event) {
57197          var maxValue, minValue;
57198          mute(event);
57199          minValue = Number($('#min_price').val());
57200          maxValue = Number($('#max_price').val());
57201          return _this.onPriceRangeChanged([minValue, maxValue < minValue ? minValue : maxValue]);
57202        };
57203      })(this));
57204    };
57205
57206    ItemsIndex.prototype.bindActiveFilters = function() {
57207      return $(document).on('click', '.js-active-filters-container .js-reset', (function(_this) {
57208        return function(event) {
57209          var filterIdentifier;
57210          mute(event);
57211          filterIdentifier = $(event.currentTarget).parents('.active-filters__item').data();
57212          if (isTargetPage && filterIdentifier.filter === 'free') {
57213            return _this.handleFreeFilterForTarget();
57214          } else {
57215            return _this.router.removeFilter(filterIdentifier.filter, "" + filterIdentifier.filterValue);
57216          }
57217        };
57218      })(this));
57219    };
57220
57221    ItemsIndex.prototype.bindSearchForm = function() {
57222      return $('#search-form').submit((function(_this) {
57223        return function(event) {
57224          var newKeywords;
57225          event.preventDefault();
57226          newKeywords = $('#search-form [name=keywords]').val();
57227          return _this.router.applyFilter('keywords', newKeywords, {
57228            reload: true
57229          });
57230        };
57231      })(this));
57232    };
57233
57234    ItemsIndex.prototype.initStickyFiltersBlock = function() {
57235      return $(window).load(function() {
57236        var jsStickyOffset, ref;
57237        jsStickyOffset = (ref = $('#navigation-header').height()) != null ? ref : 0;
57238        return $('.filter-block').sticky({
57239          topSpacing: jsStickyOffset,
57240          wrapperClassName: 'sticky-filters-wrapper'
57241        });
57242      });
57243    };
57244
57245    ItemsIndex.prototype.initAndBindLicenseFilterTooltip = function($tooltip) {
57246      var content;
57247      content = "<b>\n  " + (I18n.t('browser.license.tooltip_title')) + "\n</b>\n" + (I18n.t('browser.license.tooltip_text')) + "\n<br/>\n<div class=\"u-text-center\">\n  <a href=\"https://www.cgtrader.com/faq/page/112\" target=\"_blank\">\n    " + (I18n.t('browser.license.tooltip_more')) + "\n  </a>\n</div>";
57248      return $tooltip.tooltipster({
57249        theme: 'tooltip--light tooltip--popover u-text-left',
57250        animation: 'grow',
57251        contentAsHTML: true,
57252        content: content,
57253        position: 'left',
57254        interactive: true,
57255        maxWidth: 700
57256      });
57257    };
57258
57259    ItemsIndex.prototype.initAndBindPriceRangeFilter = function($priceRange) {
57260      var ref, startRange;
57261      if (!$priceRange.exists()) {
57262        return;
57263      }
57264      $priceRange.tooltipster({
57265        theme: 'tooltip--light',
57266        animation: 'grow',
57267        delay: 0,
57268        autoClose: true,
57269        trigger: 'custom',
57270        updateAnimation: false,
57271        timer: 500
57272      });
57273      startRange = (ref = JSON.parse($('#price').val())) != null ? ref : {
57274        from: 2,
57275        to: this.maxPrice
57276      };
57277      if (this.minValues.includes(Math.round(startRange.from))) {
57278        $('#min_price').val(startRange.from);
57279      } else {
57280        $('#min_price').append('<option value="' + startRange.from + '" selected="selected">' + startRange.from + '</option>');
57281      }
57282      if (this.maxValues.includes(Math.round(startRange.to))) {
57283        $('#max_price').val(startRange.to);
57284      } else {
57285        $('#max_price').append('<option value="' + startRange.to + '" selected="selected">' + startRange.to + '</option>');
57286      }
57287      if (!$priceRange[0].noUiSlider) {
57288        noUiSlider.create($priceRange[0], {
57289          start: [startRange.from, startRange.to],
57290          connect: true,
57291          step: 1,
57292          range: {
57293            'min': 2,
57294            'max': this.maxPrice
57295          }
57296        });
57297      }
57298      $priceRange[0].noUiSlider.on('change', this.onPriceRangeChanged);
57299      $priceRange[0].noUiSlider.on('set', this.onPriceRangeChanged);
57300      return $priceRange[0].noUiSlider.on('update', (function(_this) {
57301        return function(values, handle) {
57302          return _this.onPriceRangeUpdate(values, handle, $priceRange);
57303        };
57304      })(this));
57305    };
57306
57307    ItemsIndex.prototype.onPriceRangeChanged = function(values) {
57308      var from, to;
57309      from = parseInt(values[0]);
57310      to = parseInt(values[1]);
57311      if (from === 2 && to === this.maxPrice) {
57312        return this.router.removeFilter('price');
57313      } else {
57314        return this.router.applyFilter('price', {
57315          from: from,
57316          to: to
57317        });
57318      }
57319    };
57320
57321    ItemsIndex.prototype.onPriceRangeUpdate = function(values, handle, $priceRange) {
57322      var value;
57323      value = values[handle];
57324      if ($priceRange.priceRangeChangeCounter == null) {
57325        $priceRange.priceRangeChangeCounter = 0;
57326      }
57327      $priceRange.priceRangeChangeCounter += 1;
57328      if ($priceRange.priceRangeChangeCounter < 10) {
57329        return;
57330      }
57331      if (+value === this.maxPrice) {
57332        value = this.maxPrice + " or more";
57333      }
57334      if ($priceRange.hasClass('tooltipstered')) {
57335        return $priceRange.tooltipster({}).tooltipster('content', "$" + value).tooltipster('show');
57336      }
57337    };
57338
57339    ItemsIndex.prototype.selectedPriceScope = function() {
57340      return $('input[name=price_scope]:checked').val();
57341    };
57342
57343    ItemsIndex.prototype.paginationCookieKey = function() {
57344      if (_.isEmpty($.deparam((window.location.search || "").replace(/^\?/, '')).keywords)) {
57345        return gon.cookies_map.items_listing_per_page;
57346      } else {
57347        return gon.cookies_map.items_search_per_page;
57348      }
57349    };
57350
57351    ItemsIndex.prototype.addAdultModal = function() {
57352      var $agreementModal;
57353      $agreementModal = $(JST["modals/adult_content_agreement_modal"]());
57354      return $('body').prepend($agreementModal);
57355    };
57356
57357    ItemsIndex.prototype.removeAdultModal = function() {
57358      return $('body').find('.overlay-modal--background').remove();
57359    };
57360
57361    return ItemsIndex;
57362
57363  })(App.Core.Page);
57364
57365}).call(this);
57366(function() {
57367  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57368    hasProp = {}.hasOwnProperty;
57369
57370  App.Pages.ItemsMobileIndex = (function(superClass) {
57371    extend(ItemsMobileIndex, superClass);
57372
57373    function ItemsMobileIndex() {
57374      return ItemsMobileIndex.__super__.constructor.apply(this, arguments);
57375    }
57376
57377    return ItemsMobileIndex;
57378
57379  })(App.Pages.ItemsIndex);
57380
57381}).call(this);
57382(function() {
57383  var base,
57384    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57385    hasProp = {}.hasOwnProperty;
57386
57387  if ((base = App.Pages).Jobs == null) {
57388    base.Jobs = {};
57389  }
57390
57391  App.Pages.Jobs.CheckoutsPayment = (function(superClass) {
57392    extend(CheckoutsPayment, superClass);
57393
57394    function CheckoutsPayment() {
57395      return CheckoutsPayment.__super__.constructor.apply(this, arguments);
57396    }
57397
57398    CheckoutsPayment.prototype.initialize = function() {
57399      this.initBuyerType();
57400      this.bindApplyCompanyInformation();
57401      this.initDiscountTrigger();
57402      this.initDiscountTriggerV2();
57403      this.bindTabClick();
57404      this.handleCouponPurchase();
57405      return this.initCountrySelection();
57406    };
57407
57408    return CheckoutsPayment;
57409
57410  })(App.Pages.CheckoutPayment);
57411
57412}).call(this);
57413(function() {
57414  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57415    hasProp = {}.hasOwnProperty;
57416
57417  App.Pages.Jobs.CheckoutsPlaceOrder = (function(superClass) {
57418    extend(CheckoutsPlaceOrder, superClass);
57419
57420    function CheckoutsPlaceOrder() {
57421      return CheckoutsPlaceOrder.__super__.constructor.apply(this, arguments);
57422    }
57423
57424    return CheckoutsPlaceOrder;
57425
57426  })(App.Pages.Jobs.CheckoutsPayment);
57427
57428}).call(this);
57429(function() {
57430  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57431    hasProp = {}.hasOwnProperty;
57432
57433  App.Pages.OrdersShow = (function(superClass) {
57434    extend(OrdersShow, superClass);
57435
57436    function OrdersShow() {
57437      return OrdersShow.__super__.constructor.apply(this, arguments);
57438    }
57439
57440    OrdersShow.prototype.widgets = ['downloads', 'loyaltyLevelProgressInfoTooltip'];
57441
57442    OrdersShow.prototype.initialize = function() {
57443      $('.js-subscribe-marketing').on('click', this.onSubscribeClick);
57444      return this.sendDataToGoogleAnalytics();
57445    };
57446
57447    OrdersShow.prototype.sendDataToGoogleAnalytics = function() {
57448      var event;
57449      if (!gon.user_tracking_ga4_purchase) {
57450        return;
57451      }
57452      event = {
57453        event: 'purchase',
57454        enhanced_conversion_data: {
57455          email: gon.user_email
57456        }
57457      };
57458      return dataLayer.push(Object.assign(event, gon.user_tracking_ga4_purchase));
57459    };
57460
57461    OrdersShow.prototype.onSubscribeClick = function(e) {
57462      var email;
57463      email = $('input[name="email"]').val();
57464      return $.ajax({
57465        url: '/api/internal/users/update-gdpr-answer',
57466        type: 'PUT',
57467        data: {
57468          answer: 'opt_in'
57469        },
57470        success: function(response) {
57471          $(e.target).attr('disabled', 'disabled');
57472          return App.events.publish('app:notification:flash:success', 'Thank you! You have successfully subscribed to our emails.');
57473        }
57474      });
57475    };
57476
57477    return OrdersShow;
57478
57479  })(App.Core.Page);
57480
57481}).call(this);
57482(function() {
57483  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57484    hasProp = {}.hasOwnProperty;
57485
57486  App.Pages.PagesShow = (function(superClass) {
57487    extend(PagesShow, superClass);
57488
57489    function PagesShow() {
57490      return PagesShow.__super__.constructor.apply(this, arguments);
57491    }
57492
57493    PagesShow.prototype.widgets = ['loyaltyLevelProgressInfoTooltip'];
57494
57495    PagesShow.prototype.initialize = function() {
57496      return this.showLoyaltyLevelProgressBar();
57497    };
57498
57499    PagesShow.prototype.showLoyaltyLevelProgressBar = function() {
57500      this.$loyaltyLevelProgressBar = $('.js-loyalty-level-progress');
57501      return this.$loyaltyLevelProgressBar.appendTo('#loyalty-level-progress-bar-block');
57502    };
57503
57504    return PagesShow;
57505
57506  })(App.Core.Page);
57507
57508}).call(this);
57509(function() {
57510  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57511    hasProp = {}.hasOwnProperty;
57512
57513  App.Pages.PrinterPagesShow = (function(superClass) {
57514    extend(PrinterPagesShow, superClass);
57515
57516    function PrinterPagesShow() {
57517      return PrinterPagesShow.__super__.constructor.apply(this, arguments);
57518    }
57519
57520    PrinterPagesShow.prototype.initialize = function() {
57521      this.bindEvents();
57522      return $('.js-sticky').sticky({
57523        topSpacing: 0,
57524        wrapperClassName: 'sticky-wrapper'
57525      });
57526    };
57527
57528    PrinterPagesShow.prototype.bindEvents = function() {
57529      return $('.js-focus').on('click', this.onFocusClick);
57530    };
57531
57532    PrinterPagesShow.prototype.onFocusClick = function(e) {
57533      var $target;
57534      $target = $($(e.currentTarget).attr('href'));
57535      $('html, body').animate({
57536        scrollTop: $target.offset().top - $('.printer-navigation').height(),
57537        easing: 'easeInOutCirc'
57538      }, {
57539        easing: 'easeInOutCirc',
57540        duration: 600
57541      });
57542      return e.preventDefault();
57543    };
57544
57545    return PrinterPagesShow;
57546
57547  })(App.Core.Page);
57548
57549}).call(this);
57550(function() {
57551  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57552    hasProp = {}.hasOwnProperty;
57553
57554  App.Pages.ProductsShow = (function(superClass) {
57555    extend(ProductsShow, superClass);
57556
57557    function ProductsShow() {
57558      return ProductsShow.__super__.constructor.apply(this, arguments);
57559    }
57560
57561    ProductsShow.prototype.widgets = ['comments', 'reportContent', 'likes', 'wishLists'];
57562
57563    ProductsShow.prototype.initialize = function($el) {
57564      if ($el == null) {
57565        $el = $('.product-container, .product-page');
57566      }
57567      this.$el = $el;
57568      App.state.is3DPrint = $('.site-page--product-3d-print').length > 0;
57569      this.handlers = this.eventHandlers();
57570      this.modifySimilarProductAttributes();
57571      this.bindEvents();
57572      this.initTabs();
57573      this.initMoneyBackTooltip();
57574      this.initLicenseTooltip();
57575      this.handleLicenseLink();
57576      this.handleApproveButtons();
57577      this.initiateSimilarProductsLoader();
57578      this.updatesLikesViewsCounters();
57579      return this.calculatePageHeight();
57580    };
57581
57582    ProductsShow.prototype.bindEvents = function() {
57583      App.events.subscribe('app:preview:change', this.handlers.previewItemChange);
57584      this.$el.find('.js-report-link').click(function() {
57585        return $('#modal-report-content').makeMeModal();
57586      });
57587      this.$el.find('.js-collections-list-button').click((function(_this) {
57588        return function() {
57589          _this.$el.find('.js-collections-list-item').removeClass('is-hidden');
57590          _this.$el.find('.js-collections-list-button').addClass('is-hidden');
57591          return _this.$el.find('.js-box-bottom-space').removeClass('is-hidden');
57592        };
57593      })(this));
57594      this.$el.find('.js-toggle-collection-row').click(function() {
57595        return $(this).toggleClass('is-droped');
57596      });
57597      if ($('#js-collection-contents-rows')[0]) {
57598        this.loadingOtherContentsRows = false;
57599        window.addEventListener('scroll', (function(_this) {
57600          return function(event) {
57601            return _this.scrolledHandler(event);
57602          };
57603        })(this));
57604      }
57605      return this.$el.find('.js-hire-me').click((function(_this) {
57606        return function(e) {
57607          return _this.onHireMeClicked(e);
57608        };
57609      })(this));
57610    };
57611
57612    ProductsShow.prototype.scrolledHandler = function(e) {
57613      var $lastTr;
57614      $lastTr = $('#js-collection-contents-rows tr').last();
57615      if ($lastTr[0] && $lastTr.offset().top <= window.pageYOffset + window.innerHeight && !this.loadingOtherContentsRows) {
57616        this.loadingOtherContentsRows = true;
57617        return $.getScript("/items/" + gon.product_id + "/next-collection-rows/" + ($lastTr.data('page')), ((function(_this) {
57618          return function() {
57619            return _this.loadingOtherContentsRows = false;
57620          };
57621        })(this)));
57622      }
57623    };
57624
57625    ProductsShow.prototype.initTabs = function() {
57626      return this.$el.find('#product-tabs').easytabs({
57627        animate: false,
57628        updateHash: false,
57629        panelActiveClass: 'is-active',
57630        tabActiveClass: 'is-active'
57631      });
57632    };
57633
57634    ProductsShow.prototype.initMoneyBackTooltip = function() {
57635      if (this.$el.find('.product-money-back').length === 0) {
57636        return;
57637      }
57638      return this.$el.find('.product-money-back__title').tooltipster({
57639        theme: 'tooltip--light',
57640        animation: 'grow',
57641        trigger: 'click'
57642      });
57643    };
57644
57645    ProductsShow.prototype.initLicenseTooltip = function() {
57646      var detected_keyword_text;
57647      if (this.$el.find('.js-license-tooltip').data('keyword') === void 0) {
57648        detected_keyword_text = '';
57649      } else {
57650        detected_keyword_text = "(namely owner of the brand \"" + (this.$el.find('.js-license-tooltip').data('keyword')) + "\")";
57651      }
57652      return this.$el.find('.js-license-tooltip').tooltipster({
57653        theme: 'tooltip--light tooltip--popover u-text-center',
57654        animation: 'grow',
57655        interactive: true,
57656        contentAsHTML: true,
57657        content: I18n.t("licenses_new.tooltips." + (this.$el.find('.js-license-tooltip').data('license')) + "_html", {
57658          keyword_detected: detected_keyword_text
57659        }),
57660        functionReady: (function(_this) {
57661          return function() {
57662            return _this.handleLicenseLink();
57663          };
57664        })(this)
57665      });
57666    };
57667
57668    ProductsShow.prototype.modifySimilarProductAttributes = function() {
57669      return $.each(this.$el.find('.content-box__image'), function(index, item) {
57670        $(item).addClass('js-similar-product');
57671        return item.setAttribute('data-position', index + 1);
57672      });
57673    };
57674
57675    ProductsShow.prototype.handleLicenseLink = function() {
57676      var $el;
57677      $el = this.$el.find('.js-license');
57678      $el.on('ajax:before', function() {
57679        return $el.attr('href', $el.data('href'));
57680      });
57681      return $el.on('ajax:complete', function() {
57682        return $el.removeAttr('href');
57683      });
57684    };
57685
57686    ProductsShow.prototype.handleApproveButtons = function() {
57687      if (App.config.isAdmin) {
57688        this.$el.find('.js-auto-approve').removeClass('is-hidden');
57689        this.$el.find('.js-approve').on('change', this.handlers.changeHandler);
57690        return this.$el.find('.js-disapprove').on('change', this.handlers.changeHandler);
57691      }
57692    };
57693
57694    ProductsShow.prototype.initiateSimilarProductsLoader = function() {
57695      if (!$('.js-related-products-grid-bottom-placeholder').exists()) {
57696        return;
57697      }
57698      return $.getScript("/items/" + gon.product_id + "/similar-products");
57699    };
57700
57701    ProductsShow.prototype.updatesLikesViewsCounters = function() {
57702      this.$el.find('.js-likes-counter').text(gon.item_likes);
57703      this.$el.find('.js-views-counter').text(gon.item_views);
57704      return this.$el.find('.js-downloads-counter').text(gon.item_downloads);
57705    };
57706
57707    ProductsShow.prototype.calculatePageHeight = function() {
57708      if (window.innerWidth > 992) {
57709        return $('.product-page').css('min-height', $('.product-page__sidebar').height());
57710      }
57711    };
57712
57713    ProductsShow.prototype.eventHandlers = function() {
57714      return {
57715        thumbHover: (function(_this) {
57716          return function(e) {
57717            if ($(_this).hasClass('is-active') || $(_this).hasClass('product-thumbs-action')) {
57718              return;
57719            }
57720            return _this.$el.find('.product-thumbs li').not(_this).not('.is-active, .product-thumbs-action').addClass('is-disabled');
57721          };
57722        })(this),
57723        thumbHoverOut: (function(_this) {
57724          return function(e) {
57725            return _this.$el.find('.product-thumbs li').removeClass('is-disabled');
57726          };
57727        })(this),
57728        thumbClick: (function(_this) {
57729          return function(e) {
57730            var $el, $target;
57731            _this.handlers.updateCurrentCount('thumb', $(e.currentTarget).data('position'));
57732            if (_.has(e, 'zoomIn')) {
57733              return true;
57734            }
57735            e.preventDefault();
57736            e.stopPropagation();
57737            $el = $(e.currentTarget);
57738            $target = _this.$previewImages.find("li:eq(" + ($el.index()) + ")");
57739            if ($el.hasClass('product-thumbs-action')) {
57740              return false;
57741            }
57742            return App.events.publish('app:preview:change', $target, 'auto');
57743          };
57744        })(this),
57745        previewItemChange: (function(_this) {
57746          return function($target, direction) {
57747            var $active, $thumb;
57748            $active = _this.$previewImages.find('.is-active');
57749            if (direction === 'auto') {
57750              direction = 'next';
57751              _this.sly.activate($target.index());
57752            }
57753            $active.removeClass('is-active');
57754            $target.addClass('is-active');
57755            $target.find('iframe.sculpteo-preview').loadDynamicIframe();
57756            $thumb = _this.$el.find(".product-thumbs li:eq(" + ($target.index()) + ")");
57757            if ($thumb.hasClass('is-embedded')) {
57758              _this.$el.find('.js-zoom-in').fadeOut();
57759            } else {
57760              _this.$el.find('.js-zoom-in').fadeIn();
57761            }
57762            return _this.$el.find('.js-wr-watermark').toggleClass('hidden', !$thumb.hasClass('js-wr-360'));
57763          };
57764        })(this),
57765        previewNavClick: (function(_this) {
57766          return function(e) {
57767            var $active, $el, $target, direction;
57768            $el = $(e.currentTarget);
57769            $active = _this.$previewImages.find('.is-active');
57770            if ($(e.currentTarget).hasClass('js-preview-next')) {
57771              $target = $active.next();
57772              direction = 'next';
57773              _this.handlers.updateCurrentCount('next');
57774              if ($el.data('trigger') === 'manual') {
57775                _this.sly.next();
57776              }
57777            } else {
57778              $target = $active.prev();
57779              direction = 'prev';
57780              _this.handlers.updateCurrentCount('previous');
57781              if ($el.data('trigger') === 'manual') {
57782                _this.sly.prev();
57783              }
57784            }
57785            if ($target.length === 0) {
57786              return;
57787            }
57788            return App.events.publish('app:preview:change', $target, direction);
57789          };
57790        })(this),
57791        updateCurrentCount: (function(_this) {
57792          return function(target, index) {
57793            var currentItem, totalItems;
57794            totalItems = parseInt(_this.$el.find('.total-images').text());
57795            currentItem = parseInt(_this.$el.find('.current-image').text());
57796            if (target === 'next') {
57797              if (currentItem < totalItems) {
57798                currentItem += 1;
57799              }
57800            } else if (target === 'previous') {
57801              if (currentItem > 1) {
57802                currentItem -= 1;
57803              }
57804            } else {
57805              currentItem = index;
57806            }
57807            return _this.$el.find('.current-image').text(currentItem);
57808          };
57809        })(this),
57810        zoomClick: (function(_this) {
57811          return function(e) {
57812            return _this.$el.find('.product-thumbs li.is-active').not('.product-thumbs-action').first().find('a').trigger({
57813              type: 'click',
57814              zoomIn: true
57815            });
57816          };
57817        })(this),
57818        changeHandler: function(e) {
57819          if (this.checked) {
57820            $(e.target).parents('.js-auto-approve').children().removeClass('auto-approval');
57821            return $.post("//admin." + window.location.host + "/admin/approve/set_approve_status", {
57822              type: this.name,
57823              status: this.value,
57824              item_id: this.getAttribute('data-item')
57825            });
57826          }
57827        },
57828        resizeScrollbar: (function(_this) {
57829          return function(e) {
57830            return _this.sly.reload();
57831          };
57832        })(this)
57833      };
57834    };
57835
57836    ProductsShow.prototype.onHireMeClicked = function(e) {
57837      if (!App.config.userSignedIn) {
57838        e.preventDefault();
57839        return App.events.publish('app:login:open', {
57840          redirectURL: e.target.href
57841        });
57842      }
57843    };
57844
57845    return ProductsShow;
57846
57847  })(App.Core.Page);
57848
57849}).call(this);
57850(function() {
57851  $.validator.addMethod('greaterThan', function(value, element, param) {
57852    this.$numberValue = Number(value.replace(/[^0-9\.\-]+/g, ""));
57853    return this.$numberValue > param;
57854  }, $.format('Must be greater than {0}'));
57855
57856  $.validator.addMethod('notLowerThan50Percent', function(value, element, param) {
57857    var originalPrice;
57858    originalPrice = Number($('input#original-product-price').val());
57859    return value >= originalPrice * 0.5;
57860  }, 'Price offers should not be below the minimum of 50% of the original price');
57861
57862  $.validator.addMethod('onlyLatinSymbols', function(value) {
57863    return /^[A-z0-9 ?!,.\-:()\/&%$*#@<>=]*$/.test(value);
57864  }, 'Should not contain non-latin symbols');
57865
57866  $.validator.addMethod('validateUsername', function(value) {
57867    return /^[-A-Za-z0-9]*$/.test(value);
57868  }, 'Please use only latin symbols, numbers or a hyphen symbol ( - )');
57869
57870  $.validator.addMethod('validatePassword', function(value) {
57871    return /^((?=.*\d)(?=.*[A-Z]))/.test(value);
57872  }, 'Your password should have at least one Uppercase character and one digit');
57873
57874  $.validator.addMethod('oneFieldAllowed', function(value, element, param) {
57875    var otherValue;
57876    otherValue = $(param).val();
57877    return (value && !otherValue) || (!value && otherValue);
57878  }, 'Only one field must be filled');
57879
57880  $.validator.addMethod('validDateFormat', function(value, element) {
57881    return this.optional(element) || /^\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])$/.test(value);
57882  }, 'Please enter a valid date in the format YYYY-MM-DD');
57883
57884  $.validator.addMethod('validCardDateFormat', function(value, element) {
57885    return this.optional(element) || /^(0[123456789]|10|11|12)(\s?[\/]\s?)([1-2][0-9])/.test(value);
57886  }, 'Please enter a valid date in the format MM/YY');
57887
57888  $.validator.addMethod('validateCompetitors', function(value) {
57889    var competitorNotMentioned, competitorsList, downcasedInputValue;
57890    competitorNotMentioned = true;
57891    competitorsList = ['turbosquid', 'sketchfab', 'skfb.ly', 'gumroad', 'grabcad.com', 'shapeways.com', '3docean.net', '3dexport.com', 'pinshape.com', 'daz3d.com', 'assetstore.unity3d.com', 'renderosity.com', 'free3d.com', 'cults3d.com', '3dsky.org', 'highend3d.com'];
57892    downcasedInputValue = value.toLowerCase();
57893    competitorsList.forEach(function(competitor) {
57894      if (downcasedInputValue.indexOf(competitor) !== -1) {
57895        return competitorNotMentioned = false;
57896      }
57897    });
57898    return competitorNotMentioned;
57899  }, 'Please note that advertising other marketplaces is not allowed on your product information.');
57900
57901  $.validator.addMethod('noEmoji', function(value) {
57902    this.$re = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g;
57903    return !this.$re.test(value);
57904  }, 'Emojis are not allowed');
57905
57906}).call(this);
57907(function() {
57908  var base,
57909    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
57910    hasProp = {}.hasOwnProperty;
57911
57912  if ((base = App.Pages).Profile == null) {
57913    base.Profile = {};
57914  }
57915
57916  App.Pages.Profile.ConversationsShow = (function(superClass) {
57917    extend(ConversationsShow, superClass);
57918
57919    function ConversationsShow() {
57920      return ConversationsShow.__super__.constructor.apply(this, arguments);
57921    }
57922
57923    ConversationsShow.prototype.initialize = function() {
57924      this.$form = $('.edit_item_price_offer');
57925      this.$message_form = $('.new_conversation_message');
57926      $('.js-novalidation').on('click', function() {
57927        return $('.js-price').rules('remove');
57928      });
57929      this.$message_form.submit(function() {
57930        if ($('.new_conversation_message').valid()) {
57931          $('.js-disable-reply-button').attr('disabled', true);
57932        }
57933        return true;
57934      });
57935      this.initTooltip();
57936      return this.initValidation();
57937    };
57938
57939    ConversationsShow.prototype.initTooltip = function() {
57940      return $('.js-tooltip-with-list').tooltipster({
57941        theme: 'tooltip--light tooltip--popover tooltip--is-list',
57942        animation: 'grow',
57943        interactive: true,
57944        content: $('#hire-me-button-tooltip')
57945      });
57946    };
57947
57948    ConversationsShow.prototype.validationSettings = function() {
57949      return {
57950        rules: {
57951          'item_price_offer[offer_price]': {
57952            required: true,
57953            number: true,
57954            greaterThan: 2,
57955            notLowerThan50Percent: true
57956          },
57957          'conversation_message[message]': {
57958            required: true
57959          }
57960        },
57961        messages: {
57962          'item_price_offer[offer_price]': {
57963            greaterThan: $.format('Price must be greater than ${0}')
57964          }
57965        }
57966      };
57967    };
57968
57969    ConversationsShow.prototype.initValidation = function() {
57970      this.$form.validate(this.validationSettings());
57971      return this.$message_form.validate(this.validationSettings());
57972    };
57973
57974    return ConversationsShow;
57975
57976  })(App.Core.Page);
57977
57978}).call(this);
57979(function() {
57980  this.JST || (this.JST = {});
57981  this.JST["refunds/refund"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
57982    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
57983        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
57984          return parent[propertyName];
57985        }
57986        return undefined
57987    };
57988
57989  return "<tr class=\"u-text-center\">\n    <td>"
57990    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"orderId") || (depth0 != null ? lookupProperty(depth0,"orderId") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}
57990),{"name":"orderId","hash":{},"data":data,"loc":{"start":{"line":2,"column":8},"end":{"line":2,"column":19}}}) : helper)))
57991    + "</td>\n    <td>"
57992    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"purchased") || (depth0 != null ? lookupProperty(depth0,"purchased") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"purchased","hash":{},"data":data,"loc":{"start":{"line":3,"column":8},"end":{"line":3,"column":21}}}) : helper)))
57993    + "</td>\n    <td>"
57994    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"title") || (depth0 != null ? lookupProperty(depth0,"title") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"title","hash":{},"data":data,"loc":{"start":{"line":4,"column":8},"end":{"line":4,"column":17}}}) : helper)))
57995    + "</td>\n    <td>"
57996    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"buyer") || (depth0 != null ? lookupProperty(depth0,"buyer") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"buyer","hash":{},"data":data,"loc":{"start":{"line":5,"column":8},"end":{"line":5,"column":17}}}) : helper)))
57997    + "</td>\n    <td>$"
57998    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"amount") || (depth0 != null ? lookupProperty(depth0,"amount") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"amount","hash":{},"data":data,"loc":{"start":{"line":6,"column":9},"end":{"line":6,"column":19}}}) : helper)))
57999    + "</td>\n    <td>$"
58000    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"royaltiesAmount") || (depth0 != null ? lookupProperty(depth0,"royaltiesAmount") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"royaltiesAmount","hash":{},"data":data,"loc":{"start":{"line":7,"column":9},"end":{"line":7,"column":28}}}) : helper)))
58001    + "</td>\n    <td>"
58002    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"refundDate") || (depth0 != null ? lookupProperty(depth0,"refundDate") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"refundDate","hash":{},"data":data,"loc":{"start":{"line":8,"column":8},"end":{"line":8,"column":22}}}) : helper)))
58003    + "</td>\n    <td>"
58004    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"refundReason") || (depth0 != null ? lookupProperty(depth0,"refundReason") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"refundReason","hash":{},"data":data,"loc":{"start":{"line":9,"column":8},"end":{"line":9,"column":24}}}) : helper)))
58005    + "</td>\n</tr>";
58006},"useData":true});
58007  return this.JST["refunds/refund"];
58008}).call(this);
58009(function() {
58010  var base,
58011    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
58012    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
58013    hasProp = {}.hasOwnProperty;
58014
58015  if ((base = App.Pages).Profile == null) {
58016    base.Profile = {};
58017  }
58018
58019  App.Pages.Profile.SalesIndex = (function(superClass) {
58020    extend(SalesIndex, superClass);
58021
58022    function SalesIndex() {
58023      this.onCouponLinkClick = bind(this.onCouponLinkClick, this);
58024      return SalesIndex.__super__.constructor.apply(this, arguments);
58025    }
58026
58027    SalesIndex.prototype.initialize = function() {
58028      this.initTabs();
58029      this.initTooltip();
58030      this.initCoupons();
58031      this.initValidation();
58032      return this.bindEvents();
58033    };
58034
58035    SalesIndex.prototype.initTabs = function() {
58036      return $('.js-tabs').easytabs({
58037        animate: false,
58038        panelActiveClass: 'is-active',
58039        tabActiveClass: 'is-active'
58040      });
58041    };
58042
58043    SalesIndex.prototype.initTooltip = function() {
58044      return $('.js-payout-fee-tooltip').tooltipster({
58045        theme: 'tooltip--light tooltip--popover u-text-center',
58046        animation: 'grow',
58047        interactive: true,
58048        position: 'top',
58049        maxWidth: 650,
58050        contentAsHTML: true,
58051        functionInit: function(origin) {
58052          return origin.data('content');
58053        }
58054      });
58055    };
58056
58057    SalesIndex.prototype.initCoupons = function() {
58058      this.$couponLink = $('.js-new-coupon');
58059      this.$couponFormContainer = $('.js-coupon-container');
58060      return this.$couponLink.on('click', this.onCouponLinkClick);
58061    };
58062
58063    SalesIndex.prototype.bindEvents = function() {
58064      $('.js-refunds').on('click', this.onRefundsClick);
58065      return $('.js-sale-off-participation').on('change', this.onSaleOffParticipationChange);
58066    };
58067
58068    SalesIndex.prototype.onCouponLinkClick = function(e) {
58069      e.preventDefault();
58070      e.stopPropagation();
58071      return this.$couponLink.fadeOut().promise().done((function(_this) {
58072        return function() {
58073          _this.$couponFormContainer.parents('.table-responsive').hide().removeClass('is-hidden').fadeIn(1000);
58074          return _this.$couponFormContainer.hide().removeClass('is-hidden').fadeIn(1000);
58075        };
58076      })(this));
58077    };
58078
58079    SalesIndex.prototype.onRefundsClick = function(e) {
58080      return $.ajax('/profile/refunds', {
58081        data: {
58082          refund_ids: $(e.target).data('refund-ids')
58083        },
58084        success: function(response) {
58085          var $tbody;
58086          $tbody = $('#modal-refunds tbody');
58087          $tbody.find('tr').remove();
58088          _.each(response.data, function(refund) {
58089            return $tbody.append(JST['refunds/refund'](refund.attributes));
58090          });
58091          return $('#modal-refunds').makeMeModal();
58092        }
58093      });
58094    };
58095
58096    SalesIndex.prototype.validationSettings = function() {
58097      return {
58098        rules: {
58099          'coupon[discount]': {
58100            required: true,
58101            number: true,
58102            min: 1,
58103            max: 99
58104          },
58105          'coupon[max_usage_count]': {
58106            required: true,
58107            min: 1,
58108            max: 1000000
58109          }
58110        },
58111        messages: {
58112          'coupon[discount]': {
58113            required: 'Please enter a discount'
58114          }
58115        },
58116        errorPlacement: function(error, element) {
58117          if (element.attr('name') === 'coupon[discount]') {
58118            return error.appendTo($('.js-discount-error-field'));
58119          } else {
58120            return error.insertAfter(element);
58121          }
58122        }
58123      };
58124    };
58125
58126    SalesIndex.prototype.initValidation = function() {
58127      return $('.new_coupon').validate(this.validationSettings());
58128    };
58129
58130    SalesIndex.prototype.onSaleOffParticipationChange = function(e) {
58131      var message;
58132      message = $('.js-sale-off-participation').is(":checked") ? 'You are now participating in sale off campaigns' : 'You are no longer participating in sale off campaigns';
58133      return $.ajax({
58134        url: '/profile/settings/toggle-sale-off-participation',
58135        type: 'PUT',
58136        data: {
58137          sale_off: e.target.checked
58138        },
58139        success: function() {
58140          return App.events.publish('app:notification:flash:success', message);
58141        }
58142      });
58143    };
58144
58145    return SalesIndex;
58146
58147  })(App.Core.Page);
58148
58149}).call(this);
58150(function() {
58151  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
58152    hasProp = {}.hasOwnProperty;
58153
58154  App.Pages.Profile.CouponsCreate = (function(superClass) {
58155    extend(CouponsCreate, superClass);
58156
58157    function CouponsCreate() {
58158      return CouponsCreate.__super__.constructor.apply(this, arguments);
58159    }
58160
58161    CouponsCreate.prototype.initialize = function() {
58162      CouponsCreate.__super__.initialize.apply(this, arguments);
58163      return this.showForm();
58164    };
58165
58166    CouponsCreate.prototype.initTabs = function() {
58167      return $('.js-tabs').easytabs({
58168        animate: false,
58169        panelActiveClass: 'is-active',
58170        tabActiveClass: 'is-active',
58171        defaultTab: 'li:last-child'
58172      });
58173    };
58174
58175    CouponsCreate.prototype.showForm = function() {
58176      return $('.js-coupon-container').removeClass('is-hidden').parents('.table').removeClass('is-hidden');
58177    };
58178
58179    return CouponsCreate;
58180
58181  })(App.Pages.Profile.SalesIndex);
58182
58183}).call(this);
58184/*
58185 * HTML5 Sortable jQuery Plugin
58186 * http://farhadi.ir/projects/html5sortable
58187 *
58188 * Copyright 2012, Ali Farhadi
58189 * Released under the MIT license.
58190 */
58191
58192(function($) {
58193var dragging, placeholders = $();
58194$.fn.sortable = function(options) {
58195	var method = String(options);
58196	options = $.extend({
58197		connectWith: false
58198	}, options);
58199	return this.each(function() {
58200		if (/^enable|disable|destroy$/.test(method)) {
58201			var items = $(this).children($(this).data('items')).attr('draggable', method == 'enable');
58202			if (method == 'destroy') {
58203				items.add(this).removeData('connectWith items')
58204					.off('dragstart.h5s dragend.h5s selectstart.h5s dragover.h5s dragenter.h5s drop.h5s');
58205			}
58206			return;
58207		}
58208		var isHandle, index, items = $(this).children(options.items);
58209		var placeholder = $('<' + (/^ul|ol$/i.test(this.tagName) ? 'li' : 'div') + ' class="sortable__item is-placeholder">');
58210		items.find(options.handle).mousedown(function() {
58211			isHandle = true;
58212		}).mouseup(function() {
58213			isHandle = false;
58214		});
58215		$(this).data('items', options.items)
58216		placeholders = placeholders.add(placeholder);
58217		if (options.connectWith) {
58218			$(options.connectWith).add(this).data('connectWith', options.connectWith);
58219		}
58220		items.attr('draggable', 'true').on('dragstart.h5s', function(e) {
58221			if (options.handle && !isHandle) {
58222				return false;
58223			}
58224			isHandle = false;
58225			var dt = e.originalEvent.dataTransfer;
58226			dt.effectAllowed = 'move';
58227			dt.setData('Text', 'dummy');
58228			index = (dragging = $(this)).addClass('sortable-dragging').index();
58229		}).on('dragend.h5s', function() {
58230			if (!dragging) {
58231				return;
58232			}
58233			dragging.removeClass('sortable-dragging').show();
58234			placeholders.detach();
58235			if (index != dragging.index()) {
58236				dragging.parent().trigger('sortupdate', {item: dragging});
58237			}
58238			dragging = null;
58239		}).not('a[href], img').on('selectstart.h5s', function() {
58240			this.dragDrop && this.dragDrop();
58241			return false;
58242		}).end().add([this, placeholder]).on('dragover.h5s dragenter.h5s drop.h5s', function(e) {
58243			if (!items.is(dragging) && options.connectWith !== $(dragging).parent().data('connectWith')) {
58244				return true;
58245			}
58246			if (e.type == 'drop') {
58247				e.stopPropagation();
58248				placeholders.filter(':visible').after(dragging);
58249				dragging.trigger('dragend.h5s');
58250				return false;
58251			}
58252			e.preventDefault();
58253			e.originalEvent.dataTransfer.dropEffect = 'move';
58254			if (items.is(this)) {
58255				if (options.forcePlaceholderSize) {
58256					placeholder.height(dragging.outerHeight());
58257				}
58258				dragging.hide();
58259				$(this)[placeholder.index() < $(this).index() ? 'after' : 'before'](placeholder);
58260				placeholders.not(placeholder).detach();
58261			} else if (!placeholders.is(this) && !$(this).children(options.items).length) {
58262				placeholders.detach();
58263				$(this).append(placeholder);
58264			}
58265			return false;
58266		});
58267	});
58268};
58269})(jQuery);
58270/*
58271
58272	jQuery Tags Input Plugin 1.3.3
58273
58274	Copyright (c) 2011 XOXCO, Inc
58275
58276	Documentation for this plugin lives here:
58277	http://xoxco.com/clickable/jquery-tags-input
58278
58279	Licensed under the MIT license:
58280	http://www.opensource.org/licenses/mit-license.php
58281
58282	[email protected]
58283
58284
58285	MODIFIED:
58286	Date: 2015-06-22
58287	Task: #DT-1534
58288
58289	$.fn.tagExist method - same tags with uppercase characters and not are same tag (car/Car/CAR)
58290*/
58291
58292
58293(function($) {
58294
58295	var delimiter = new Array();
58296	var tags_callbacks = new Array();
58297	$.fn.doAutosize = function(o){
58298	    var minWidth = $(this).data('minwidth'),
58299	        maxWidth = $(this).data('maxwidth'),
58300	        val = '',
58301	        input = $(this),
58302	        testSubject = $('#'+$(this).data('tester_id'));
58303
58304	    if (val === (val = input.val())) {return;}
58305
58306	    // Enter new content into testSubject
58307	    var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
58308	    testSubject.html(escaped);
58309	    // Calculate new width + whether to change
58310	    var testerWidth = testSubject.width(),
58311	        newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
58312	        currentWidth = input.width(),
58313	        isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
58314	                             || (newWidth > minWidth && newWidth < maxWidth);
58315
58316	    // Animate width
58317	    if (isValidWidthChange) {
58318	        input.width(newWidth);
58319	    }
58320
58321
58322  };
58323  $.fn.resetAutosize = function(options){
58324    // alert(JSON.stringify(options));
58325    var minWidth =  $(this).data('minwidth') || options.minInputWidth || $(this).width(),
58326        maxWidth = $(this).data('maxwidth') || options.maxInputWidth || ($(this).closest('.tagsinput').width() - options.inputPadding),
58327        val = '',
58328        input = $(this),
58329        testSubject = $('<tester/>').css({
58330            position: 'absolute',
58331            top: -9999,
58332            left: -9999,
58333            width: 'auto',
58334            fontSize: input.css('fontSize'),
58335            fontFamily: input.css('fontFamily'),
58336            fontWeight: input.css('fontWeight'),
58337            letterSpacing: input.css('letterSpacing'),
58338            whiteSpace: 'nowrap'
58339        }),
58340        testerId = $(this).attr('id')+'_autosize_tester';
58341    if(! $('#'+testerId).length > 0){
58342      testSubject.attr('id', testerId);
58343      testSubject.appendTo('body');
58344    }
58345
58346    input.data('minwidth', minWidth);
58347    input.data('maxwidth', maxWidth);
58348    input.data('tester_id', testerId);
58349    input.css('width', minWidth);
58350  };
58351
58352	$.fn.addTag = function(value,options) {
58353			options = jQuery.extend({focus:false,callback:true},options);
58354			this.each(function() {
58355				var id = $(this).attr('id');
58356
58357				var tagslist = $(this).val().split(delimiter[id]);
58358				if (tagslist[0] == '') {
58359					tagslist = new Array();
58360				}
58361
58362				value = jQuery.trim(value);
58363
58364				if (options.unique) {
58365					var skipTag = $(this).tagExist(value);
58366					if(skipTag == true) {
58367					    //Marks fake input as not_valid to let styling it
58368    				    $('#'+id+'_tag').addClass('is-invalid');
58369    				}
58370				} else {
58371					var skipTag = false;
58372				}
58373
58374				if (value !='' && skipTag != true) {
58375                    $('<span>').addClass('tag').append(
58376                        $('<span>').text(value).append('&nbsp;&nbsp;'),
58377                        $('<a>', {
58378                            href  : '#',
58379                            title : 'Removing tag',
58380                            text  : 'x'
58381                        }).click(function () {
58382                            return $('#' + id).removeTag(escape(value));
58383                        })
58384                    ).insertBefore('#' + id + '_addTag');
58385
58386					tagslist.push(value);
58387
58388					$('#'+id+'_tag').val('');
58389					if (options.focus) {
58390						$('#'+id+'_tag').focus();
58391					} else {
58392						$('#'+id+'_tag').blur();
58393					}
58394
58395					$.fn.tagsInput.updateTagsField(this,tagslist);
58396
58397					if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
58398						var f = tags_callbacks[id]['onAddTag'];
58399						f.call(this, value);
58400					}
58401					if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
58402					{
58403						var i = tagslist.length;
58404						var f = tags_callbacks[id]['onChange'];
58405						f.call(this, $(this), tagslist[i-1]);
58406					}
58407				}
58408
58409			});
58410
58411			return false;
58412		};
58413
58414	$.fn.removeTag = function(value) {
58415			value = unescape(value);
58416			this.each(function() {
58417				var id = $(this).attr('id');
58418
58419				var old = $(this).val().split(delimiter[id]);
58420
58421				$('#'+id+'_tagsinput .tag').remove();
58422				str = '';
58423				for (i=0; i< old.length; i++) {
58424					if (old[i]!=value) {
58425						str = str + delimiter[id] +old[i];
58426					}
58427				}
58428
58429				$.fn.tagsInput.importTags(this,str);
58430
58431				if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
58432					var f = tags_callbacks[id]['onRemoveTag'];
58433					f.call(this, value);
58434				}
58435			});
58436
58437			return false;
58438		};
58439
58440	$.fn.tagExist = function(val) {
58441		var id = $(this).attr('id');
58442		var tagslist = $(this).val().toLowerCase().split(delimiter[id]);
58443		return (jQuery.inArray(val.toLowerCase(), tagslist) >= 0); //true when tag exists, false when not
58444	};
58445
58446	// clear all existing tags and import new ones from a string
58447	$.fn.importTags = function(str) {
58448                id = $(this).attr('id');
58449		$('#'+id+'_tagsinput .tag').remove();
58450		$.fn.tagsInput.importTags(this,str);
58451	}
58452
58453	$.fn.tagsInput = function(options) {
58454    var settings = jQuery.extend({
58455      interactive:true,
58456      defaultText:'add a tag',
58457      minChars:0,
58458      width:'300px',
58459      height:'100px',
58460      autocomplete: {selectFirst: false },
58461      'hide':true,
58462      'delimiter':',',
58463      'unique':true,
58464      removeWithBackspace:true,
58465      placeholderColor:'#666666',
58466      autosize: true,
58467      comfortZone: 20,
58468      inputPadding: 6*2
58469    },options);
58470
58471		this.each(function() {
58472			if (settings.hide) {
58473				$(this).hide();
58474			}
58475			var id = $(this).attr('id');
58476			if (!id || delimiter[$(this).attr('id')]) {
58477				id = $(this).attr('id', 'tags' + new Date().getTime()).attr('id');
58478			}
58479
58480			var data = jQuery.extend({
58481				pid:id,
58482				real_input: '#'+id,
58483				holder: '#'+id+'_tagsinput',
58484				input_wrapper: '#'+id+'_addTag',
58485				fake_input: '#'+id+'_tag'
58486			},settings);
58487
58488			delimiter[id] = data.delimiter;
58489
58490			if (settings.onAddTag || settings.onRemoveTag || settings.onChange) {
58491				tags_callbacks[id] = new Array();
58492				tags_callbacks[id]['onAddTag'] = settings.onAddTag;
58493				tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
58494				tags_callbacks[id]['onChange'] = settings.onChange;
58495			}
58496
58497			var markup = '<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">';
58498
58499			if (settings.interactive) {
58500				markup = markup + '<input id="'+id+'_tag" value="" data-default="'+settings.defaultText+'" />';
58501			}
58502
58503			markup = markup + '</div><div class="tags_clear"></div></div>';
58504
58505			$(markup).insertAfter(this);
58506
58507			$(data.holder).css('width',settings.width);
58508			$(data.holder).css('min-height',settings.height);
58509			$(data.holder).css('height','100%');
58510
58511			if ($(data.real_input).val()!='') {
58512				$.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
58513			}
58514			if (settings.interactive) {
58515				$(data.fake_input).val($(data.fake_input).attr('data-default'));
58516				//$(data.fake_input).css('color',settings.placeholderColor);
58517		        $(data.fake_input).resetAutosize(settings);
58518
58519				$(data.holder).bind('click',data,function(event) {
58520					$(event.data.fake_input).focus();
58521				});
58522
58523				$(data.fake_input).bind('focus',data,function(event) {
58524					if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
58525						$(event.data.fake_input).val('');
58526					}
58527					//$(event.data.fake_input).css('color','#000000');
58528				});
58529
58530				if (settings.autocomplete_url != undefined) {
58531					autocomplete_options = {source: settings.autocomplete_url};
58532					for (attrname in settings.autocomplete) {
58533						autocomplete_options[attrname] = settings.autocomplete[attrname];
58534					}
58535
58536					if (jQuery.Autocompleter !== undefined) {
58537						$(data.fake_input).autocomplete(settings.autocomplete_url, settings.autocomplete);
58538						$(data.fake_input).bind('result',data,function(event,data,formatted) {
58539							if (data) {
58540								$('#'+id).addTag(data[0] + "",{focus:true,unique:(settings.unique)});
58541							}
58542					  	});
58543					} else if (jQuery.ui.autocomplete !== undefined) {
58544						$(data.fake_input).autocomplete(autocomplete_options);
58545						$(data.fake_input).bind('autocompleteselect',data,function(event,ui) {
58546							$(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)});
58547							return false;
58548						});
58549					}
58550
58551
58552				} else {
58553						// if a user tabs out of the field, create a new tag
58554						// this is only available if autocomplete is not used.
58555						$(data.fake_input).bind('blur',data,function(event) {
58556							var d = $(this).attr('data-default');
58557							if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
58558								if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
58559									$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
58560							} else {
58561								$(event.data.fake_input).val($(event.data.fake_input).attr('data-default'));
58562								//$(event.data.fake_input).css('color',settings.placeholderColor);
58563							}
58564							return false;
58565						});
58566
58567				}
58568				// if user types a comma, create a new tag
58569				$(data.fake_input).bind('keypress',data,function(event) {
58570					if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13 ) {
58571					    event.preventDefault();
58572						if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
58573							$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
58574					  	$(event.data.fake_input).resetAutosize(settings);
58575						return false;
58576					} else if (event.data.autosize) {
58577			            $(event.data.fake_input).doAutosize(settings);
58578
58579          			}
58580				});
58581				//Delete last tag on backspace
58582				data.removeWithBackspace && $(data.fake_input).bind('keydown', function(event)
58583				{
58584					if(event.keyCode == 8 && $(this).val() == '')
58585					{
58586						 event.preventDefault();
58587						 var last_tag = $(this).closest('.tagsinput').find('.tag:last').text();
58588						 var id = $(this).attr('id').replace(/_tag$/, '');
58589						 last_tag = last_tag.replace(/[\s]+x$/, '');
58590						 $('#' + id).removeTag(escape(last_tag));
58591						 $(this).trigger('focus');
58592					}
58593				});
58594				$(data.fake_input).blur();
58595
58596				//Removes the not_valid class when user changes the value of the fake input
58597				if(data.unique) {
58598				    $(data.fake_input).keydown(function(event){
58599				        if(event.keyCode == 8 || String.fromCharCode(event.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/)) {
58600				            $(this).removeClass('is-invalid');
58601				        }
58602				    });
58603				}
58604			} // if settings.interactive
58605		});
58606
58607		return this;
58608
58609	};
58610
58611	$.fn.tagsInput.updateTagsField = function(obj,tagslist) {
58612		var id = $(obj).attr('id');
58613		$(obj).val(tagslist.join(delimiter[id]));
58614	};
58615
58616	$.fn.tagsInput.importTags = function(obj,val) {
58617		$(obj).val('');
58618		var id = $(obj).attr('id');
58619		var tags = val.split(delimiter[id]);
58620		for (i=0; i<tags.length; i++) {
58621			$(obj).addTag(tags[i],{focus:false,callback:false});
58622		}
58623		if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
58624		{
58625			var f = tags_callbacks[id]['onChange'];
58626			f.call(obj, obj, tags[i]);
58627		}
58628	};
58629
58630})(jQuery);
58631(function() {
58632  this.JST || (this.JST = {});
58633  this.JST["file_upload/file"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
58634    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
58635        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
58636          return parent[propertyName];
58637        }
58638        return undefined
58639    };
58640
58641  return "<div class=\"js-file-wrap\">\n  <p>\n    <a class=\"attachment\" href=\""
58642    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"file_url") || (depth0 != null ? lookupProperty(depth0,"file_url") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}
58642),{"name":"file_url","hash":{},"data":data,"loc":{"start":{"line":3,"column":32},"end":{"line":3,"column":44}}}) : helper)))
58643    + "\" target=\"_blank\">\n      <span>"
58644    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"file_name") || (depth0 != null ? lookupProperty(depth0,"file_name") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"file_name","hash":{},"data":data,"loc":{"start":{"line":4,"column":12},"end":{"line":4,"column":25}}}) : helper)))
58645    + "</span>\n    </a>\n    <a class=\"remove\" href=\""
58646    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"delete_url") || (depth0 != null ? lookupProperty(depth0,"delete_url") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"delete_url","hash":{},"data":data,"loc":{"start":{"line":6,"column":28},"end":{"line":6,"column":42}}}) : helper)))
58647    + "\" data-method=\"delete\" data-remote=\"true\" rel=\"nofollow\">\n      <i class=\"far height-24 pull-right fa-times\"></i>\n    </a>\n    <br>\n  </p>\n</div>";
58648},"useData":true});
58649  return this.JST["file_upload/file"];
58650}).call(this);
58651(function() {
58652  this.JST || (this.JST = {});
58653  this.JST["file_upload/file_v2"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
58654    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
58655        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
58656          return parent[propertyName];
58657        }
58658        return undefined
58659    };
58660
58661  return "<div class=\"js-file-wrap upload--file\">\n  <i class=\"fal fa-paperclip height-24 is-not-spaced\"></i>\n  <div class=\"file-info\">\n    <a class=\"attachment\" href=\""
58662    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"file_url") || (depth0 != null ? lookupProperty(depth0,"file_url") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"file_url","hash":{},"data":data,"loc":{"start":{"line":4,"column":32},"end":{"line":4,"column":44}}}) : helper)))
58663    + "\" target=\"_blank\">
58663\n      <span>"
58664    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"file_name") || (depth0 != null ? lookupProperty(depth0,"file_name") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"file_name","hash":{},"data":data,"loc":{"start":{"line":5,"column":12},"end":{"line":5,"column":25}}}) : helper)))
58665    + "</span>\n    </a>\n  </div>\n  <div class=\"file-remove\">\n    <a class=\"remove\" href=\""
58666    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"delete_url") || (depth0 != null ? lookupProperty(depth0,"delete_url") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"delete_url","hash":{},"data":data,"loc":{"start":{"line":9,"column":28},"end":{"line":9,"column":42}}}) : helper)))
58667    + "\" data-method=\"delete\" data-remote=\"true\" rel=\"nofollow\">\n      <i class=\"far fa-times height-24 is-not-spaced\"></i>\n    </a>\n  </div>\n</div>";
58668},"useData":true});
58669  return this.JST["file_upload/file_v2"];
58670}).call(this);
58671(function() {
58672  this.JST || (this.JST = {});
58673  this.JST["file_upload/image"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
58674    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
58675        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
58676          return parent[propertyName];
58677        }
58678        return undefined
58679    };
58680
58681  return "<div class=\"js-file-wrap upload--image\">\n  <div class=\"upload__image-wrapper\">\n    <img src=\""
58682    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"file_url") || (depth0 != null ? lookupProperty(depth0,"file_url") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"file_url","hash":{},"data":data,"loc":{"start":{"line":3,"column":14},"end":{"line":3,"column":26}}}) : helper)))
58683    + "\" />\n    <a class=\"remove\" href=\""
58684    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"delete_url") || (depth0 != null ? lookupProperty(depth0,"delete_url") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"delete_url","hash":{},"data":data,"loc":{"start":{"line":4,"column":28},"end":{"line":4,"column":42}}}) : helper)))
58685    + "\" data-method=\"delete\" data-remote=\"true\" rel=\"nofollow\">\n      <i class=\"far height-12 fa-times is-not-spaced\"></i>\n    </a>\n  </div>\n</div>";
58686},"useData":true});
58687  return this.JST["file_upload/image"];
58688}).call(this);
58689(function() {
58690  this.JST || (this.JST = {});
58691  this.JST["file_upload/progress"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
58692    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
58693        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
58694          return parent[propertyName];
58695        }
58696        return undefined
58697    };
58698
58699  return "<div class=\"file file-upload-progress\" data-id=\""
58700    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"id") || (depth0 != null ? lookupProperty(depth0,"id") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"id","hash":{},"data":data,"loc":{"start":{"line":1,"column":48},"end":{"line":1,"column":54}}}) : helper)))
58701    + "\">\n  <div class=\"progress-bar__info\">\n      Uploading "
58702    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"file_name") || (depth0 != null ? lookupProperty(depth0,"file_name") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}
58702),{"name":"file_name","hash":{},"data":data,"loc":{"start":{"line":3,"column":16},"end":{"line":3,"column":29}}}) : helper)))
58703    + " ...\n\n      <span class=\"progress-bar__percentage\"></span>\n\n      <a href=\"javascript:;\" class=\"js-cancel-upload\">cancel</a>\n  </div>\n  <div class=\"progress-bar\">\n      <div class=\"progress-bar__progress\"></div>\n  </div>\n</div>";
58704},"useData":true});
58705  return this.JST["file_upload/progress"];
58706}).call(this);
58707(function() {
58708  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
58709
58710  App.Modules.FileUpload = (function() {
58711    FileUpload.prototype.maxFileSize = 100 * 1024 * 1024;
58712
58713    FileUpload.prototype.templates = {
58714      progress: JST['file_upload/progress'],
58715      file: JST['file_upload/file'],
58716      image: JST['file_upload/image'],
58717      file_v2: JST['file_upload/file_v2']
58718    };
58719
58720    FileUpload.prototype.options = {
58721      url: '',
58722      dataType: 'json',
58723      autoUpload: false,
58724      formData: {
58725        _method: 'POST'
58726      }
58727    };
58728
58729    FileUpload.prototype.uploads = {};
58730
58731    function FileUpload(optionsUrl, $selector) {
58732      if ($selector == null) {
58733        $selector = $('#fileupload');
58734      }
58735      this.cancelUpload = bind(this.cancelUpload, this);
58736      this.fileProgress = bind(this.fileProgress, this);
58737      this.fileUploadFailed = bind(this.fileUploadFailed, this);
58738      this.fileUploaded = bind(this.fileUploaded, this);
58739      this.fileAdded = bind(this.fileAdded, this);
58740      if ($selector.attr('id') === 'fileupload') {
58741        this.$filesElement = $('.files');
58742      } else if ($selector.attr('id') === 'job-file-upload') {
58743        this.$filesElement = $('.files');
58744        this.$imagesElement = $('.images');
58745        this.isJobFileUpload = true;
58746      } else {
58747        this.$filesElement = $('.files.' + $selector.attr('id'));
58748      }
58749      this.options.url = optionsUrl;
58750      $selector.fileupload(this.options).on('fileuploadadd', this.fileAdded).on('fileuploadprogress', this.fileProgress).on('fileuploaddone', this.fileUploaded).on('fileuploadfail', this.fileUploadFailed);
58751      $(document).on('click', '.file-upload-progress .js-cancel-upload', this.cancelUpload);
58752      $(document).on('click', '#files a.remove', this.removeFile);
58753    }
58754
58755    FileUpload.prototype.fileAdded = function(_, data) {
58756      return $.each(data.files, (function(_this) {
58757        return function(index, file) {
58758          var upload;
58759          if (file.size > _this.maxFileSize) {
58760            App.events.publish('app:notification:flash:error', I18n.t('activerecord.errors.messages.file_too_big'));
58761            return;
58762          }
58763          upload = {
58764            id: guid(),
58765            file_name: file.name
58766          };
58767          if (file.type.includes('image') && _this.isJobFileUpload) {
58768            data.context = $(_this.templates.progress(upload)).appendTo(_this.$imagesElement);
58769          } else {
58770            data.context = $(_this.templates.progress(upload)).appendTo(_this.$filesElement);
58771          }
58772          _this.uploads[upload.id] = upload;
58773          return upload.jqXHR = data.submit();
58774        };
58775      })(this));
58776    };
58777
58778    FileUpload.prototype.fileUploaded = function(_, data) {
58779      return $.each(data.result, (function(_this) {
58780        return function(index, file) {
58781          var error, id_field, progressBar, upload;
58782          if (file.url) {
58783            upload = {
58784              file_name: file.name,
58785              file_url: file.url,
58786              delete_url: file.delete_url
58787            };
58788            if (_this.isJobFileUpload) {
58789              if (file.type === 'image') {
58790                $(data.context).html(_this.templates.image(upload));
58791              } else {
58792                $(data.context).html(_this.templates.file_v2(upload));
58793              }
58794            } else {
58795              $(data.context).html(_this.templates.file(upload));
58796            }
58797            id_field = $('<input type="hidden" value="' + file.id + '" name="attachment_ids[]" />');
58798            data.context.append(id_field);
58799            progressBar = _this.$filesElement.find(".file-upload-progress[data-id=" + upload.id + "]");
58800            return progressBar.remove();
58801          } else if (file.error) {
58802            error = $('<span class="text-danger"/>').text(file.error);
58803            $(data.context.children()[index]).append('<br>').append(error);
58804            upload = _this.uploadByFileName(file.name);
58805            progressBar = _this.$filesElement.find(".file-upload-progress[data-id=" + upload.id + "]");
58806            return progressBar.remove();
58807          }
58808        };
58809      })(this));
58810    };
58811
58812    FileUpload.prototype.fileUploadFailed = function(_, data) {
58813      var errMsg;
58814      errMsg = data.jqXHR.responseJSON.errors.join(', ');
58815      return $.each(data.files, (function(_this) {
58816        return function(index, file) {
58817          var progressBar;
58818          $(data.context).append($('<span class="text-danger text-warning"/>').text(errMsg || 'File upload failed.'));
58819          progressBar = _this.$filesElement.find(".file-upload-progress[data-id=" + (_this.uploadByFileName(file.name).id) + "]");
58820          return progressBar.remove();
58821        };
58822      })(this));
58823    };
58824
58825    FileUpload.prototype.fileProgress = function(_, data) {
58826      return $.each(data.files, (function(_this) {
58827        return function(index, file) {
58828          var progress, progressBar, progressText, upload;
58829          progress = parseInt(data.loaded / data.total * 100, 10);
58830          progressText = progress === 100 ? 'processing' : progress + " %";
58831          upload = _this.uploadByFileName(file.name);
58832          if (progress > 95) {
58833            upload.jqXHR.forbidCanceling = true;
58834          }
58835          progressBar = _this.$filesElement.find(".file-upload-progress[data-id=" + upload.id + "]");
58836          progressBar.find('.progress-bar__progress').css('width', progress + "%");
58837          return progressBar.find(".progress-bar__info .progress-bar__percentage").html(progressText);
58838        };
58839      })(this));
58840    };
58841
58842    FileUpload.prototype.uploadByFileName = function(filename) {
58843      return _.detect(this.uploads, function(obj, index) {
58844        return (obj != null ? obj.file_name : void 0) === filename;
58845      });
58846    };
58847
58848    FileUpload.prototype.cancelUpload = function(event) {
58849      var progressBar, uploadId;
58850      mute(event);
58851      progressBar = $(event.target).parents('.file-upload-progress');
58852      uploadId = progressBar.data('id');
58853      if (!this.uploads[uploadId] || this.uploads[uploadId].jqXHR.forbidCanceling) {
58854        return;
58855      }
58856      this.uploads[uploadId].jqXHR.abort();
58857      progressBar.remove();
58858      return this.uploads[uploadId] = null;
58859    };
58860
58861    FileUpload.prototype.removeFile = function(e) {
58862      return $(this).closest('.js-file-wrap').remove();
58863    };
58864
58865    return FileUpload;
58866
58867  })();
58868
58869}).call(this);
58870(function() {
58871  this.JST || (this.JST = {});
58872  this.JST["galleries/uploaded_image"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
58873    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
58874        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
58875          return parent[propertyName];
58876        }
58877        return undefined
58878    };
58879
58880  return "<li class=\"sortable__item\"><img src=\""
58881    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"thumb_image") || (depth0 != null ? lookupProperty(depth0,"thumb_image") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}
58881),{"name":"thumb_image","hash":{},"data":data,"loc":{"start":{"line":1,"column":37},"end":{"line":1,"column":52}}}) : helper)))
58882    + "\" /><span class=\"u-text-center u-block\"><input type=\"checkbox\" /></span><input name=\"gallery[images][]\" type=\"hidden\" value=\""
58883    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"gallery_image_id") || (depth0 != null ? lookupProperty(depth0,"gallery_image_id") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"gallery_image_id","hash":{},"data":data,"loc":{"start":{"line":1,"column":177},"end":{"line":1,"column":197}}}) : helper)))
58884    + "\" /></li>";
58885},"useData":true});
58886  return this.JST["galleries/uploaded_image"];
58887}).call(this);
58888(function() {
58889  var base,
58890    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
58891    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
58892    hasProp = {}.hasOwnProperty;
58893
58894  if ((base = App.Pages).Profile == null) {
58895    base.Profile = {};
58896  }
58897
58898  App.Pages.Profile.GalleriesNew = (function(superClass) {
58899    extend(GalleriesNew, superClass);
58900
58901    function GalleriesNew() {
58902      this.onGalleryError = bind(this.onGalleryError, this);
58903      this.onSubmitGalleryForm = bind(this.onSubmitGalleryForm, this);
58904      this.cancelUpload = bind(this.cancelUpload, this);
58905      this.initUploader = bind(this.initUploader, this);
58906      this.removeSelectedImages = bind(this.removeSelectedImages, this);
58907      this.checkImages = bind(this.checkImages, this);
58908      this.bindEvents = bind(this.bindEvents, this);
58909      return GalleriesNew.__super__.constructor.apply(this, arguments);
58910    }
58911
58912    GalleriesNew.prototype.uploads = {};
58913
58914    GalleriesNew.prototype.initialize = function() {
58915      this.$el = $("#gallery-form-container");
58916      this.$imagesContainer = $("#gallery-uploaded-images");
58917      this.$removeImagesBtn = $("#gallery-remove-selected");
58918      this.$uploadProgress = $("#gallery-upload-progress");
58919      this.$form = $('#gallery-form');
58920      this.$imagesCount = $('#images-count');
58921      this.initValidation();
58922      this.initUploader();
58923      this.checkImages();
58924      this.bindEvents();
58925      this.initTags();
58926      this.$imagesContainer.sortable();
58927      return this.totalUploadFiles = 0;
58928    };
58929
58930    GalleriesNew.prototype.bindEvents = function() {
58931      this.$removeImagesBtn.on('click', this.removeSelectedImages);
58932      this.$form.on('ajax:before', this.onSubmitGalleryForm);
58933      this.$form.on('ajax:success', this.onGalleryPublished);
58934      return this.$form.on('ajax:error', this.onGalleryError);
58935    };
58936
58937    GalleriesNew.prototype.validationSetings = function() {
58938      return {
58939        ignore: [],
58940        rules: {
58941          'gallery[title]': {
58942            required: true,
58943            validateCompetitors: true
58944          },
58945          'gallery[description]': {
58946            validateCompetitors: true
58947          },
58948          'gallery[tags]': {
58949            validateCompetitors: true
58950          },
58951          tags_count: {
58952            required: true,
58953            min: 3
58954          },
58955          images_count: {
58956            required: true,
58957            min: 1
58958          }
58959        },
58960        messages: {
58961          tags_count: {
58962            min: $.format("You should add at least {0} tags.")
58963          },
58964          images_count: {
58965            min: $.format("Please upload at least {0} image.")
58966          }
58967        },
58968        errorPlacement: function(error, element) {
58969          if (element.attr('name') === 'gallery[tags]') {
58970            return error.appendTo($('.js-tags-error-field'));
58971          } else {
58972            return error.insertAfter(element);
58973          }
58974        }
58975      };
58976    };
58977
58978    GalleriesNew.prototype.initValidation = function() {
58979      return this.$form.validate(this.validationSetings());
58980    };
58981
58982    GalleriesNew.prototype.checkImages = function() {
58983      this.$imagesCount.val(this.$imagesContainer.find('li').length);
58984      this.$imagesCount.valid();
58985      if (this.$imagesContainer.find('li').length > 0) {
58986        return this.$el.find('#project-images').removeClass('is-hidden').show();
58987      } else {
58988        return this.$el.find('#project-images').hide().addClass('is-hidden');
58989      }
58990    };
58991
58992    GalleriesNew.prototype.removeSelectedImages = function() {
58993      this.$imagesContainer.find('input[type="checkbox"]:checked').each(function() {
58994        return $(this).parent().parent().remove();
58995      });
58996      this.checkImages();
58997      return false;
58998    };
58999
59000    GalleriesNew.prototype.initUploader = function() {
59001      this.$filesElement = $('.files');
59002      $(document).on('click', '.file-upload-progress .js-cancel-upload', this.cancelUpload);
59003      return $('#gallery-upload').fileupload({
59004        dataType: 'json',
59005        url: '/api/internal/gallery_images/temp_upload',
59006        autoUpload: true,
59007        dropZone: $('#gallery-dropzone'),
59008        paramName: 'files[]',
59009        add: (function(_this) {
59010          return function(_, data) {
59011            return $.each(data.files, function(index, file) {
59012              var upload;
59013              upload = {
59014                id: guid(),
59015                file_name: file.name
59016              };
59017              data.context = $(JST["file_upload/progress"](upload)).appendTo(_this.$filesElement);
59018              _this.uploads[upload.id] = upload;
59019              return upload.jqXHR = data.submit();
59020            });
59021          };
59022        })(this),
59023        progress: (function(_this) {
59024          return function(_, data) {
59025            return $.each(data.files, function(index, file) {
59026              var progress, progressBar, progressText, upload;
59027              progress = parseInt(data.loaded / data.total * 100, 10);
59028              progressText = progress === 100 ? 'processing' : progress + " %";
59029              upload = _this.uploadByFileName(file.name);
59030              if (progress > 95) {
59031                upload.jqXHR.forbidCanceling = true;
59032              }
59033              progressBar = _this.$filesElement.find(".file-upload-progress[data-id=" + upload.id + "]");
59034              progressBar.find('.progress-bar__progress').css('width', progress + "%");
59035              return progressBar.find(".progress-bar__info .progress-bar__percentage").html(progressText);
59036            });
59037          };
59038        })(this),
59039        done: (function(_this) {
59040          return function(_, data) {
59041            if (_this.$el.hasClass('is-hidden')) {
59042              _this.$el.hide().removeClass('is-hidden').fadeIn();
59043            }
59044            $.each(data.result, function(index, file) {
59045              var id_field, progressBar, upload;
59046              if (file.thumb_image) {
59047                upload = {
59048                  gallery_image_id: file.gallery_image_id,
59049                  thumb_image: file.thumb_image
59050                };
59051                _this.$imagesContainer.append(JST["galleries/uploaded_image"](upload));
59052                id_field = $('<input type="hidden" value="' + file.gallery_image_id + '" name="gallery_image_ids[]" />');
59053                data.context.append(id_field);
59054                progressBar = _this.$filesElement.find(".file-upload-progress[data-id=" + (data.context.data('id')) + "]");
59055                return progressBar.remove();
59056              } else if (file.error) {
59057                progressBar = _this.$filesElement.find(".file-upload-progress[data-id=" + (data.context.data('id')) + "]");
59058                return progressBar.remove();
59059              }
59060            });
59061            _this.$imagesContainer.sortable();
59062            return _this.checkImages();
59063          };
59064        })(this)
59065      });
59066    };
59067
59068    GalleriesNew.prototype.uploadByFileName = function(filename) {
59069      return _.detect(this.uploads, function(obj, index) {
59070        return (obj != null ? obj.file_name : void 0) === filename;
59071      });
59072    };
59073
59074    GalleriesNew.prototype.cancelUpload = function(event) {
59075      var progressBar, uploadId;
59076      mute(event);
59077      progressBar = $(event.target).parents('.file-upload-progress');
59078      uploadId = progressBar.data('id');
59079      if (!this.uploads[uploadId] || this.uploads[uploadId].jqXHR.forbidCanceling) {
59080        return;
59081      }
59082      this.uploads[uploadId].jqXHR.abort();
59083      progressBar.remove();
59084      return this.uploads[uploadId] = null;
59085    };
59086
59087    GalleriesNew.prototype.initTags = function() {
59088      return $('#gallery_tags').tagsInput({
59089        delimiter: ' ',
59090        width: '100%',
59091        height: 'auto',
59092        onChange: function() {
59093          $('#tags-count').val($('.tagsinput .tag').length);
59094          $('#tags-count').valid();
59095          return $('#gallery_tags').valid();
59096        }
59097      });
59098    };
59099
59100    GalleriesNew.prototype.onSubmitGalleryForm = function() {
59101      return this.$form.find('button[type="submit"]').attr('disabled', true);
59102    };
59103
59104    GalleriesNew.prototype.onGalleryPublished = function(e, data, status, xhr) {
59105      var categoryInput, currentCategory, currentTags, tagsInput;
59106      categoryInput = $('#gallery_category')[0];
59107      currentCategory = $(categoryInput).find(':selected').text();
59108      tagsInput = $('#gallery_tags')[0];
59109      currentTags = tagsInput.value.replaceAll(' ', '_');
59110      window.customEventTracker({
59111        name: 'upload_content',
59112        userId: App.config.userId || '',
59113        eventData: {
59114          content_type: 'gallery',
59115          content_category: currentCategory,
59116          content_tags: currentTags,
59117          file_format: '',
59118          content_subcategory: '',
59119          model_price: 0.00
59120        }
59121      });
59122      return window.location = '/profile/gallery';
59123    };
59124
59125    GalleriesNew.prototype.onGalleryError = function(xhr, status, error) {
59126      var errors;
59127      errors = xhr.responseJSON.errors;
59128      App.events.publish('app:notification:flash:error', this.parseErrors(errors));
59129      return $('#gallery-form').find('button[type="submit"]').attr('disabled', false);
59130    };
59131
59132    GalleriesNew.prototype.parseErrors = function(errors) {
59133      var attribute, error, errorMessages, parts;
59134      errorMessages = [];
59135      for (error in errors) {
59136        parts = errors[error].source.pointer.split('/');
59137        attribute = parts[parts.length - 1];
59138        errorMessages.push(attribute.capitalizeFirst + " " + errors[error].detail);
59139      }
59140      return errorMessages.join('.');
59141    };
59142
59143    return GalleriesNew;
59144
59145  })(App.Core.Page);
59146
59147}).call(this);
59148(function() {
59149  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
59150    hasProp = {}.hasOwnProperty;
59151
59152  App.Pages.Profile.GalleriesEdit = (function(superClass) {
59153    extend(GalleriesEdit, superClass);
59154
59155    function GalleriesEdit() {
59156      return GalleriesEdit.__super__.constructor.apply(this, arguments);
59157    }
59158
59159    return GalleriesEdit;
59160
59161  })(App.Pages.Profile.GalleriesNew);
59162
59163}).call(this);
59164/* =========================================================
59165 * bootstrap-datepicker.js 
59166 * http://www.eyecon.ro/bootstrap-datepicker
59167 * =========================================================
59168 * Copyright 2012 Stefan Petre
59169 *
59170 * Licensed under the Apache License, Version 2.0 (the "License");
59171 * you may not use this file except in compliance with the License.
59172 * You may obtain a copy of the License at
59173 *
59174 * http://www.apache.org/licenses/LICENSE-2.0
59175 *
59176 * Unless required by applicable law or agreed to in writing, software
59177 * distributed under the License is distributed on an "AS IS" BASIS,
59178 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
59179 * See the License for the specific language governing permissions and
59180 * limitations under the License.
59181 * ========================================================= */
59182
59183 
59184!function( $ ) {
59185	
59186	// Picker object
59187	
59188	var Datepicker = function(element, options){
59189		this.element = $(element);
59190		this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
59191		this.picker = $(DPGlobal.template)
59192							.appendTo('body')
59193							.on({
59194								click: $.proxy(this.click, this)//,
59195								//mousedown: $.proxy(this.mousedown, this)
59196							});
59197		this.isInput = this.element.is('input');
59198		this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
59199		
59200		if (this.isInput) {
59201			this.element.on({
59202				focus: $.proxy(this.show, this),
59203				//blur: $.proxy(this.hide, this),
59204				keyup: $.proxy(this.update, this)
59205			});
59206		} else {
59207			if (this.component){
59208				this.component.on('click', $.proxy(this.show, this));
59209			} else {
59210				this.element.on('click', $.proxy(this.show, this));
59211			}
59212		}
59213	
59214		this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
59215		if (typeof this.minViewMode === 'string') {
59216			switch (this.minViewMode) {
59217				case 'months':
59218					this.minViewMode = 1;
59219					break;
59220				case 'years':
59221					this.minViewMode = 2;
59222					break;
59223				default:
59224					this.minViewMode = 0;
59225					break;
59226			}
59227		}
59228		this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
59229		if (typeof this.viewMode === 'string') {
59230			switch (this.viewMode) {
59231				case 'months':
59232					this.viewMode = 1;
59233					break;
59234				case 'years':
59235					this.viewMode = 2;
59236					break;
59237				default:
59238					this.viewMode = 0;
59239					break;
59240			}
59241		}
59242		this.startViewMode = this.viewMode;
59243		this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
59244		this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
59245		this.onRender = options.onRender;
59246		this.fillDow();
59247		this.fillMonths();
59248		this.update();
59249		this.showMode();
59250	};
59251	
59252	Datepicker.prototype = {
59253		constructor: Datepicker,
59254		
59255		show: function(e) {
59256			this.picker.show();
59257			this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
59258			this.place();
59259			$(window).on('resize', $.proxy(this.place, this));
59260			if (e ) {
59261				e.stopPropagation();
59262				e.preventDefault();
59263			}
59264			if (!this.isInput) {
59265			}
59266			var that = this;
59267			$(document).on('mousedown', function(ev){
59268				if ($(ev.target).closest('.datepicker').length == 0) {
59269					that.hide();
59270				}
59271			});
59272			this.element.trigger({
59273				type: 'show',
59274				date: this.date
59275			});
59276		},
59277		
59278		hide: function(){
59279			this.picker.hide();
59280			$(window).off('resize', this.place);
59281			this.viewMode = this.startViewMode;
59282			this.showMode();
59283			if (!this.isInput) {
59284				$(document).off('mousedown', this.hide);
59285			}
59286			//this.set();
59287			this.element.trigger({
59288				type: 'hide',
59289				date: this.date
59290			});
59291		},
59292		
59293		set: function() {
59294			var formated = DPGlobal.formatDate(this.date, this.format);
59295			if (!this.isInput) {
59296				if (this.component){
59297					this.element.find('input').prop('value', formated);
59298				}
59299				this.element.data('date', formated);
59300			} else {
59301				this.element.prop('value', formated);
59302			}
59303		},
59304		
59305		setValue: function(newDate) {
59306			if (typeof newDate === 'string') {
59307				this.date = DPGlobal.parseDate(newDate, this.format);
59308			} else {
59309				this.date = new Date(newDate);
59310			}
59311			this.set();
59312			this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
59313			this.fill();
59314		},
59315		
59316		place: function(){
59317			var offset = this.component ? this.component.offset() : this.element.offset();
59318			this.picker.css({
59319				top: offset.top + this.height,
59320				left: offset.left
59321			});
59322		},
59323		
59324		update: function(newDate){
59325			this.date = DPGlobal.parseDate(
59326				typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
59327				this.format
59328			);
59329			this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
59330			this.fill();
59331		},
59332		
59333		fillDow: function(){
59334			var dowCnt = this.weekStart;
59335			var html = '<tr>';
59336			while (dowCnt < this.weekStart + 7) {
59337				html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
59338			}
59339			html += '</tr>';
59340			this.picker.find('.datepicker-days thead').append(html);
59341		},
59342		
59343		fillMonths: function(){
59344			var html = '';
59345			var i = 0
59346			while (i < 12) {
59347				html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
59348			}
59349			this.picker.find('.datepicker-months td').append(html);
59350		},
59351		
59352		fill: function() {
59353			var d = new Date(this.viewDate),
59354				year = d.getFullYear(),
59355				month = d.getMonth(),
59356				currentDate = this.date.valueOf();
59357			this.picker.find('.datepicker-days th:eq(1)')
59358						.text(DPGlobal.dates.months[month]+' '+year);
59359			var prevMonth = new Date(year, month-1, 28,0,0,0,0),
59360				day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
59361			prevMonth.setDate(day);
59362			prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
59363			var nextMonth = new Date(prevMonth);
59364			nextMonth.setDate(nextMonth.getDate() + 42);
59365			nextMonth = nextMonth.valueOf();
59366			var html = [];
59367			var clsName,
59368				prevY,
59369				prevM;
59370			while(prevMonth.valueOf() < nextMonth) {
59371				if (prevMonth.getDay() === this.weekStart) {
59372					html.push('<tr>');
59373				}
59374				clsName = this.onRender(prevMonth);
59375				prevY = prevMonth.getFullYear();
59376				prevM = prevMonth.getMonth();
59377				if ((prevM < month &&  prevY === year) ||  prevY < year) {
59378					clsName += ' old';
59379				} else if ((prevM > month && prevY === year) || prevY > year) {
59380					clsName += ' new';
59381				}
59382				if (prevMonth.valueOf() === currentDate) {
59383					clsName += ' active';
59384				}
59385				html.push('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>');
59386				if (prevMonth.getDay() === this.weekEnd) {
59387					html.push('</tr>');
59388				}
59389				prevMonth.setDate(prevMonth.getDate()+1);
59390			}
59391			this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
59392			var currentYear = this.date.getFullYear();
59393			
59394			var months = this.picker.find('.datepicker-months')
59395						.find('th:eq(1)')
59396							.text(year)
59397							.end()
59398						.find('span').removeClass('active');
59399			if (currentYear === year) {
59400				months.eq(this.date.getMonth()).addClass('active');
59401			}
59402			
59403			html = '';
59404			year = parseInt(year/10, 10) * 10;
59405			var yearCont = this.picker.find('.datepicker-years')
59406								.find('th:eq(1)')
59407									.text(year + '-' + (year + 9))
59408									.end()
59409								.find('td');
59410			year -= 1;
59411			for (var i = -1; i < 11; i++) {
59412				html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
59413				year += 1;
59414			}
59415			yearCont.html(html);
59416		},
59417		
59418		click: function(e) {
59419			e.stopPropagation();
59420			e.preventDefault();
59421			var target = $(e.target).closest('span, td, th');
59422			if (target.length === 1) {
59423				switch(target[0].nodeName.toLowerCase()) {
59424					case 'th':
59425						switch(target[0].className) {
59426							case 'switch':
59427								this.showMode(1);
59428								break;
59429							case 'prev':
59430							case 'next':
59431								this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
59432									this.viewDate,
59433									this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) + 
59434									DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
59435								);
59436								this.fill();
59437								this.set();
59438								break;
59439						}
59440						break;
59441					case 'span':
59442						if (target.is('.month')) {
59443							var month = target.parent().find('span').index(target);
59444							this.viewDate.setMonth(month);
59445						} else {
59446							var year = parseInt(target.text(), 10)||0;
59447							this.viewDate.setFullYear(year);
59448						}
59449						if (this.viewMode !== 0) {
59450							this.date = new Date(this.viewDate);
59451							this.element.trigger({
59452								type: 'changeDate',
59453								date: this.date,
59454								viewMode: DPGlobal.modes[this.viewMode].clsName
59455							});
59456						}
59457						this.showMode(-1);
59458						this.fill();
59459						this.set();
59460						break;
59461					case 'td':
59462						if (target.is('.day') && !target.is('.disabled')){
59463							var day = parseInt(target.text(), 10)||1;
59464							var month = this.viewDate.getMonth();
59465							if (target.is('.old')) {
59466								month -= 1;
59467							} else if (target.is('.new')) {
59468								month += 1;
59469							}
59470							var year = this.viewDate.getFullYear();
59471							this.date = new Date(year, month, day,0,0,0,0);
59472							this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
59473							this.fill();
59474							this.set();
59475							this.element.trigger({
59476								type: 'changeDate',
59477								date: this.date,
59478								viewMode: DPGlobal.modes[this.viewMode].clsName
59479							});
59480						}
59481						break;
59482				}
59483			}
59484		},
59485		
59486		mousedown: function(e){
59487			e.stopPropagation();
59488			e.preventDefault();
59489		},
59490		
59491		showMode: function(dir) {
59492			if (dir) {
59493				this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
59494			}
59495			this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
59496		}
59497	};
59498	
59499	$.fn.datepicker = function ( option, val ) {
59500		return this.each(function () {
59501			var $this = $(this),
59502				data = $this.data('datepicker'),
59503				options = typeof option === 'object' && option;
59504			if (!data) {
59505				$this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
59506			}
59507			if (typeof option === 'string') data[option](val);
59508		});
59509	};
59510
59511	$.fn.datepicker.defaults = {
59512		onRender: function(date) {
59513			return '';
59514		}
59515	};
59516	$.fn.datepicker.Constructor = Datepicker;
59517	
59518	var DPGlobal = {
59519		modes: [
59520			{
59521				clsName: 'days',
59522				navFnc: 'Month',
59523				navStep: 1
59524			},
59525			{
59526				clsName: 'months',
59527				navFnc: 'FullYear',
59528				navStep: 1
59529			},
59530			{
59531				clsName: 'years',
59532				navFnc: 'FullYear',
59533				navStep: 10
59534		}],
59535		dates:{
59536			days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
59537			daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
59538			daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
59539			months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
59539November", "December"],
59540			monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
59541		},
59542		isLeapYear: function (year) {
59543			return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
59544		},
59545		getDaysInMonth: function (year, month) {
59546			return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
59547		},
59548		parseFormat: function(format){
59549			var separator = format.match(/[.\/\-\s].*?/),
59550				parts = format.split(/\W+/);
59551			if (!separator || !parts || parts.length === 0){
59552				throw new Error("Invalid date format.");
59553			}
59554			return {separator: separator, parts: parts};
59555		},
59556		parseDate: function(date, format) {
59557			var parts = date.split(format.separator),
59558				date = new Date(),
59559				val;
59560			date.setHours(0);
59561			date.setMinutes(0);
59562			date.setSeconds(0);
59563			date.setMilliseconds(0);
59564			if (parts.length === format.parts.length) {
59565				var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
59566				for (var i=0, cnt = format.parts.length; i < cnt; i++) {
59567					val = parseInt(parts[i], 10)||1;
59568					switch(format.parts[i]) {
59569						case 'dd':
59570						case 'd':
59571							day = val;
59572							date.setDate(val);
59573							break;
59574						case 'mm':
59575						case 'm':
59576							month = val - 1;
59577							date.setMonth(val - 1);
59578							break;
59579						case 'yy':
59580							year = 2000 + val;
59581							date.setFullYear(2000 + val);
59582							break;
59583						case 'yyyy':
59584							year = val;
59585							date.setFullYear(val);
59586							break;
59587					}
59588				}
59589				date = new Date(year, month, day, 0 ,0 ,0);
59590			}
59591			return date;
59592		},
59593		formatDate: function(date, format){
59594			var val = {
59595				d: date.getDate(),
59596				m: date.getMonth() + 1,
59597				yy: date.getFullYear().toString().substring(2),
59598				yyyy: date.getFullYear()
59599			};
59600			val.dd = (val.d < 10 ? '0' : '') + val.d;
59601			val.mm = (val.m < 10 ? '0' : '') + val.m;
59602			var date = [];
59603			for (var i=0, cnt = format.parts.length; i < cnt; i++) {
59604				date.push(val[format.parts[i]]);
59605			}
59606			return date.join(format.separator);
59607		},
59608		headTemplate: '<thead>'+
59609							'<tr>'+
59610								'<th class="prev">&lsaquo;</th>'+
59611								'<th colspan="5" class="switch"></th>'+
59612								'<th class="next">&rsaquo;</th>'+
59613							'</tr>'+
59614						'</thead>',
59615		contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
59616	};
59617	DPGlobal.template = '<div class="datepicker dropdown-menu">'+
59618							'<div class="datepicker-days">'+
59619								'<table class=" table-condensed">'+
59620									DPGlobal.headTemplate+
59621									'<tbody></tbody>'+
59622								'</table>'+
59623							'</div>'+
59624							'<div class="datepicker-months">'+
59625								'<table class="table-condensed">'+
59626									DPGlobal.headTemplate+
59627									DPGlobal.contTemplate+
59628								'</table>'+
59629							'</div>'+
59630							'<div class="datepicker-years">'+
59631								'<table class="table-condensed">'+
59632									DPGlobal.headTemplate+
59633									DPGlobal.contTemplate+
59634								'</table>'+
59635							'</div>'+
59636						'</div>';
59637
59638}( window.jQuery );
59639(function() {
59640  var base,
59641    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
59642
59643  if ((base = App.Pages).Profile == null) {
59644    base.Profile = {};
59645  }
59646
59647  App.Pages.Profile.HyperwalletAccountsNew = (function() {
59648    function HyperwalletAccountsNew() {
59649      this.onCountrySelect = bind(this.onCountrySelect, this);
59650      this.onTabClick = bind(this.onTabClick, this);
59651    }
59652
59653    HyperwalletAccountsNew.prototype.initialize = function() {
59654      this.initTabs();
59655      this.activateFormData(this.currentType());
59656      this.initCountrySelection();
59657      this.initDatePicker();
59658      return this.initAttachmentsUploader(this.currentType());
59659    };
59660
59661    HyperwalletAccountsNew.prototype.initTabs = function() {
59662      var defaultTab;
59663      defaultTab = $(".payment-tabs a[data-type=" + (this.currentType()) + "]").parents('li');
59664      $('.payment-tabs').easytabs({
59665        animate: true,
59666        panelActiveClass: 'is-active',
59667        tabActiveClass: 'is-active',
59668        updateHash: false,
59669        defaultTab: defaultTab
59670      });
59671      return $('.payment-tabs .tabs--sealed a').on('click', this.onTabClick);
59672    };
59673
59674    HyperwalletAccountsNew.prototype.onTabClick = function(e) {
59675      var $el;
59676      $el = $(e.currentTarget);
59677      this.activateFormData($el.data('type'));
59678      return this.onCountrySelect();
59679    };
59680
59681    HyperwalletAccountsNew.prototype.activateFormData = function(type) {
59682      var $form;
59683      $form = $('.hyperwallet-account-form');
59684      $('.js-payment-type-field').val(type);
59685      $form.find('input, select').prop('required', false);
59686      $form.find("#payment_" + type).find('input, select').prop('required', true);
59687      return $form.find('.js-ignore-required').prop('required', false);
59688    };
59689
59690    HyperwalletAccountsNew.prototype.currentType = function() {
59691      return $('.js-payment-type-field').val();
59692    };
59693
59694    HyperwalletAccountsNew.prototype.initCountrySelection = function() {
59695      this.onCountrySelect();
59696      $('select[name="company_country"]').on('change', this.onCountrySelect);
59697      return $('select[name="country"]').on('change', this.onCountrySelect);
59698    };
59699
59700    HyperwalletAccountsNew.prototype.initDatePicker = function() {
59701      var $datePicker;
59702      $datePicker = $(".datepicker-input").datepicker({
59703        format: "yyyy-mm-dd"
59704      });
59705      return $datePicker.datepicker("hide");
59706    };
59707
59708    HyperwalletAccountsNew.prototype.onCountrySelect = function() {
59709      var $countrySelectField, $hyperwalletFields, $payoneerFields;
59710      if (this.currentType() === 'business') {
59711        $countrySelectField = $('select[name="company_country"]');
59712      } else {
59713        $countrySelectField = $('select[name="country"]');
59714      }
59715      $hyperwalletFields = $('.hyperwallet-fields');
59716      $payoneerFields = $('.payoneer-fields');
59717      if (['iq', 'af', 'tr'].includes($countrySelectField.val()) || $('.js-force-payonner
59717-agreement-field').val() === 'true') {
59718        $hyperwalletFields.addClass('is-hidden');
59719        $payoneerFields.removeClass('is-hidden');
59720        $payoneerFields.find("input").prop('required', true);
59721        $payoneerFields.find(".js-attachments-upload").removeAttr('required');
59722        return $hyperwalletFields.find("input").prop('required', false);
59723      } else {
59724        $hyperwalletFields.removeClass('is-hidden');
59725        $payoneerFields.addClass('is-hidden');
59726        $payoneerFields.find("input").prop('required', false);
59727        return $hyperwalletFields.find("input").prop('required', true);
59728      }
59729    };
59730
59731    HyperwalletAccountsNew.prototype.initAttachmentsUploader = function(currentType) {
59732      var $paymentInput;
59733      $paymentInput = $(".payoneer-fields").find('#fileupload');
59734      return new App.Modules.FileUpload('/profile/hyperwallet-account/upload_attachment', $paymentInput);
59735    };
59736
59737    return HyperwalletAccountsNew;
59738
59739  })();
59740
59741}).call(this);
59742(function() {
59743  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
59744    hasProp = {}.hasOwnProperty;
59745
59746  App.Pages.Profile.HyperwalletAccountsCreate = (function(superClass) {
59747    extend(HyperwalletAccountsCreate, superClass);
59748
59749    function HyperwalletAccountsCreate() {
59750      return HyperwalletAccountsCreate.__super__.constructor.apply(this, arguments);
59751    }
59752
59753    return HyperwalletAccountsCreate;
59754
59755  })(App.Pages.Profile.HyperwalletAccountsNew);
59756
59757}).call(this);
59758(function() {
59759  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
59760    hasProp = {}.hasOwnProperty;
59761
59762  App.Pages.Profile.HyperwalletAccountsEdit = (function(superClass) {
59763    extend(HyperwalletAccountsEdit, superClass);
59764
59765    function HyperwalletAccountsEdit() {
59766      return HyperwalletAccountsEdit.__super__.constructor.apply(this, arguments);
59767    }
59768
59769    return HyperwalletAccountsEdit;
59770
59771  })(App.Pages.Profile.HyperwalletAccountsNew);
59772
59773}).call(this);
59774(function() {
59775  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
59776
59777  App.Pages.Profile.HyperwalletAccountsIndex = (function() {
59778    function HyperwalletAccountsIndex() {
59779      this.closeActivationModal = bind(this.closeActivationModal, this);
59780    }
59781
59782    HyperwalletAccountsIndex.prototype.initialize = function() {
59783      var cookieName, cookieValue, haveCookie, that;
59784      cookieName = 'new_payment_agreement_created';
59785      cookieValue = Cookies.get(cookieName);
59786      haveCookie = cookieValue && ['individual', 'business'].includes(cookieValue);
59787      if (haveCookie) {
59788        this.showActivationModal();
59789        if (window.customEventTracker) {
59790          return this.trackPaymentAgreementCreation(cookieValue);
59791        } else {
59792          that = this;
59793          return App.events.subscribe('app:tracking:load', function(data) {
59794            return that.trackPaymentAgreementCreation(cookieValue);
59795          });
59796        }
59797      }
59798    };
59799
59800    HyperwalletAccountsIndex.prototype.showActivationModal = function() {
59801      var modalTemplate;
59802      modalTemplate = $('#hyperwallet-activation-modal').html();
59803      if (!modalTemplate) {
59804        return;
59805      }
59806      $('body').prepend(modalTemplate);
59807      $(document).on('click.hyperwalletActivationModal', '.js-close-modal', this.closeActivationModal);
59808      return $(document).on('click.hyperwalletActivationModal', '.overlay-modal--background', (function(_this) {
59809        return function(event) {
59810          if ($(event.target).hasClass('overlay-modal--background')) {
59811            return _this.closeActivationModal();
59812          }
59813        };
59814      })(this));
59815    };
59816
59817    HyperwalletAccountsIndex.prototype.closeActivationModal = function() {
59818      $('body').find('.overlay-modal--background').remove();
59819      return $(document).off('.hyperwalletActivationModal');
59820    };
59821
59822    HyperwalletAccountsIndex.prototype.trackPaymentAgreementCreation = function(value) {
59823      var userId;
59824      userId = App.config.userId || '';
59825      window.customEventTracker({
59826        name: 'create_payment_agreement',
59827        userId: userId,
59828        customAttributes: {
59829          user_type: value
59830        },
59831        eventData: {}
59832      });
59833      return Cookies.remove('new_payment_agreement_created');
59834    };
59835
59836    return HyperwalletAccountsIndex;
59837
59838  })();
59839
59840}).call(this);
59841(function() {
59842  var base,
59843    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
59844    hasProp = {}.hasOwnProperty;
59845
59846  if ((base = App.Pages).Profile == null) {
59847    base.Profile = {};
59848  }
59849
59850  App.Pages.Profile.ItemsIndex = (function(superClass) {
59851    extend(ItemsIndex, superClass);
59852
59853    function ItemsIndex() {
59854      return ItemsIndex.__super__.constructor.apply(this, arguments);
59855    }
59856
59857    ItemsIndex.prototype.widgets = ['downloads'];
59858
59859    ItemsIndex.prototype.initialize = function() {
59860      this.bindEvents();
59861      return this.initValidation();
59862    };
59863
59864    ItemsIndex.prototype.bindEvents = function() {
59865      $('.js-select-menu').on('change', this.onCheckboxClick);
59866      $('.js-select-all-in-page').on('click', this.onSelectAll);
59867      $('.js-select-filtered').on('click', this.onSelectFiltered);
59868      return $('.js-select-units').on('change', this.onUnitsChange);
59869    };
59870
59871    ItemsIndex.prototype.onCheckboxClick = function(e) {
59872      if (e.target.checked === true) {
59873        return $('.js-menu').show();
59874      } else {
59875        $('.js-menu').hide();
59876        return $('input:checkbox').each(function() {
59877          return $(this).prop('checked', false);
59878        });
59879      }
59880    };
59881
59882    ItemsIndex.prototype.onSelectAll = function(e) {
59883      e.preventDefault();
59884      $('.js-selected').val('this_page');
59885      $('input:checkbox').each(function() {
59886        return $(this).prop('checked', true);
59887      });
59888      return $('.js-menu').hide();
59889    };
59890
59891    ItemsIndex.prototype.onSelectFiltered = function(e) {
59892      e.preventDefault();
59893      $('.js-selected').val('filtered');
59894      $('input:checkbox').each(function() {
59895        return $(this).prop('checked', true);
59896      });
59897      return $('.js-menu').hide();
59898    };
59899
59900    ItemsIndex.prototype.onUnitsChange = function(e) {
59901      $('#price').rules('remove', 'max');
59902      $('#price').rules('remove', 'min');
59903      if (e.target.value === 'set') {
59904        $('.js-change-units').text('$');
59905        return $('#price').rules('add', {
59906          min: 2
59907        });
59908      } else {
59909        $('.js-change-units').text('%');
59910        if (e.target.value === 'decrease') {
59911          return $('#price').rules('add', {
59912            max: 99
59913          });
59914        }
59915      }
59916    };
59917
59918    ItemsIndex.prototype.validationSettings = function() {
59919      return {
59920        rules: {
59921          price: {
59922            required: true,
59923            min: 2
59924          }
59925        }
59926      };
59927    };
59928
59929    ItemsIndex.prototype.initValidation = function() {
59930      return $('#price-change-form').validate(this.validationSettings());
59931    };
59932
59933    return ItemsIndex;
59934
59935  })(App.Core.Page);
59936
59937}).call(this);
59938(function() {
59939  var base,
59940    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
59941    hasProp = {}.hasOwnProperty;
59942
59943  if ((base = App.Pages).Profile == null) {
59944    base.Profile = {};
59945  }
59946
59947  App.Pages.Profile.ItemsShow = (function(superClass) {
59948    extend(ItemsShow, superClass);
59949
59950    function ItemsShow() {
59951      return ItemsShow.__super__.constructor.apply(this, arguments);
59952    }
59953
59954    ItemsShow.prototype.widgets = ['comments', 'reportContent', 'likes', 'wishLists'];
59955
59956    ItemsShow.prototype.initialize = function() {
59957      $(document).on('click', '.js-publish-in-preview', (function(_this) {
59958        return function(event) {
59959          mute(event);
59960          return _this.tryPublishing(event);
59961        };
59962      })(this));
59963      return ItemsShow.__super__.initialize.call(this);
59964    };
59965
59966    ItemsShow.prototype.tryPublishing = function(event) {
59967      return $.ajax({
59968        type: 'POST',
59969        url: "/profile/items/" + ($(event.target).data('id')) + "/publish",
59970        data: {
59971          item: {
59972            created_at: Date.now()
59973          }
59974        },
59975        success: function(response, status) {
59976          if (response.error_message) {
59977            return App.events.publish('app:notification:flash:error', response.error_message);
59978          } else {
59979            return window.location = response.location;
59980          }
59981        }
59982      });
59983    };
59984
59985    return ItemsShow;
59986
59987  })(App.Pages.ProductsShow);
59988
59989}).call(this);
59990(function() {
59991  var base,
59992    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
59993    hasProp = {}.hasOwnProperty;
59994
59995  if ((base = App.Pages).Profile == null) {
59996    base.Profile = {};
59997  }
59998
59999  App.Pages.Profile.LikedModelsIndex = (function(superClass) {
60000    extend(LikedModelsIndex, superClass);
60001
60002    function LikedModelsIndex() {
60003      return LikedModelsIndex.__super__.constructor.apply(this, arguments);
60004    }
60005
60006    LikedModelsIndex.prototype.widgets = ['downloads'];
60007
60008    return LikedModelsIndex;
60009
60010  })(App.Core.Page);
60011
60012}).call(this);
60013(function() {
60014  var base;
60015
60016  if ((base = App.Pages).Profile == null) {
60017    base.Profile = {};
60018  }
60019
60020  App.Pages.Profile.PasswordRequestPassword = (function() {
60021    function PasswordRequestPassword() {}
60022
60023    PasswordRequestPassword.prototype.initialize = function() {
60024      this.$form = $('.js-complete-registration-form');
60025      return this.initValidation();
60026    };
60027
60028    PasswordRequestPassword.prototype.validationSettings = function() {
60029      return {
60030        rules: {
60031          'email[]': {
60032            required: true,
60033            email: true
60034          },
60035          password: {
60036            required: true,
60037            minlength: 8,
60038            validatePassword: true
60039          }
60040        }
60041      };
60042    };
60043
60044    PasswordRequestPassword.prototype.initValidation = function() {
60045      return this.$form.validate(this.validationSettings());
60046    };
60047
60048    return PasswordRequestPassword;
60049
60050  })();
60051
60052}).call(this);
60053(function() {
60054  var base,
60055    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
60056    hasProp = {}.hasOwnProperty;
60057
60058  if ((base = App.Pages).Profile == null) {
60059    base.Profile = {};
60060  }
60061
60062  App.Pages.Profile.PurchasesIndex = (function(superClass) {
60063    extend(PurchasesIndex, superClass);
60064
60065    function PurchasesIndex() {
60066      return PurchasesIndex.__super__.constructor.apply(this, arguments);
60067    }
60068
60069    PurchasesIndex.prototype.widgets = ['downloads', 'loyaltyLevelProgressInfoTooltip'];
60070
60071    return PurchasesIndex;
60072
60073  })(App.Core.Page);
60074
60075}).call(this);
60076(function() {
60077  var base;
60078
60079  if ((base = App.Pages).Profile == null) {
60080    base.Profile = {};
60081  }
60082
60083  App.Pages.Profile.ReferralsIndex = (function() {
60084    function ReferralsIndex() {}
60085
60086    ReferralsIndex.prototype.initialize = function() {
60087      return this.initFacebookShareButton();
60088    };
60089
60090    ReferralsIndex.prototype.initFacebookShareButton = function() {
60091      var $el;
60092      $el = $('.js-facebook-share');
60093      return $el.click(function(e) {
60094        e.preventDefault();
60095        e.stopPropagation();
60096        FB.init({
60097          appId: App.config.facebookAppId
60098        });
60099        return FB.ui({
60100          method: 'share',
60101          href: $el.data('url')
60102        });
60103      });
60104    };
60105
60106    return ReferralsIndex;
60107
60108  })();
60109
60110}).call(this);
60111(function() {
60112  var base;
60113
60114  if ((base = App.Pages).Profile == null) {
60115    base.Profile = {};
60116  }
60117
60118  App.Pages.Profile.SettingsShow = (function() {
60119    function SettingsShow() {}
60120
60121    SettingsShow.prototype.initialize = function() {
60122      this.$mainForm = $('#tab-settings-main').find('.edit_user');
60123      this.$passwordForm = $('#tab-settings-password').find('.edit_user');
60124      this.$usernameForm = $('#tab-settings-username').find('.edit_user');
60125      this.$infoForm = $('#tab-settings-info').find('form');
60126      this.$notificationsForm = $('#tab-settings-notifications').find('.edit_user');
60127      this.initTabs();
60128      this.initValidation();
60129      this.initTracking();
60130      this.initAvatarUploader();
60131      return this.bindEvents();
60132    };
60133
60134    SettingsShow.prototype.bindEvents = function() {
60135      var countryField, ref, ref1, ref2;
60136      $('.js-email-field').on('input', (function(_this) {
60137        return function() {
60138          return _this.onEmailChange(_this.$mainForm);
60139        };
60140      })(this));
60141      $('.js-sale-off').on('change', this.onSaleOffChange);
60142      $('.js-engagement').on('change', this.onBuyerEngagementChange);
60143      $('.js-segment-radio').on('change', this.onUserSegmentChange);
60144      $('.js-accept-price-offers').on('change', this.onAcceptPriceOffersChange);
60145      countryField = $('#info_country')[0];
60146      if (countryField == null) {
60147        return;
60148      }
60149      if (((ref = window.google) != null ? (ref1 = ref.maps) != null ? (ref2 = ref1.places) != null ? ref2.Autocomplete : void 0 : void 0 : void 0) == null) {
60150        return;
60151      }
60152      return new window.google.maps.places.Autocomplete(countryField, {});
60153    };
60154
60155    SettingsShow.prototype.initTabs = function() {
60156      return $('.js-tabs').easytabs({
60157        animate: false,
60158        panelActiveClass: 'is-active',
60159        tabActiveClass: 'is-active'
60160      });
60161    };
60162
60163    SettingsShow.prototype.validationSettings = function() {
60164      return {
60165        rules: {
60166          'user[email]': {
60167            required: true,
60168            email: true
60169          },
60170          'user[birthday]': {
60171            validDateFormat: true
60172          },
60173          'user[current_password]': {
60174            required: true
60175          },
60176          'user[password]': {
60177            required: true,
60178            minlength: 8,
60179            validatePassword: true
60180          },
60181          'user[password_confirmation]': {
60182            required: true,
60183            equalTo: '#user_password'
60184          },
60185          'user[new_username]': {
60186            required: true,
60187            validateUsername: true
60188          },
60189          'user[new_username_confirmation]': {
60190            required: true,
60191            validateUsername: true,
60192            equalTo: '#user_new_username'
60193          }
60194        },
60195        errorPlacement: function(error, element) {
60196          if (element.attr('name') === 'user[birthday]') {
60197            return error.appendTo($('.js-birthday-field-error'));
60198          } else {
60199            return error.insertAfter(element);
60200          }
60201        }
60202      };
60203    };
60204
60205    SettingsShow.prototype.getNotificationSettings = function() {
60206      var selctedSettingsRows, selectedSettings;
60207      selectedSettings = [];
60208      selctedSettingsRows = $('.checkbox:checked').closest('tr');
60209      selctedSettingsRows.each(function(index, row) {
60210        return selectedSettings.push($(row).find('td[data-tracking-label]').attr('data-tracking-label'));
60211      });
60212      return selectedSettings.join('_');
60213    };
60214
60215    SettingsShow.prototype.prepareTrackingAttributes = function() {
60216      var activeTab, settingsType;
60217      activeTab = $('.js-tabs .tabs').find('a.is-active');
60218      settingsType = activeTab.attr('data-tracking-label');
60219      return settingsType;
60220    };
60221
60222    SettingsShow.prototype.trackFormSubmit = function(notificationSettings) {
60223      var settingsType;
60224      if (notificationSettings == null) {
60225        notificationSettings = '';
60226      }
60227      settingsType = window.prepareSettingsTrackingAttributes();
60228      return window.customEventTracker({
60229        name: 'update_account_settings',
60230        eventData: {
60231          'settings_type': settingsType,
60232          'notification_settings_checked': notificationSettings
60233        }
60234      });
60235    };
60236
60237    SettingsShow.prototype.handleFormSubmit = function(e, form) {
60238      var isNotificationsForm, notificationSettings;
60239      if (form.valid()) {
60240        e.preventDefault();
60241        e.stopPropagation();
60242        isNotificationsForm = form === this.$notificationsForm;
60243        notificationSettings = '';
60244        if (isNotificationsForm) {
60245          notificationSettings = this.getNotificationSettings();
60246        }
60247        this.trackFormSubmit(notificationSettings);
60248        return $(e.currentTarget).unbind().submit();
60249      }
60250    };
60251
60252    SettingsShow.prototype.initTracking = function() {
60253      var forms, that;
60254      that = this;
60255      forms = [this.$mainForm, this.$usernameForm, this.$passwordForm, this.$infoForm, this.$notificationsForm];
60256      window.trackSettingsFormSubmit = this.trackFormSubmit;
60257      window.prepareSettingsTrackingAttributes = this.prepareTrackingAttributes;
60258      return forms.forEach(function(form) {
60259        return form.on('submit', function(e) {
60260          return that.handleFormSubmit(e, form);
60261        });
60262      });
60263    };
60264
60265    SettingsShow.prototype.initValidation = function() {
60266      this.$mainForm.validate(this.validationSettings());
60267      this.$passwordForm.validate(this.validationSettings());
60268      return this.$usernameForm.validate(this.validationSettings());
60269    };
60270
60271    SettingsShow.prototype.initAvatarUploader = function() {
60272      return $('.js-avatar-upload').fileupload({
60273        dataType: "json",
60274        url: "/api/internal/users/upload_avatar",
60275        autoUpload: true,
60276        add: function(e, data) {
60277          $("#upload-action").fadeOut().promise().done(function() {
60278            $("#upload-action").addClass("is-hidden");
60279            return $("#upload-progress").fadeIn().promise().done(function() {
60280              return $("#upload-progress").removeClass("is-hidden");
60281            });
60282          });
60283          return data.submit();
60284        },
60285        done: function(e, data) {
60286          $(".avatar img").fadeOut().promise().done(function() {
60287            return $(".avatar img").attr("src", data.jqXHR.responseJSON.url).fadeIn();
60288          });
60289          return $("#upload-progress").fadeOut().promise().done(function() {
60290            $("#upload-progress").addClass("is-hidden");
60291            return $("#upload-action").fadeIn().promise().done(function() {
60292              return $("#upload-action").removeClass("is-hidden");
60293            });
60294          });
60295        }
60296      });
60297    };
60298
60299    SettingsShow.prototype.onEmailChange = function(form) {
60300      form.find('.js-submit-button').addClass("is-hidden");
60301      return form.find('.js-tfa-trigger').removeClass("is-hidden");
60302    };
60303
60304    SettingsShow.prototype.onSaleOffChange = function(e) {
60305      if (!e.target.checked) {
60306        return $('.js-unchecked-sale-off').val(false);
60307      }
60308    };
60309
60310    SettingsShow.prototype.onBuyerEngagementChange = function(e) {
60311      if (!e.target.checked) {
60312        return $('.js-unchecked-buyer-engagement').val(false);
60313      }
60314    };
60315
60316    SettingsShow.prototype.onUserSegmentChange = function(e) {
60317      return $.ajax({
60318        url: '/api/internal/users/update-segment',
60319        type: 'PUT',
60320        data: {
60321          segment: $(e.currentTarget).val()
60322        }
60323      });
60324    };
60325
60326    SettingsShow.prototype.onAcceptPriceOffersChange = function(e) {
60327      if (!e.target.checked) {
60328        $('.js-accept-price-offers-to').val('');
60329        return $('.js-accept-price-offers-to').prop('disabled', true);
60330      } else {
60331        return $('.js-accept-price-offers-to').prop('disabled', false);
60332      }
60333    };
60334
60335    return SettingsShow;
60336
60337  })();
60338
60339}).call(this);
60340(function() {
60341  var base,
60342    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
60343    hasProp = {}.hasOwnProperty;
60344
60345  if ((base = App.Pages).Profile == null) {
60346    base.Profile = {};
60347  }
60348
60349  App.Pages.Profile.WishListsShow = (function(superClass) {
60350    extend(WishListsShow, superClass);
60351
60352    function WishListsShow() {
60353      return WishListsShow.__super__.constructor.apply(this, arguments);
60354    }
60355
60356    WishListsShow.prototype.initialize = function() {
60357      return this.bindEvents();
60358    };
60359
60360    WishListsShow.prototype.bindEvents = function() {
60361      var that;
60362      that = this;
60363      $('.js-remove-selected').click(this.removeSelected);
60364      $('.js-purchase-selected').click(this.purchaseSelected);
60365      $('.js-purchase-all').click(this.purchaseAll);
60366      $('.js-model-type-select').chosen().change(function(event, data) {
60367        return that.onSelect(event, data, 'model type');
60368      });
60369      $('.js-model-category-select').chosen().change(function(event, data) {
60370        return that.onSelect(event, data, 'model category');
60371      });
60372      return $('#wish-list-search-form form').submit(function(event) {
60373        event.preventDefault();
60374        that.onSearchSubmit();
60375        return this.submit();
60376      });
60377    };
60378
60379    WishListsShow.prototype.onSearchSubmit = function() {
60380      var inputValue, textInput;
60381      textInput = $('#keywords');
60382      inputValue = textInput.val();
60383      if (window.searchTracker && inputValue) {
60384        return window.searchTracker({
60385          searchTerm: inputValue,
60386          type: 'wish list'
60387        });
60388      }
60389    };
60390
60391    WishListsShow.prototype.removeSelected = function() {
60392      var selectedItemIds;
60393      selectedItemIds = [];
60394      $('.content-box.is-checked .js-check-box').each(function(index, el) {
60395        return selectedItemIds.push($(el).data('id'));
60396      });
60397      return $.ajax({
60398        url: "/api/internal/wish-lists/" + gon.wish_list_id + "/items/0",
60399        type: 'DELETE',
60400        data: {
60401          item_ids: selectedItemIds
60402        },
60403        success: function() {
60404          return window.location.reload();
60405        }
60406      });
60407    };
60408
60409    WishListsShow.prototype.purchaseSelected = function() {
60410      return $('.content-box.is-checked .js-order').click();
60411    };
60412
60413    WishListsShow.prototype.purchaseAll = function() {
60414      return $('.content-box .js-order').click();
60415    };
60416
60417    WishListsShow.prototype.onSelect = function(event, data, name) {
60418      var matchingOption, optionsElements, optionsValues, query, searchQuery, type, value;
60419      searchQuery = $('#keywords');
60420      value = data.selected;
60421      if (name === 'model category') {
60422        optionsElements = event.target.options || [];
60423        optionsValues = Array.from(optionsElements).map((function(_this) {
60424          return function(opt) {
60425            return {
60426              value: opt.value,
60427              label: opt.text
60428            };
60429          };
60430        })(this));
60431        matchingOption = optionsValues.find((function(_this) {
60432          return function(opt) {
60433            return opt.value === data.selected;
60434          };
60435        })(this));
60436        if (matchingOption) {
60437          value = matchingOption.label;
60438        }
60439      }
60440      if (window.filterTracker && data.selected) {
60441        query = searchQuery.val();
60442        type = 'wish list';
60443        window.removeCurrentSearchFilter({
60444          name: name
60445        });
60446        window.filterTracker({
60447          name: name,
60448          value: value,
60449          searchTerm: query,
60450          type: type
60451        });
60452        return window.searchTracker({
60453          searchTerm: query,
60454          type: type
60455        });
60456      }
60457    };
60458
60459    return WishListsShow;
60460
60461  })(App.Core.Page);
60462
60463}).call(this);
60464if (!RedactorPlugins) var RedactorPlugins = {};
60465
60466RedactorPlugins.fullscreen = {
60467	init: function()
60468	{
60469		this.fullscreen = false;
60470
60471		this.buttonAdd('fullscreen', 'Fullscreen', $.proxy(this.toggleFullscreen, this));
60472		this.buttonSetRight('fullscreen');
60473
60474		if (this.opts.fullscreen) this.toggleFullscreen();
60475	},
60476	toggleFullscreen: function()
60477	{
60478		var html;
60479
60480		if (!this.fullscreen)
60481		{
60482			this.buttonChangeIcon('fullscreen', 'normalscreen');
60483			this.buttonActive('fullscreen');
60484			this.fullscreen = true;
60485
60486			if (this.opts.toolbarExternal)
60487			{
60488				this.toolcss = {};
60489				this.boxcss = {};
60490				this.toolcss.width = this.$toolbar.css('width');
60491				this.toolcss.top = this.$toolbar.css('top');
60492				this.toolcss.position = this.$toolbar.css('position');
60493				this.boxcss.top = this.$box.css('top');
60494			}
60495
60496			this.fsheight = this.$editor.height();
60497
60498			if (this.opts.iframe) html = this.get();
60499
60500			this.tmpspan = $('<span></span>');
60501			this.$box.addClass('redactor_box_fullscreen').after(this.tmpspan);
60502
60503			$('body, html').css('overflow', 'hidden');
60504			$('body').prepend(this.$box);
60505
60506			if (this.opts.iframe) this.fullscreenIframe(html);
60507
60508			this.fullScreenResize();
60509			$(window).resize($.proxy(this.fullScreenResize, this));
60510			$(document).scrollTop(0, 0);
60511
60512			this.focus();
60513			this.observeStart();
60514
60515		}
60516		else
60517		{
60518			this.buttonRemoveIcon('fullscreen', 'normalscreen');
60519			this.buttonInactive('fullscreen');
60520			this.fullscreen = false;
60521
60522			$(window).off('resize', $.proxy(this.fullScreenResize, this));
60523			$('body, html').css('overflow', '');
60524
60525			this.$box.removeClass('redactor_box_fullscreen').css({ width: 'auto', height: 'auto' });
60526
60527			if (this.opts.iframe) html = this.$editor.html();
60528			this.tmpspan.after(this.$box).remove();
60529
60530			if (this.opts.iframe) this.fullscreenIframe(html);
60531			else this.sync();
60532
60533			var height = this.fsheight;
60534			if (this.opts.autoresize) height = 'auto';
60535
60536			if (this.opts.toolbarExternal)
60537			{
60538				this.$box.css('top', this.boxcss.top);
60539				this.$toolbar.css({
60540					'width': this.toolcss.width,
60541					'top': this.toolcss.top,
60542					'position': this.toolcss.position
60543				});
60544			}
60545
60546			if (!this.opts.iframe) this.$editor.css('height', height);
60547			else this.$frame.css('height', height);
60548
60549			this.$editor.css('height', height);
60550			this.focus();
60551			this.observeStart();
60552		}
60553	},
60554	fullscreenIframe: function(html)
60555	{
60556		this.$editor = this.$frame.contents().find('body').attr({
60557			'contenteditable': true,
60558			'dir': this.opts.direction
60559		});
60560
60561		// set document & window
60562		if (this.$editor[0])
60563		{
60564			this.document = this.$editor[0].ownerDocument;
60565			this.window = this.document.defaultView || window;
60566		}
60567
60568		// iframe css
60569		this.iframeAddCss();
60570
60571		if (this.opts.fullpage) this.setFullpageOnInit(html);
60572		else this.set(html);
60573
60574		if (this.opts.wym) this.$editor.addClass('redactor_editor_wym');
60575	},
60576	fullScreenResize: function()
60577	{
60578		if (!this.fullscreen) return false;
60579
60580		var toolbarHeight = this.$toolbar.height();
60581
60582		var pad = this.$editor.css('padding-top').replace('px', '');
60583		var height = $(window).height() - toolbarHeight;
60584		this.$box.width($(window).width() - 2).height(height + toolbarHeight);
60585
60586		if (this.opts.toolbarExternal)
60587		{
60588			this.$toolbar.css({
60589				'top': '0px',
60590				'position': 'absolute',
60591				'width': '100%'
60592			});
60593
60594			this.$box.css('top', toolbarHeight + 'px');
60595		}
60596
60597		if (!this.opts.iframe) this.$editor.height(height - (pad * 2));
60598		else
60599		{
60600			setTimeout($.proxy(function()
60601			{
60602				this.$frame.height(height);
60603
60604			}, this), 1);
60605		}
60606
60607		this.$editor.height(height);
60608	}
60609};
60610if (!RedactorPlugins) var RedactorPlugins = {};
60611
60612RedactorPlugins.fontsize = {
60613	init: function()
60614	{
60615		var fonts = [10, 11, 12, 14, 16, 18, 20, 24, 28, 30];
60616		var that = this;
60617		var dropdown = {};
60618
60619		$.each(fonts, function(i, s)
60620		{
60621			dropdown['s' + i] = { title: s + 'px', callback: function() { that.setFontsize(s); } };
60622		});
60623
60624		dropdown['remove'] = { title: 'Remove font size', callback: function() { that.resetFontsize(); } };
60625
60626		this.buttonAdd( 'fontsize', 'Change font size', false, dropdown);
60627	},
60628	setFontsize: function(size)
60629	{
60630		this.inlineSetStyle('font-size', size + 'px');
60631	},
60632	resetFontsize: function()
60633	{
60634		this.inlineRemoveStyle('font-size');
60635	}
60636};
60637if (!RedactorPlugins) var RedactorPlugins = {};
60638
60639RedactorPlugins.fontcolor = {
60640	init: function()
60641	{
60642		var colors = ['#ffffff', '#000000', '#eeece1', '#1f497d', '#4f81bd', '#c0504d', '#9bbb59', '#8064a2', '#4bacc6', '#f79646', '#ffff00', '#f2f2f2', '#7f7f7f', '#ddd9c3', '#c6d9f0', '#dbe5f1', '#f2dcdb', '#ebf1dd', '#e5e0ec', '#dbeef3', '#fdeada', '#fff2ca', '#d8d8d8', '#595959', '#c4bd97', '#8db3e2', '#b8cce4', '#e5b9b7', '#d7e3bc', '#ccc1d9', '#b7dde8', '#fbd5b5', '#ffe694', '#bfbfbf', '#3f3f3f', '#938953', '#548dd4', '#95b3d7', '#d99694', '#c3d69b', '#b2a2c7', '#b7dde8', '#fac08f', '#f2c314', '#a5a5a5', '#262626', '#494429', '#17365d', '#366092', '#953734', '#76923c', '#5f497a', '#92cddc', '#e36c09', '#c09100', '#7f7f7f', '#0c0c0c', '#1d1b10', '#0f243e', '#244061', '#632423', '#4f6128', '#3f3151', '#31859b', '#974806', '#7f6000'];
60643		var buttons = ['fontcolor', 'backcolor'];
60644
60645		this.buttonAddSeparator();
60646
60647		for (var i = 0; i < 2; i++)
60648		{
60649			var name = buttons[i];
60650
60651			var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_' + name + '" style="display: none; width: 210px;">');
60652
60653			this.pickerBuild($dropdown, name, colors);
60654			$(this.$toolbar).append($dropdown);
60655
60656			this.buttonAdd(name, this.opts.curLang[name], $.proxy(function(btnName, $button, btnObject, e)
60657			{
60658				this.dropdownShow(e, btnName);
60659
60660			}, this));
60661		}
60662	},
60663	pickerBuild: function($dropdown, name, colors)
60664	{
60665		var rule = 'color';
60666		if (name === 'backcolor') rule = 'background-color';
60667
60668		var _self = this;
60669		var onSwatch = function(e)
60670		{
60671			e.preventDefault();
60672
60673			var $this = $(this);
60674			_self.pickerSet($this.data('rule'), $this.attr('rel'));
60675
60676		}
60677
60678		var len = colors.length;
60679		for (var z = 0; z < len; z++)
60680		{
60681			var color = colors[z];
60682
60683			var $swatch = $('<a rel="' + color + '" data-rule="' + rule +'" href="#" style="float: left; font-size: 0; border: 2px solid #fff; padding: 0; margin: 0; width: 15px; height: 15px;"></a>');
60684			$swatch.css('background-color', color);
60685			$dropdown.append($swatch);
60686			$swatch.on('click', onSwatch);
60687		}
60688
60689		var $elNone = $('<a href="#" style="display: block; clear: both; padding: 4px 0; font-size: 11px; line-height: 1;"></a>')
60690		.html(this.opts.curLang.none)
60691		.on('click', function(e)
60692		{
60693			e.preventDefault();
60694			_self.pickerSet(rule, false);
60695		});
60696
60697		$dropdown.append($elNone);
60698	},
60699	pickerSet: function(rule, type)
60700	{
60701		this.bufferSet();
60702
60703		this.$editor.focus();
60704		this.inlineRemoveStyle(rule);
60705		if (type !== false) this.inlineSetStyle(rule, type);
60706		if (this.opts.air) this.$air.fadeOut(100);
60707		this.sync();
60708	}
60709};
60710if (!RedactorPlugins) var RedactorPlugins = {};
60711
60712RedactorPlugins.fontfamily = {
60713	init: function ()
60714	{
60715		var fonts = [ 'Arial', 'Helvetica', 'Georgia', 'Times New Roman', 'Monospace' ];
60716		var that = this;
60717		var dropdown = {};
60718
60719		$.each(fonts, function(i, s)
60720		{
60721			dropdown['s' + i] = { title: s, callback: function() { that.setFontfamily(s); }};
60722		});
60723
60724		dropdown['remove'] = { title: 'Remove font', callback: function() { that.resetFontfamily(); }};
60725
60726		this.buttonAdd('fontfamily', 'Change font family', false, dropdown);
60727	},
60728	setFontfamily: function (value)
60729	{
60730		this.inlineSetStyle('font-family', value);
60731	},
60732	resetFontfamily: function()
60733	{
60734		this.inlineRemoveStyle('font-family');
60735	}
60736};
60737(function() {
60738  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
60739    hasProp = {}.hasOwnProperty;
60740
60741  App.Pages.ScholarshipNew = (function(superClass) {
60742    extend(ScholarshipNew, superClass);
60743
60744    function ScholarshipNew() {
60745      return ScholarshipNew.__super__.constructor.apply(this, arguments);
60746    }
60747
60748    ScholarshipNew.prototype.initialize = function() {
60749      this.initTabs();
60750      this.$form = $('.scholarship-submission-form');
60751      if (this.$form.length > 0) {
60752        $('.js-scroll-to-form').on('click', this.scrollToSubmissionForm);
60753        this.attachRedactor($('.js-essay-field'));
60754        return this.initValidation();
60755      }
60756    };
60757
60758    ScholarshipNew.prototype.initTabs = function() {
60759      return $('.js-scholarship-easytabs').easytabs({
60760        animate: true,
60761        panelActiveClass: 'is-active',
60762        panelContext: $('.js-scholarship-sections'),
60763        tabActiveClass: 'is-active'
60764      });
60765    };
60766
60767    ScholarshipNew.prototype.scrollToSubmissionForm = function() {
60768      $('.js-scholarship-easytabs').easytabs('select', '#section-submission');
60769      return setTimeout(function() {
60770        return $('body').scrollTo('#section-submission');
60771      }, 500);
60772    };
60773
60774    ScholarshipNew.prototype.attachRedactor = function($element) {
60775      return $element.redactor({
60776        plugins: ['fullscreen', 'fontsize', 'fontcolor', 'fontfamily'],
60777        minHeight: 300,
60778        convertDivs: false,
60779        autoresize: false,
60780        buttons: ['html', '|', 'formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'table', '|', 'alignment']
60781      });
60782    };
60783
60784    ScholarshipNew.prototype.validationSettings = function() {
60785      return {
60786        rules: {
60787          'scholarship_application[full_name]': {
60788            required: true
60789          },
60790          'scholarship_application[birthday]': {
60791            required: true
60792          },
60793          'scholarship_application[country]': {
60794            required: true
60795          },
60796          'scholarship_application[city]': {
60797            required: true
60798          },
60799          'scholarship_application[university]': {
60800            required: true
60801          },
60802          'scholarship_application[email]': {
60803            required: true,
60804            email: true
60805          }
60806        }
60807      };
60808    };
60809
60810    ScholarshipNew.prototype.initValidation = function() {
60811      return this.$form.validate(this.validationSettings());
60812    };
60813
60814    return ScholarshipNew;
60815
60816  })(App.Core.Page);
60817
60818}).call(this);
60819(function() {
60820  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
60821    hasProp = {}.hasOwnProperty;
60822
60823  App.Pages.ScholarshipAnnual = (function(superClass) {
60824    extend(ScholarshipAnnual, superClass);
60825
60826    function ScholarshipAnnual() {
60827      return ScholarshipAnnual.__super__.constructor.apply(this, arguments);
60828    }
60829
60830    return ScholarshipAnnual;
60831
60832  })(App.Pages.ScholarshipNew);
60833
60834}).call(this);
60835(function() {
60836  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
60837    hasProp = {}.hasOwnProperty;
60838
60839  App.Pages.ScholarshipCreate = (function(superClass) {
60840    extend(ScholarshipCreate, superClass);
60841
60842    function ScholarshipCreate() {
60843      return ScholarshipCreate.__super__.constructor.apply(this, arguments);
60844    }
60845
60846    return ScholarshipCreate;
60847
60848  })(App.Pages.ScholarshipNew);
60849
60850}).call(this);
60851/*
60852Copyright 2012 Igor Vaynberg
60853
60854Version: @@ver@@ Timestamp: @@timestamp@@
60855
60856This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
60857General Public License version 2 (the "GPL License"). You may choose either license to govern your
60858use of this software only upon the condition that you accept all of the terms of either the Apache
60859License or the GPL License.
60860
60861You may obtain a copy of the Apache License and the GPL License at:
60862
60863    http://www.apache.org/licenses/LICENSE-2.0
60864    http://www.gnu.org/licenses/gpl-2.0.html
60865
60866Unless required by applicable law or agreed to in writing, softw
60866are distributed under the
60867Apache License or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
60868CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for
60869the specific language governing permissions and limitations under the Apache License and the GPL License.
60870*/
60871
60872 (function ($) {
60873 	if(typeof $.fn.each2 == "undefined"){
60874 		$.fn.extend({
60875 			/*
60876			* 4-10 times faster .each replacement
60877			* use it carefully, as it overrides jQuery context of element on each iteration
60878			*/
60879			each2 : function (c) {
60880				var j = $([0]), i = -1, l = this.length;
60881				while (
60882					++i < l
60883					&& (j.context = j[0] = this[i])
60884					&& c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
60885				);
60886				return this;
60887			}
60888 		});
60889 	}
60890})(jQuery);
60891
60892(function ($, undefined) {
60893    "use strict";
60894    /*global document, window, jQuery, console */
60895
60896    if (window.Select2 !== undefined) {
60897        return;
60898    }
60899
60900    var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
60901        lastMousePosition, $document;
60902
60903    KEY = {
60904        TAB: 9,
60905        ENTER: 13,
60906        ESC: 27,
60907        SPACE: 32,
60908        LEFT: 37,
60909        UP: 38,
60910        RIGHT: 39,
60911        DOWN: 40,
60912        SHIFT: 16,
60913        CTRL: 17,
60914        ALT: 18,
60915        PAGE_UP: 33,
60916        PAGE_DOWN: 34,
60917        HOME: 36,
60918        END: 35,
60919        BACKSPACE: 8,
60920        DELETE: 46,
60921        isArrow: function (k) {
60922            k = k.which ? k.which : k;
60923            switch (k) {
60924            case KEY.LEFT:
60925            case KEY.RIGHT:
60926            case KEY.UP:
60927            case KEY.DOWN:
60928                return true;
60929            }
60930            return false;
60931        },
60932        isControl: function (e) {
60933            var k = e.which;
60934            switch (k) {
60935            case KEY.SHIFT:
60936            case KEY.CTRL:
60937            case KEY.ALT:
60938                return true;
60939            }
60940
60941            if (e.metaKey) return true;
60942
60943            return false;
60944        },
60945        isFunctionKey: function (k) {
60946            k = k.which ? k.which : k;
60947            return k >= 112 && k <= 123;
60948        }
60949    };
60950
60951    $document = $(document);
60952
60953    nextUid=(function() { var counter=1; return function() { return counter++; }; }());
60954
60955    function indexOf(value, array) {
60956        var i = 0, l = array.length, v;
60957
60958        if (typeof value === "undefined") {
60959          return -1;
60960        }
60961
60962        if (value.constructor === String) {
60963            for (; i < l; i = i + 1) if (value.localeCompare(array[i]) === 0) return i;
60964        } else {
60965            for (; i < l; i = i + 1) {
60966                v = array[i];
60967                if (v.constructor === String) {
60968                    if (v.localeCompare(value) === 0) return i;
60969                } else {
60970                    if (v === value) return i;
60971                }
60972            }
60973        }
60974        return -1;
60975    }
60976
60977    /**
60978     * Compares equality of a and b taking into account that a and b may be strings, in which case localeCompare is used
60979     * @param a
60980     * @param b
60981     */
60982    function equal(a, b) {
60983        if (a === b) return true;
60984        if (a === undefined || b === undefined) return false;
60985        if (a === null || b === null) return false;
60986        if (a.constructor === String) return a.localeCompare(b) === 0;
60987        if (b.constructor === String) return b.localeCompare(a) === 0;
60988        return false;
60989    }
60990
60991    /**
60992     * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
60993     * strings
60994     * @param string
60995     * @param separator
60996     */
60997    function splitVal(string, separator) {
60998        var val, i, l;
60999        if (string === null || string.length < 1) return [];
61000        val = string.split(separator);
61001        for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
61002        return val;
61003    }
61004
61005    function getSideBorderPadding(element) {
61006        return element.outerWidth(false) - element.width();
61007    }
61008
61009    function installKeyUpChangeEvent(element) {
61010        var key="keyup-change-value";
61011        element.bind("keydown", function () {
61012            if ($.data(element, key) === undefined) {
61013                $.data(element, key, element.val());
61014            }
61015        });
61016        element.bind("keyup", function () {
61017            var val= $.data(element, key);
61018            if (val !== undefined && element.val() !== val) {
61019                $.removeData(element, key);
61020                element.trigger("keyup-change");
61021            }
61022        });
61023    }
61024
61025    $document.bind("mousemove", function (e) {
61026        lastMousePosition = {x: e.pageX, y: e.pageY};
61027    });
61028
61029    /**
61030     * filters mouse events so an event is fired only if the mouse moved.
61031     *
61032     * filters out mouse events that occur when mouse is stationary but
61033     * the elements under the pointer are scrolled.
61034     */
61035    function installFilteredMouseMove(element) {
61036	    element.bind("mousemove", function (e) {
61037            var lastpos = lastMousePosition;
61038            if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
61039                $(e.target).trigger("mousemove-filtered", e);
61040            }
61041        });
61042    }
61043
61044    /**
61045     * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
61046     * within the last quietMillis milliseconds.
61047     *
61048     * @param quietMillis number of milliseconds to wait before invoking fn
61049     * @param fn function to be debounced
61050     * @param ctx object to be used as this reference within fn
61051     * @return debounced version of fn
61052     */
61053    function debounce(quietMillis, fn, ctx) {
61054        ctx = ctx || undefined;
61055        var timeout;
61056        return function () {
61057            var args = arguments;
61058            window.clearTimeout(timeout);
61059            timeout = window.setTimeout(function() {
61060                fn.apply(ctx, args);
61061            }, quietMillis);
61062        };
61063    }
61064
61065    /**
61066     * A simple implementation of a thunk
61067     * @param formula function used to lazily initialize the thunk
61068     * @return {Function}
61069     */
61070    function thunk(formula) {
61071        var evaluated = false,
61072            value;
61073        return function() {
61074            if (evaluated === false) { value = formula(); evaluated = true; }
61075            return value;
61076        };
61077    };
61078
61079    function installDebouncedScroll(threshold, element) {
61080        var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
61081        element.bind("scroll", function (e) {
61082            if (indexOf(e.target, element.get()) >= 0) notify(e);
61083        });
61084    }
61085
61086    function killEvent(event) {
61087        event.preventDefault();
61088        event.stopPropagation();
61089    }
61090    function killEventImmediately(event) {
61091        event.preventDefault();
61092        event.stopImmediatePropagation();
61093    }
61094
61095    function measureTextWidth(e) {
61096        if (!sizer){
61097        	var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
61098        	sizer = $("<div></div>").css({
61099	            position: "absolute",
61100	            left: "-10000px",
61101	            top: "-10000px",
61102	            display: "none",
61103	            fontSize: style.fontSize,
61104	            fontFamily: style.fontFamily,
61105	            fontStyle: style.fontStyle,
61106	            fontWeight: style.fontWeight,
61107	            letterSpacing: style.letterSpacing,
61108	            textTransform: style.textTransform,
61109	            whiteSpace: "nowrap"
61110	        });
61111        	$("body").append(sizer);
61112        }
61113        sizer.text(e.val());
61114        return sizer.width();
61115    }
61116
61117    function markMatch(text, term, markup) {
61118        var match=text.toUpperCase().indexOf(term.toUpperCase()),
61119            tl=term.length;
61120
61121        if (match<0) {
61122            markup.push(text);
61123            return;
61124        }
61125
61126        markup.push(text.substring(0, match));
61127        markup.push("<span class='select2-match'>");
61128        markup.push(text.substring(match, match + tl));
61129        markup.push("</span>");
61130        markup.push(text.substring(match + tl, text.length));
61131    }
61132
61133    /**
61134     * Produces an ajax-based query function
61135     *
61136     * @param options object containing configuration paramters
61137     * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
61138     * @param options.url url for the data
61139     * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
61140     * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified
61141     * @param options.traditional a boolean flag that should be true if you wish to use the traditional style of param serialization for the ajax request
61142     * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
61143     * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2.
61144     *      The expected format is an object c
61144ontaining the following keys:
61145     *      results array of objects that will be used as choices
61146     *      more (optional) boolean indicating whether there are more results available
61147     *      Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
61148     */
61149    function ajax(options) {
61150        var timeout, // current scheduled but not yet executed request
61151            requestSequence = 0, // sequence used to drop out-of-order responses
61152            handler = null,
61153            quietMillis = options.quietMillis || 100;
61154
61155        return function (query) {
61156            window.clearTimeout(timeout);
61157            timeout = window.setTimeout(function () {
61158                requestSequence += 1; // increment the sequence
61159                var requestNumber = requestSequence, // this request's sequence number
61160                    data = options.data, // ajax data function
61161                    transport = options.transport || $.ajax,
61162                    traditional = options.traditional || false,
61163                    type = options.type || 'GET'; // set type of request (GET or POST)
61164
61165                data = data.call(this, query.term, query.page, query.context);
61166
61167                if( null !== handler) { handler.abort(); }
61168
61169                handler = transport.call(null, {
61170                    url: options.url,
61171                    dataType: options.dataType,
61172                    data: data,
61173                    type: type,
61174                    traditional: traditional,
61175                    success: function (data) {
61176                        if (requestNumber < requestSequence) {
61177                            return;
61178                        }
61179                        // TODO 3.0 - replace query.page with query so users have access to term, page, etc.
61180                        var results = options.results(data, query.page);
61181                        query.callback(results);
61182                    }
61183                });
61184            }, quietMillis);
61185        };
61186    }
61187
61188    /**
61189     * Produces a query function that works with a local array
61190     *
61191     * @param options object containing configuration parameters. The options parameter can either be an array or an
61192     * object.
61193     *
61194     * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
61195     *
61196     * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
61197     * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
61198     * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
61199     * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
61200     * the text.
61201     */
61202    function local(options) {
61203        var data = options, // data elements
61204            dataText,
61205            text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
61206
61207        if (!$.isArray(data)) {
61208            text = data.text;
61209            // if text is not a function we assume it to be a key name
61210            if (!$.isFunction(text)) {
61211              dataText = data.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
61212              text = function (item) { return item[dataText]; };
61213            }
61214            data = data.results;
61215        }
61216
61217        return function (query) {
61218            var t = query.term, filtered = { results: [] }, process;
61219            if (t === "") {
61220                query.callback({results: data});
61221                return;
61222            }
61223
61224            process = function(datum, collection) {
61225                var group, attr;
61226                datum = datum[0];
61227                if (datum.children) {
61228                    group = {};
61229                    for (attr in datum) {
61230                        if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
61231                    }
61232                    group.children=[];
61233                    $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
61234                    if (group.children.length || query.matcher(t, text(group))) {
61235                        collection.push(group);
61236                    }
61237                } else {
61238                    if (query.matcher(t, text(datum))) {
61239                        collection.push(datum);
61240                    }
61241                }
61242            };
61243
61244            $(data).each2(function(i, datum) { process(datum, filtered.results); });
61245            query.callback(filtered);
61246        };
61247    }
61248
61249    // TODO javadoc
61250    function tags(data) {
61251        // TODO even for a function we should probably return a wrapper that does the same object/string check as
61252        // the function for arrays. otherwise only functions that return objects are supported.
61253        if ($.isFunction(data)) {
61254            return data;
61255        }
61256
61257        // if not a function we assume it to be an array
61258
61259        return function (query) {
61260            var t = query.term, filtered = {results: []};
61261            $(data).each(function () {
61262                var isObject = this.text !== undefined,
61263                    text = isObject ? this.text : this;
61264                if (t === "" || query.matcher(t, text)) {
61265                    filtered.results.push(isObject ? this : {id: this, text: this});
61266                }
61267            });
61268            query.callback(filtered);
61269        };
61270    }
61271
61272    /**
61273     * Checks if the formatter function should be used.
61274     *
61275     * Throws an error if it is not a function. Returns true if it should be used,
61276     * false if no formatting should be performed.
61277     *
61278     * @param formatter
61279     */
61280    function checkFormatter(formatter, formatterName) {
61281        if ($.isFunction(formatter)) return true;
61282        if (!formatter) return false;
61283        throw new Error("formatterName must be a function or a falsy value");
61284    }
61285
61286    function evaluate(val) {
61287        return $.isFunction(val) ? val() : val;
61288    }
61289
61290    function countResults(results) {
61291        var count = 0;
61292        $.each(results, function(i, item) {
61293            if (item.children) {
61294                count += countResults(item.children);
61295            } else {
61296                count++;
61297            }
61298        });
61299        return count;
61300    }
61301
61302    /**
61303     * Default tokenizer. This function uses breaks the input on substring match of any string from the
61304     * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
61305     * two options have to be defined in order for the tokenizer to work.
61306     *
61307     * @param input text user has typed so far or pasted into the search field
61308     * @param selection currently selected choices
61309     * @param selectCallback function(choice) callback tho add the choice to selection
61310     * @param opts select2's opts
61311     * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
61312     */
61313    function defaultTokenizer(input, selection, selectCallback, opts) {
61314        var original = input, // store the original so we can compare and know if we need to tell the search to update its text
61315            dupe = false, // check for whether a token we extracted represents a duplicate selected choice
61316            token, // token
61317            index, // position at which the separator was found
61318            i, l, // looping variables
61319            separator; // the matched separator
61320
61321        if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
61322
61323        while (true) {
61324            index = -1;
61325
61326            for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
61327                separator = opts.tokenSeparators[i];
61328                index = input.indexOf(separator);
61329                if (index >= 0) break;
61330            }
61331
61332            if (index < 0) break; // did not find any token separator in the input string, bail
61333
61334            token = input.substring(0, index);
61335            input = input.substring(index + separator.length);
61336
61337            if (token.length > 0) {
61338                token = opts.createSearchChoice(token, selection);
61339                if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
61340                    dupe = false;
61341                    for (i = 0, l = selection.length; i < l; i++) {
61342                        if (equal(opts.id(token), opts.id(selection[i]))) {
61343                            dupe = true; break;
61344                        }
61345                    }
61346
61347                    if (!dupe) selectCallback(token);
61348                }
61349            }
61350        }
61351
61352        if (original.localeCompare(input) != 0) return input;
61353    }
61354
61355    /**
61356     * blurs any Select2 container that has focus when an element outside them was clicked or received focus
61357     *
61358     * also takes care of clicks on label tags that point to the source element
61359     */
61360    $document.ready(function () {
61361        $document.bind("mousedown touchend", function (e) {
61362            var target = $(e.target).closest("div.select2-container").get(0), attr;
61363            var targetDropdown = null;
61364            if (target) {
61365                $document.find("div.select2-container-active").each(function () {
61366                    if (this !== target) $(this).data("select2").blur();
61367                });
61368                targetDropdown = $(target).data('select2').dropdown.get(0);
61369            }
61370
61371            // close any other active dropdowns
61372            target = targetDropdown || $(e.target).closest("div.select2-drop").get(0);
61373            $document.find("div.select2-drop-active").each(function () {
61374                if (this !== target) $(this).data("select2").blur();
61375            });
61376
61377            target=$(e.target);
61378            attr = target.attr("for");
61379            if ("LABEL" === e.target.tagName && attr && attr.length > 0) {
61380                attr = attr.replace(/([\[\].])/g,'\\$1'); /* escapes [, ], and . so properly selects the id */
61381                target = $("#"+attr);
61382                target = target.data("select2");
61383                if (target !== undefined) { target.focus(); e.preventDefault();}
61384            }
61385        });
61386    });
61387
61388    /**
61389     * Creates a new class
61390     *
61391     * @param superClass
61392     * @param methods
61393     */
61394    function clazz(SuperClass, methods) {
61395        var constructor = function () {};
61396        constructor.prototype = new SuperClass;
61397        constructor.prototype.constructor = constructor;
61398        constructor.prototype.parent = SuperClass.prototype;
61399        constructor.prototype = $.extend(constructor.prototype, methods);
61400        return constructor;
61401    }
61402
61403    AbstractSelect2 = clazz(Object, {
61404
61405        // abstract
61406        bind: function (func) {
61407            var self = this;
61408            return function () {
61409                func.apply(self, arguments);
61410            };
61411        },
61412
61413        // abstract
61414        init: function (opts) {
61415            var results, search, resultsSelector = ".select2-results";
61416
61417            // prepare options
61418            this.opts = opts = this.prepareOpts(opts);
61419
61420            this.id=opts.id;
61421
61422            // destroy if called on an existing component
61423            if (opts.element.data("select2") !== undefined &&
61424                opts.element.data("select2") !== null) {
61425                this.destroy();
61426            }
61427
61428            this.enabled=true;
61429            this.container = this.createContainer();
61430
61431            this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
61432            this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
61433            this.container.attr("id", this.containerId);
61434
61435            // cache the body so future lookups are cheap
61436            this.body = thunk(function() { return opts.element.closest("body"); });
61437
61438            if (opts.element.attr("class") !== undefined) {
61439                this.container.addClass(opts.element.attr("class").replace(/validate\[[\S ]+] ?/, ''));
61440            }
61441
61442            this.container.css(evaluate(opts.containerCss));
61443            this.container.addClass(evaluate(opts.containerCssClass));
61444
61445            // swap container for the element
61446            this.opts.element
61447                .data("select2", this)
61448                .hide()
61449                .before(this.container);
61450            this.container.data("select2", this);
61451
61452            this.dropdown = this.container.find(".select2-drop");
61453            this.dropdown.addClass(evaluate(opts.dropdownCssClass));
61454            this.dropdown.data("select2", this);
61455
61456            this.results = results = this.container.find(resultsSelector);
61457            this.search = search = this.container.find("input.select2-input");
61458
61459            search.attr("tabIndex", this.opts.element.attr("tabIndex"));
61460
61461            this.resultsPage = 0;
61462            this.context = null;
61463
61464            // initialize the container
61465            this.initContainer();
61466            this.initContainerWidth();
61467
61468            installFilteredMouseMove(this.results);
61469            this.dropdown.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent));
61470
61471            installDebouncedScroll(80, this.results);
61472            this.dropdown.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded));
61473
61474            // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
61475            if ($.fn.mousewheel) {
61476                results.mousewheel(function (e, delta, deltaX, deltaY) {
61477                    var top = results.scrollTop(), height;
61478                    if (deltaY > 0 && top - deltaY <= 0) {
61479                        results.scrollTop(0);
61480                        killEvent(e);
61481                    } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
61482                        results.scrollTop(results.get(0).scrollHeight - results.height());
61483                        killEvent(e);
61484                    }
61485                });
61486            }
61487
61488            installKeyUpChangeEvent(search);
61489            search.bind("keyup-change", this.bind(this.updateResults));
61490            search.bind("focus", function () { search.addClass("select2-focused"); if (search.val() === " ") search.val(""); });
61491            search.bind("blur", function () { search.removeClass("select2-focused");});
61492
61493            this.dropdown.delegate(resultsSelector, "mouseup", this.bind(function (e) {
61494                if ($(e.target).closest(".select2-result-selectable:not(.select2-disabled)").length > 0) {
61495                    this.highlightUnderEvent(e);
61496                    this.selectHighlighted(e);
61497                } else {
61498                    this.focusSearch();
61499                }
61500                killEvent(e);
61501            }));
61502
61503            // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
61504            // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
61505            // dom it will trigger the popup close, which is not what we want
61506            this.dropdown.bind("click mouseup mousedown", function (e) { e.stopPropagation(); });
61507
61508            if ($.isFunction(this.opts.initSelection)) {
61509                // initialize selection based on the current value of the source element
61510                this.initSelection();
61511
61512                // if the user has provided a function that can set selection based on the value of the source element
61513                // we monitor the change event on the element and trigger it, allowing for two way synchronization
61514                this.monitorSource();
61515            }
61516
61517            if (opts.element.is(":disabled") || opts.element.is("[readonly='readonly']")) this.disable();
61518        },
61519
61520        // abstract
61521        destroy: function () {
61522            var select2 = this.opts.element.data("select2");
61523            if (select2 !== undefined) {
61524                select2.container.remove();
61525                select2.dropdown.remove();
61526                select2.opts.element
61527                    .removeData("select2")
61528                    .unbind(".select2")
61529                    .show();
61530            }
61531        },
61532
61533        // abstract
61534        prepareOpts: function (opts) {
61535            var element, select, idKey, ajaxUrl;
61536
61537            element = opts.element;
61538
61539            if (element.get(0).tagName.toLowerCase() === "select") {
61540                this.select = select = opts.element;
61541            }
61542
61543            if (select) {
61544                // these options are not allowed when attached to a select because they are picked up off the element itself
61545                $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
61546                    if (this in opts) {
61547                        throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
61548                    }
61549                });
61550            }
61551
61552            opts = $.extend({}, {
61553                populateResults: function(container, results, query
61553) {
61554                    var populate,  data, result, children, id=this.opts.id, self=this;
61555
61556                    populate=function(results, container, depth) {
61557
61558                        var i, l, result, selectable, compound, node, label, innerContainer, formatted;
61559
61560                        results = opts.sortResults(results, container, query);
61561
61562                        for (i = 0, l = results.length; i < l; i = i + 1) {
61563
61564                            result=results[i];
61565                            selectable=id(result) !== undefined;
61566                            compound=result.children && result.children.length > 0;
61567
61568                            node=$("<li></li>");
61569                            node.addClass("select2-results-dept-"+depth);
61570                            node.addClass("select2-result");
61571                            node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
61572                            if (compound) { node.addClass("select2-result-with-children"); }
61573                            node.addClass(self.opts.formatResultCssClass(result));
61574
61575                            label=$("<div></div>");
61576                            label.addClass("select2-result-label");
61577
61578                            formatted=opts.formatResult(result, label, query);
61579                            if (formatted!==undefined) {
61580                                label.html(self.opts.escapeMarkup(formatted));
61581                            }
61582
61583                            node.append(label);
61584
61585                            if (compound) {
61586
61587                                innerContainer=$("<ul></ul>");
61588                                innerContainer.addClass("select2-result-sub");
61589                                populate(result.children, innerContainer, depth+1);
61590                                node.append(innerContainer);
61591                            }
61592
61593                            node.data("select2-data", result);
61594                            container.append(node);
61595                        }
61596                    };
61597
61598                    populate(results, container, 0);
61599                }
61600            }, $.fn.select2.defaults, opts);
61601
61602            if (typeof(opts.id) !== "function") {
61603                idKey = opts.id;
61604                opts.id = function (e) { return e[idKey]; };
61605            }
61606
61607            if (select) {
61608                opts.query = this.bind(function (query) {
61609                    var data = { results: [], more: false },
61610                        term = query.term,
61611                        children, firstChild, process;
61612
61613                    process=function(element, collection) {
61614                        var group;
61615                        if (element.is("option")) {
61616                            if (query.matcher(term, element.text(), element)) {
61617                                collection.push({id:element.attr("value"), text:element.text(), element: element.get(), css: element.attr("class")});
61618                            }
61619                        } else if (element.is("optgroup")) {
61620                            group={text:element.attr("label"), children:[], element: element.get(), css: element.attr("class")};
61621                            element.children().each2(function(i, elm) { process(elm, group.children); });
61622                            if (group.children.length>0) {
61623                                collection.push(group);
61624                            }
61625                        }
61626                    };
61627
61628                    children=element.children();
61629
61630                    // ignore the placeholder option if there is one
61631                    if (this.getPlaceholder() !== undefined && children.length > 0) {
61632                        firstChild = children[0];
61633                        if ($(firstChild).text() === "") {
61634                            children=children.not(firstChild);
61635                        }
61636                    }
61637
61638                    children.each2(function(i, elm) { process(elm, data.results); });
61639
61640                    query.callback(data);
61641                });
61642                // this is needed because inside val() we construct choices from options and there id is hardcoded
61643                opts.id=function(e) { return e.id; };
61644                opts.formatResultCssClass = function(data) { return data.css; }
61645            } else {
61646                if (!("query" in opts)) {
61647                    if ("ajax" in opts) {
61648                        ajaxUrl = opts.element.data("ajax-url");
61649                        if (ajaxUrl && ajaxUrl.length > 0) {
61650                            opts.ajax.url = ajaxUrl;
61651                        }
61652                        opts.query = ajax(opts.ajax);
61653                    } else if ("data" in opts) {
61654                        opts.query = local(opts.data);
61655                    } else if ("tags" in opts) {
61656                        opts.query = tags(opts.tags);
61657                        if (opts.createSearchChoice === undefined) {
61658                            opts.createSearchChoice = function (term) { return {id: term, text: term}; };
61659                        }
61660                        opts.initSelection = function (element, callback) {
61661                            var data = [];
61662                            $(splitVal(element.val(), opts.separator)).each(function () {
61663                                var id = this, text = this, tags=opts.tags;
61664                                if ($.isFunction(tags)) tags=tags();
61665                                $(tags).each(function() { if (equal(this.id, id)) { text = this.text; return false; } });
61666                                data.push({id: id, text: text});
61667                            });
61668
61669                            callback(data);
61670                        };
61671                    }
61672                }
61673            }
61674            if (typeof(opts.query) !== "function") {
61675                throw "query function not defined for Select2 " + opts.element.attr("id");
61676            }
61677
61678            return opts;
61679        },
61680
61681        /**
61682         * Monitor the original element for changes and update select2 accordingly
61683         */
61684        // abstract
61685        monitorSource: function () {
61686            this.opts.element.bind("change.select2", this.bind(function (e) {
61687                if (this.opts.element.data("select2-change-triggered") !== true) {
61688                    this.initSelection();
61689                }
61690            }));
61691        },
61692
61693        /**
61694         * Triggers the change event on the source element
61695         */
61696        // abstract
61697        triggerChange: function (details) {
61698
61699            details = details || {};
61700            details= $.extend({}, details, { type: "change", val: this.val() });
61701            // prevents recursive triggering
61702            this.opts.element.data("select2-change-triggered", true);
61703            this.opts.element.trigger(details);
61704            this.opts.element.data("select2-change-triggered", false);
61705
61706            // some validation frameworks ignore the change event and listen instead to keyup, click for selects
61707            // so here we trigger the click event manually
61708            this.opts.element.click();
61709
61710            // ValidationEngine ignorea the change event and listens instead to blur
61711            // so here we trigger the blur event manually if so desired
61712            if (this.opts.blurOnChange)
61713                this.opts.element.blur();
61714        },
61715
61716
61717        // abstract
61718        enable: function() {
61719            if (this.enabled) return;
61720
61721            this.enabled=true;
61722            this.container.removeClass("select2-container-disabled");
61723            this.opts.element.removeAttr("disabled");
61724        },
61725
61726        // abstract
61727        disable: function() {
61728            if (!this.enabled) return;
61729
61730            this.close();
61731
61732            this.enabled=false;
61733            this.container.addClass("select2-container-disabled");
61734            this.opts.element.attr("disabled", "disabled");
61735        },
61736
61737        // abstract
61738        opened: function () {
61739            return this.container.hasClass("select2-dropdown-open");
61740        },
61741
61742        // abstract
61743        positionDropdown: function() {
61744            var offset = this.container.offset(),
61745                height = this.container.outerHeight(false),
61746                width = this.container.outerWidth(false),
61747                dropHeight = this.dropdown.outerHeight(false),
61748	        viewPortRight = $(window).scrollLeft() + document.documentElement.clientWidth,
61749                viewportBottom = $(window).scrollTop() + document.documentElement.clientHeight,
61750                dropTop = offset.top + height,
61751                dropLeft = offset.left,
61752                enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
61753                enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(),
61754	        dropWidth = this.dropdown.outerWidth(false),
61755	        enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
61756                aboveNow = this.dropdown.hasClass("select2-drop-above"),
61757                bodyOffset,
61758                above,
61759                css;
61760
61761            // console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
61762            // console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove);
61763
61764            // fix positioning when body has an offset and is not position: static
61765
61766            if (this.body().css('position') !== 'static') {
61767                bodyOffset = this.body().offset();
61768                dropTop -= bodyOffset.top;
61769                dropLeft -= bodyOffset.left;
61770            }
61771
61772            // always prefer the current above/below alignment, unless there is not enough room
61773
61774            if (aboveNow) {
61775                above = true;
61776                if (!enoughRoomAbove && enoughRoomBelow) above = false;
61777            } else {
61778                above = false;
61779                if (!enoughRoomBelow && enoughRoomAbove) above = true;
61780            }
61781
61782	    if (!enoughRoomOnRight) {
61783		   dropLeft = offset.left + width - dropWidth;
61784	    }
61785
61786            if (above) {
61787                dropTop = offset.top - dropHeight;
61788                this.container.addClass("select2-drop-above");
61789                this.dropdown.addClass("select2-drop-above");
61790            }
61791            else {
61792                this.container.removeClass("select2-drop-above");
61793                this.dropdown.removeClass("select2-drop-above");
61794            }
61795
61796            css = $.extend({
61797                top: dropTop,
61798                left: dropLeft,
61799                width: width
61800            }, evaluate(this.opts.dropdownCss));
61801
61802            this.dropdown.css(css);
61803        },
61804
61805        // abstract
61806        shouldOpen: function() {
61807            var event;
61808
61809            if (this.opened()) return false;
61810
61811            event = $.Event("open");
61812            this.opts.element.trigger(event);
61813            return !event.isDefaultPrevented();
61814        },
61815
61816        // abstract
61817        clearDropdownAlignmentPreference: function() {
61818            // clear the classes used to figure out the preference of where the dropdown should be opened
61819            this.container.removeClass("select2-drop-above");
61820            this.dropdown.removeClass("select2-drop-above");
61821        },
61822
61823        /**
61824         * Opens the dropdown
61825         *
61826         * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
61827         * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
61828         */
61829        // abstract
61830        open: function () {
61831
61832            if (!this.shouldOpen()) return false;
61833
61834            window.setTimeout(this.bind(this.opening), 1);
61835
61836            return true;
61837        },
61838
61839        /**
61840         * Performs the opening of the dropdown
61841         */
61842        // abstract
61843        opening: function() {
61844            var cid = this.containerId, selector = this.containerSelector,
61845                scroll = "scroll." + cid, resize = "resize." + cid;
61846
61847            this.container.parents().each(function() {
61848                $(this).bind(scroll, function() {
61849                    var s2 = $(selector);
61850                    if (s2.length == 0) {
61851                        $(this).unbind(scroll);
61852                    }
61853                    s2.select2("close");
61854                });
61855            });
61856
61857            window.setTimeout(function() {
61858                // this is done inside a timeout because IE will sometimes fire a resize event while opening
61859                // the dropdown and that causes this handler to immediately close it. this way the dropdown
61860                // has a chance to fully open before we start listening to resize events
61861                $(window).bind(resize, function() {
61862                    var s2 = $(selector);
61863                    if (s2.length == 0) {
61864                        $(window).unbind(resize);
61865                    }
61866                    s2.select2("close");
61867                })
61868            }, 10);
61869
61870            this.clearDropdownAlignmentPreference();
61871
61872            if (this.search.val() === " ") { this.search.val(""); }
61873
61874            this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
61875
61876            this.updateResults(true);
61877
61878            if(this.dropdown[0] !== this.body().children().last()[0]) {
61879                this.dropdown.detach().appendTo(this.body());
61880            }
61881
61882            this.dropdown.show();
61883
61884            this.positionDropdown();
61885            this.dropdown.addClass("select2-drop-active");
61886
61887            this.ensureHighlightVisible();
61888
61889            this.focusSearch();
61890        },
61891
61892        // abstract
61893        close: function () {
61894            if (!this.opened()) return;
61895
61896            var self = this;
61897
61898            this.container.parents().each(function() {
61899                $(this).unbind("scroll." + self.containerId);
61900            });
61901            $(window).unbind("resize." + this.containerId);
61902
61903            this.clearDropdownAlignmentPreference();
61904
61905            this.dropdown.hide();
61906            this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
61907            this.results.empty();
61908            this.clearSearch();
61909
61910            this.opts.element.trigger($.Event("close"));
61911        },
61912
61913        // abstract
61914        clearSearch: function () {
61915
61916        },
61917
61918        // abstract
61919        ensureHighlightVisible: function () {
61920            var results = this.results, children, index, child, hb, rb, y, more;
61921
61922            index = this.highlight();
61923
61924            if (index < 0) return;
61925
61926            if (index == 0) {
61927
61928                // if the first element is highlighted scroll all the way to the top,
61929                // that way any unselectable headers above it will also be scrolled
61930                // into view
61931
61932                results.scrollTop(0);
61933                return;
61934            }
61935
61936            children = results.find(".select2-result-selectable");
61937
61938            child = $(children[index]);
61939
61940            hb = child.offset().top + child.outerHeight(true);
61941
61942            // if this is the last child lets also make sure select2-more-results is visible
61943            if (index === children.length - 1) {
61944                more = results.find("li.select2-more-results");
61945                if (more.length > 0) {
61946                    hb = more.offset().top + more.outerHeight(true);
61947                }
61948            }
61949
61950            rb = results.offset().top + results.outerHeight(true);
61951            if (hb > rb) {
61952                results.scrollTop(results.scrollTop() + (hb - rb));
61953            }
61954            y = child.offset().top - results.offset().top;
61955
61956            // make sure the top of the element is visible
61957            if (y < 0 && child.css('display') != 'none' ) {
61958                results.scrollTop(results.scrollTop() + y); // y is negative
61959            }
61960        },
61961
61962        // abstract
61963        moveHighlight: function (delta) {
61964            var choices = this.results.find(".select2-result-selectable"),
61965                index = this.highlight();
61966
61967            while (index > -1 && index < choices.length) {
61968                index += delta;
61969                var choice = $(choices[index]);
61970                if (choice.hasClass("select2-result-selectable") && !c
61970hoice.hasClass("select2-disabled")) {
61971                    this.highlight(index);
61972                    break;
61973                }
61974            }
61975        },
61976
61977        // abstract
61978        highlight: function (index) {
61979            var choices = this.results.find(".select2-result-selectable").not(".select2-disabled");
61980
61981            if (arguments.length === 0) {
61982                return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
61983            }
61984
61985            if (index >= choices.length) index = choices.length - 1;
61986            if (index < 0) index = 0;
61987
61988            choices.removeClass("select2-highlighted");
61989
61990            $(choices[index]).addClass("select2-highlighted");
61991            this.ensureHighlightVisible();
61992
61993        },
61994
61995        // abstract
61996        countSelectableResults: function() {
61997            return this.results.find(".select2-result-selectable").not(".select2-disabled").length;
61998        },
61999
62000        // abstract
62001        highlightUnderEvent: function (event) {
62002            var el = $(event.target).closest(".select2-result-selectable");
62003            if (el.length > 0 && !el.is(".select2-highlighted")) {
62004        		var choices = this.results.find('.select2-result-selectable');
62005                this.highlight(choices.index(el));
62006            } else if (el.length == 0) {
62007                // if we are over an unselectable item remove al highlights
62008                this.results.find(".select2-highlighted").removeClass("select2-highlighted");
62009            }
62010        },
62011
62012        // abstract
62013        loadMoreIfNeeded: function () {
62014            var results = this.results,
62015                more = results.find("li.select2-more-results"),
62016                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
62017                offset = -1, // index of first element without data
62018                page = this.resultsPage + 1,
62019                self=this,
62020                term=this.search.val(),
62021                context=this.context;
62022
62023            if (more.length === 0) return;
62024            below = more.offset().top - results.offset().top - results.height();
62025
62026            if (below <= 0) {
62027                more.addClass("select2-active");
62028                this.opts.query({
62029                        term: term,
62030                        page: page,
62031                        context: context,
62032                        matcher: this.opts.matcher,
62033                        callback: this.bind(function (data) {
62034
62035                    // ignore a response if the select2 has been closed before it was received
62036                    if (!self.opened()) return;
62037
62038
62039                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
62040
62041                    if (data.more===true) {
62042                        more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
62043                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
62044                    } else {
62045                        more.remove();
62046                    }
62047                    self.positionDropdown();
62048                    self.resultsPage = page;
62049                })});
62050            }
62051        },
62052
62053        /**
62054         * Default tokenizer function which does nothing
62055         */
62056        tokenize: function() {
62057
62058        },
62059
62060        /**
62061         * @param initial whether or not this is the call to this method right after the dropdown has been opened
62062         */
62063        // abstract
62064        updateResults: function (initial) {
62065            var search = this.search, results = this.results, opts = this.opts, data, self=this, input;
62066
62067            // if the search is currently hidden we do not alter the results
62068            if (initial !== true && (this.showSearchInput === false || !this.opened())) {
62069                return;
62070            }
62071
62072            search.addClass("select2-active");
62073
62074            function postRender() {
62075                results.scrollTop(0);
62076                search.removeClass("select2-active");
62077                self.positionDropdown();
62078            }
62079
62080            function render(html) {
62081                results.html(self.opts.escapeMarkup(html));
62082                postRender();
62083            }
62084
62085            if (opts.maximumSelectionSize >=1) {
62086                data = this.data();
62087                if ($.isArray(data) && data.length >= opts.maximumSelectionSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
62088            	    render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(opts.maximumSelectionSize) + "</li>");
62089            	    return;
62090                }
62091            }
62092
62093            if (search.val().length < opts.minimumInputLength) {
62094                if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
62095                    render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
62096                } else {
62097                    render("");
62098                }
62099                return;
62100            }
62101            else if (opts.formatSearching()) {
62102                render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
62103            }
62104
62105            if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
62106                if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
62107                    render("<li class='select2-no-results'>" + opts.formatInputTooLong(search.val(), opts.maximumInputLength) + "</li>");
62108                } else {
62109                    render("");
62110                }
62111                return;
62112            }
62113
62114            // give the tokenizer a chance to pre-process the input
62115            input = this.tokenize();
62116            if (input != undefined && input != null) {
62117                search.val(input);
62118            }
62119
62120            this.resultsPage = 1;
62121            opts.query({
62122                    term: search.val(),
62123                    page: this.resultsPage,
62124                    context: null,
62125                    matcher: opts.matcher,
62126                    callback: this.bind(function (data) {
62127                var def; // default choice
62128
62129                // ignore a response if the select2 has been closed before it was received
62130                if (!this.opened()) return;
62131
62132                // save context, if any
62133                this.context = (data.context===undefined) ? null : data.context;
62134
62135                // create a default choice and prepend it to the list
62136                if (this.opts.createSearchChoice && search.val() !== "") {
62137                    def = this.opts.createSearchChoice.call(null, search.val(), data.results);
62138                    if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
62139                        if ($(data.results).filter(
62140                            function () {
62141                                return equal(self.id(this), self.id(def));
62142                            }).length === 0) {
62143                            data.results.unshift(def);
62144                        }
62145                    }
62146                }
62147
62148                if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
62149                    render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
62150                    return;
62151                }
62152
62153                results.empty();
62154                self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
62155
62156                if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
62157                    results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>");
62158                    window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
62159                }
62160
62161                this.postprocessResults(data, initial);
62162
62163                postRender();
62164            })});
62165        },
62166
62167        // abstract
62168        cancel: function () {
62169            this.close();
62170        },
62171
62172        // abstract
62173        blur: function () {
62174            this.close();
62175            this.container.removeClass("select2-container-active");
62176            this.dropdown.removeClass("select2-drop-active");
62177            // synonymous to .is(':focus'), which is available in jquery >= 1.6
62178            if (this.search[0] === document.activeElement) { this.search.blur(); }
62179            this.clearSearch();
62180            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
62181            this.opts.element.triggerHandler("blur");
62182        },
62183
62184        // abstract
62185        focusSearch: function () {
62186            // need to do it here as well as in timeout so it works in IE
62187            this.search.show();
62188            this.search.focus();
62189
62190            /* we do this in a timeout so that current event processing can complete before this code is executed.
62191             this makes sure the search field is focussed even if the current event would blur it */
62192            window.setTimeout(this.bind(function () {
62193                // reset the value so IE places the cursor at the end of the input box
62194                this.search.show();
62195                this.search.focus();
62196                this.search.val(this.search.val());
62197            }), 10);
62198        },
62199
62200        // abstract
62201        selectHighlighted: function () {
62202            var index=this.highlight(),
62203                highlighted=this.results.find(".select2-highlighted").not(".select2-disabled"),
62204                data = highlighted.closest('.select2-result-selectable').data("select2-data");
62205            if (data) {
62206                highlighted.addClass("select2-disabled");
62207                this.highlight(index);
62208                this.onSelect(data);
62209            }
62210        },
62211
62212        // abstract
62213        getPlaceholder: function () {
62214            return this.opts.element.attr("placeholder") ||
62215                this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
62216                this.opts.element.data("placeholder") ||
62217                this.opts.placeholder;
62218        },
62219
62220        /**
62221         * Get the desired width for the container element.  This is
62222         * derived first from option `width` passed to select2, then
62223         * the inline 'style' on the original element, and finally
62224         * falls back to the jQuery calculated element width.
62225         */
62226        // abstract
62227        initContainerWidth: function () {
62228            function resolveContainerWidth() {
62229                var style, attrs, matches, i, l;
62230
62231                if (this.opts.width === "off") {
62232                    return null;
62233                } else if (this.opts.width === "element"){
62234                    return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
62235                } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
62236                    // check if there is inline style on the element that contains width
62237                    style = this.opts.element.attr('style');
62238                    if (style !== undefined) {
62239                        attrs = style.split(';');
62240                        for (i = 0, l = attrs.length; i < l; i = i + 1) {
62241                            matches = attrs[i].replace(/\s/g, '')
62242                                .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/);
62243                            if (matches !== null && matches.length >= 1)
62244                                return matches[1];
62245                        }
62246                    }
62247
62248                    if (this.opts.width === "resolve") {
62249                        // next check if css('width') can resolve a width that is percent based, this is sometimes possible
62250                        // when attached to input type=hidden or elements hidden via css
62251                        style = this.opts.element.css('width');
62252                        if (style.indexOf("%") > 0) return style;
62253
62254                        // finally, fallback on the calculated width of the element
62255                        return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
62256                    }
62257
62258                    return null;
62259                } else if ($.isFunction(this.opts.width)) {
62260                    return this.opts.width();
62261                } else {
62262                    return this.opts.width;
62263               }
62264            };
62265
62266            var width = resolveContainerWidth.call(this);
62267            if (width !== null) {
62268                this.container.attr("style", "width: "+width);
62269            }
62270        }
62271    });
62272
62273    SingleSelect2 = clazz(AbstractSelect2, {
62274
62275        // single
62276
62277		createContainer: function () {
62278            var container = $("<div></div>", {
62279                "class": "select2-container"
62280            }).html([
62281                "    <a href='javascript:void(0)' onclick='return false;' class='select2-choice'>",
62282                "   <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>",
62283                "   <div><b></b></div>" ,
62284                "</a>",
62285                "    <div class='select2-drop select2-offscreen'>" ,
62286                "   <div class='select2-search'>" ,
62287                "       <input type='text' autocomplete='off' class='select2-input'/>" ,
62288                "   </div>" ,
62289                "   <ul class='select2-results'>" ,
62290                "   </ul>" ,
62291                "</div>"].join(""));
62292            return container;
62293        },
62294
62295        // single
62296        opening: function () {
62297            this.search.show();
62298            this.parent.opening.apply(this, arguments);
62299            this.dropdown.removeClass("select2-offscreen");
62300        },
62301
62302        // single
62303        close: function () {
62304            if (!this.opened()) return;
62305            this.parent.close.apply(this, arguments);
62306            this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show();
62307        },
62308
62309        // single
62310        focus: function () {
62311            this.close();
62312            this.selection.focus();
62313        },
62314
62315        // single
62316        isFocused: function () {
62317            return this.selection[0] === document.activeElement;
62318        },
62319
62320        // single
62321        cancel: function () {
62322            this.parent.cancel.apply(this, arguments);
62323            this.selection.focus();
62324        },
62325
62326        // single
62327        initContainer: function () {
62328
62329            var selection,
62330                container = this.container,
62331                dropdown = this.dropdown,
62332                clickingInside = false;
62333
62334            this.selection = selection = container.find(".select2-choice");
62335
62336            this.search.bind("keydown", this.bind(function (e) {
62337                if (!this.enabled) return;
62338
62339                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
62340                    // prevent the page from scrolling
62341                    killEvent(e);
62342                    return;
62343                }
62344
62345                if (this.opened()) {
62346                    switch (e.which) {
62347                        case KEY.UP:
62348                        case KEY.DOWN:
62349                            this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
62350                            killEvent(e);
62351                            return;
62352                        case KEY.TAB:
62353                        case KEY.ENTER:
62354                            this.selectHighlighted();
62355                            killEvent(e);
62356                            return;
62357                        case KEY.ESC:
62358                            this.cancel(e);
62359                            killEvent(e);
62360                            return;
62361                    }
62362                } else {
62363
62364                    if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
62365                        return;
62366                    }
62367
62368                    if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
62369                        return;
62370                    }
62371
62372                    this.open();
62373
62374                    if (e.which === KEY.ENTER) {
62375                        // do not propagate the event otherwise we open, and propagate enter which closes
62376                        return;
62377                    }
62378                }
62379            }));
62380
62381            this.search.bind("focus", this.bind(function() {
62382                this.selection.attr("tabIndex", "-1");
62383            }));
62384            this.search.bind("blur", this.bind(function() {
62385                if (!this.opened()) this.container.removeClass("select2-container-active");
62386                window.setTimeout(this.bind(function() {
62387                    // restore original tab index
62388                    var ti=this.opts.element.attr("tabIndex") || 0;
62389                    if (ti) {
62390                        this.selection.attr("tabIndex", ti);
62391                    } else {
62392                        this.selection.removeAttr("tabIndex");
62393                    }
62394                }), 10);
62395            }));
62396
62397            selection.delegate("abbr", "mousedown", this.bind(function (e) {
62398                if (!this.enabled) return;
62399                this.clear();
62400                killEventImmediately(e);
62401                this.close();
62402                this.triggerChange();
62403                this.selection.focus();
62404            }));
62405
62406            selection.bind("mousedown", this.bind(function (e) {
62407                clickingInside = true;
62408
62409                if (this.opened()) {
62410                    this.close();
62411                    this.selection.focus();
62412                } else if (this.enabled) {
62413                    this.open();
62414                }
62415
62416                clickingInside = false;
62417            }));
62418
62419            dropdown.bind("mousedown", this.bind(function() { this.search.focus(); }));
62420
62421            selection.bind("focus", this.bind(function() {
62422                this.container.addClass("select2-container-active");
62423                // hide the search so the tab key does not focus on it
62424                this.search.attr("tabIndex", "-1");
62425            }));
62426
62427            selection.bind("blur", this.bind(function() {
62428                if (!this.opened()) {
62429                    this.container.removeClass("select2-container-active");
62430                }
62431                window.setTimeout(this.bind(function() { this.search.attr("tabIndex", this.opts.element.attr("tabIndex") || 0); }), 10);
62432            }));
62433
62434            selection.bind("keydown", this.bind(function(e) {
62435                if (!this.enabled) return;
62436
62437                if (e.which == KEY.DOWN || e.which == KEY.UP
62438                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
62439                    this.open();
62440                    killEvent(e);
62441                    return;
62442                }
62443
62444                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
62445                    if (this.opts.allowClear) {
62446                        this.clear();
62447                    }
62448                    killEvent(e);
62449                    return;
62450                }
62451            }));
62452            selection.bind("keypress", this.bind(function(e) {
62453                var key = String.fromCharCode(e.which);
62454                this.search.val(key);
62455                this.open();
62456            }));
62457
62458            this.setPlaceholder();
62459
62460            this.search.bind("focus", this.bind(function() {
62461                this.container.addClass("select2-container-active");
62462            }));
62463        },
62464
62465        // single
62466        clear: function() {
62467            this.opts.element.val("");
62468            this.selection.find("span").empty();
62469            this.selection.removeData("select2-data");
62470            this.setPlaceholder();
62471        },
62472
62473        /**
62474         * Sets selection based on source element's value
62475         */
62476        // single
62477        initSelection: function () {
62478            var selected;
62479            if (this.opts.element.val() === "" && this.opts.element.text() === "") {
62480                this.close();
62481                this.setPlaceholder();
62482            } else {
62483                var self = this;
62484                this.opts.initSelection.call(null, this.opts.element, function(selected){
62485                    if (selected !== undefined && selected !== null) {
62486                        self.updateSelection(selected);
62487                        self.close();
62488                        self.setPlaceholder();
62489                    }
62490                });
62491            }
62492        },
62493
62494        // single
62495        prepareOpts: function () {
62496            var opts = this.parent.prepareOpts.apply(this, arguments);
62497
62498            if (opts.element.get(0).tagName.toLowerCase() === "select") {
62499                // install the selection initializer
62500                opts.initSelection = function (element, callback) {
62501                    var selected = element.find(":selected");
62502                    // a single select box always has a value, no need to null check 'selected'
62503                    if ($.isFunction(callback))
62504                        callback({id: selected.attr("value"), text: selected.text(), element:selected});
62505                };
62506            }
62507
62508            return opts;
62509        },
62510
62511        // single
62512        setPlaceholder: function () {
62513            var placeholder = this.getPlaceholder();
62514
62515            if (this.opts.element.val() === "" && placeholder !== undefined) {
62516
62517                // check for a first blank option if attached to a select
62518                if (this.select && this.select.find("option:first").text() !== "") return;
62519
62520                this.selection.find("span").html(this.opts.escapeMarkup(placeholder));
62521
62522                this.selection.addClass("select2-default");
62523
62524                this.selection.find("abbr").hide();
62525            }
62526        },
62527
62528        // single
62529        postprocessResults: function (data, initial) {
62530            var selected = 0, self = this, showSearchInput = true;
62531
62532            // find the selected element in the result list
62533
62534            this.results.find(".select2-result-selectable").each2(function (i, elm) {
62535                if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
62536                    selected = i;
62537                    return false;
62538                }
62539            });
62540
62541            // and highlight it
62542
62543            this.highlight(selected);
62544
62545            // hide the search box if this is the first we got the results and there are a few of them
62546
62547            if (initial === true) {
62548                showSearchInput = this.showSearchInput = countResults(data.results) >= this.opts.minimumResultsForSearch;
62549                this.dropdown.find(".select2-search")[showSearchInput ? "removeClass" : "addClass"]("select2-search-hidden");
62550
62551                //add "select2-with-searchbox" to the container if search box is shown
62552                $(this.dropdown, this.container)[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox");
62553            }
62554
62555        },
62556
62557        // single
62558        onSelect: function (data) {
62559            var old = this.opts.element.val();
62560
62561            this.opts.element.val(this.id(data));
62562            this.updateSelection(data);
62563            this.close();
62564            this.selection.focus();
62565
62566            if (!equal(old, this.id(data))) { this.triggerChange(); }
62567        },
62568
62569        // single
62570        updateSelection: function (data) {
62571
62572            var container=this.selection.find("span"), formatted;
62573
62574            this.selection.data("select2-data", data);
62575
62576            container.empty();
62577            formatted=this.opts.formatSelection(data, container);
62578            if (formatted !== undefined) {
62579                container.append(this.opts.escapeMarkup(formatted));
62580            }
62581
62582            this.selection.removeClass("select2-default");
62583
62584            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
62585                this.selection.find("abbr").show();
62586            }
62587        },
62588
62589        // single
62590        val: function () {
62591            var val, data = null, self = this;
62592
62593            if (arguments.length === 0) {
62594                return this.opts.element.val();
62595            }
62596
62597            val = arguments[0];
62598
62599            if (this.select) {
62600                this.select
62601                    .val(val)
62602                    .find(":selected").each2(function (i, elm) {
62603                        data = {id: elm.attr("value"), text: elm.text()};
62604                        return false;
62605                    });
62606                this.updateSelection(data);
62607                this.setPlaceholder();
62608                this.triggerChange();
62609            } else {
62610                if (this.opts.initSelection === undefined) {
62611                    throw new Error("cannot call val() if initSelection() is not defined");
62612                }
62613                // val is an id. !val is true for [undefined,null,'']
62614                if (!val) {
62615                    this.clear();
62616                    this.triggerChange();
62617                    return;
62618                }
62619                this.opts.element.val(val);
62620                this.opts.initSelection(this.opts.element, function(data){
62621                    self.opts.element.val(!data ? "" : self.id(data));
62622                    self.updateSelection(data);
62623                    self.setPlaceholder();
62624                    self.triggerChange();
62625                });
62626            }
62627        },
62628
62629        // single
62630        clearSearch: function () {
62631            this.search.val("");
62632        },
62633
62634        // single
62635        data: function(value) {
62636            var data;
62637
62638            if (arguments.length === 0) {
62639                data = this.selection.data("select2-data");
62640                if (data == undefined) data = null;
62641                return data;
62642            } else {
62643                if (!value || value === "") {
62644                    this.clear();
62645                } else {
62646                    this.opts.element.val(!value ? "" : this.id(value));
62647                    this.updateSelection(value);
62648                }
62649            }
62650        }
62651    });
62652
62653    MultiSelect2 = clazz(AbstractSelect2, {
62654
62655        // multi
62656        createContainer: function () {
62657            var container = $("<div></div>", {
62658                "class": "select2-container select2-container-multi"
62659            }).html([
62660                "    <ul class='select2-choices'>",
62661                //"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" ,
62662                "  <li class='select2-search-field'>" ,
62663                "    <input type='text' autocomplete='off' class='select2-input'>" ,
62664                "  </li>" ,
62665                "</ul>" ,
62666                "<div class='select2-drop select2-drop-multi' style='display:none;'>" ,
62667                "   <ul class='select2-results'>" ,
62668                "   </ul>" ,
62669                "</div>"].join(""));
62670			return container;
62671        },
62672
62673        // multi
62674        prepareOpts: function () {
62675            var opts = this.parent.prepareOpts.apply(this, arguments);
62676
62677            // TODO validate placeholder is a string if specified
62678
62679            if (opts.element.get(0).tagName.toLowerCase() === "select") {
62680                // install sthe selection initializer
62681                opts.initSelection = function (element,callback) {
62682
62683                    var data = [];
62684                    element.find(":selected").each2(function (i, elm) {
62685                        data.push({id: elm.attr("value"), text: elm.text(), element: elm});
62686                    });
62687
62688                    if ($.isFunction(callback))
62689                        callback(data);
62690                };
62691            }
62692
62693            return opts;
62694        },
62695
62696        // multi
62697        initContainer: function () {
62698
62699            var selector = ".select2-choices", selection;
62700
62701            this.searchContainer = this.container.find(".select2-search-field");
62702            this.selection = selection = this.container.find(selector);
62703
62704            this.search.bind("keydown", this.bind(function (e) {
62705                if (!this.enabled) return;
62706
62707                if (e.which === KEY.BACKSPACE && this.search.val() === "") {
62708                    this.close();
62709
62710                    var choices,
62711                        selected = selection.find(".select2-search-choice-focus");
62712                    if (selected.length > 0) {
62713                        this.unselect(selected.first());
62714                        this.search.width(10);
62715                        killEvent(e);
62716                        return;
62717                    }
62718
62719                    choices = selection.find(".select2-search-choice:not(.select2-locked)");
62720                    if (choices.length > 0) {
62721                        choices.last().addClass("select2-search-choice-focus");
62722                    }
62723                } else {
62724                    selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
62725                }
62726
62727                if (this.opened()) {
62728                    switch (e.which) {
62729                    case KEY.UP:
62730                    case KEY.DOWN:
62731                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
62732                        killEvent(e);
62733                        return;
62734                    case KEY.ENTER:
62735                    case KEY.TAB:
62736                        this.selectHighlighted();
62737                        killEvent(e);
62738                        return;
62739                    case KEY.ESC:
62740                        this.cancel(e);
62741                        killEvent(e);
62742                        return;
62743                    }
62744                }
62745
62746                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
62747                 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
62748                    return;
62749                }
62750
62751                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
62752                    return;
62753                }
62754
62755                this.open();
62756
62757                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
62758                    // prevent the page from scrolling
62759                    killEvent(e);
62760                }
62761            }));
62762
62763            this.search.bind("keyup", this.bind(this.resizeSearch));
62764
62765            this.search.bind("blur", this.bind(function(e) {
62766                this.container.removeClass("select2-container-active");
62767                this.search.removeClass("select2-focused");
62768                this.clearSearch();
62769                e.stopImmediatePropagation();
62770            }));
62771
62772            this.container.delegate(selector, "mousedown", this.bind(function (e) {
62773                if (!this.enabled) return;
62774                if ($(e.target).closest(".select2-search-choice").length > 0) {
62775                    // clicked inside a select2 search choice, do not open
62776                    return;
62777                }
62778                this.clearPlaceholder();
62779                this.open();
62780                this.focusSearch();
62781                e.preventDefault();
62782            }));
62783
62784            this.container.delegate(selector, "focus", this.bind(function () {
62785                if (!this.enabled) return;
62786                this.container.addClass("select2-container-active");
62787                this.dropdown.addClass("select2-drop-active");
62788                this.clearPlaceholder();
62789            }));
62790
62791            // set the placeholder if necessary
62792            this.clearSearch();
62793        },
62794
62795        // multi
62796        enable: function() {
62797            if (this.enabled) return;
62798
62799            this.parent.enable.apply(this, arguments);
62800
62801            this.search.removeAttr("disabled");
62802        },
62803
62804        // multi
62805        disable: function() {
62806            if (!this.enabled) return;
62807
62808            this.parent.disable.apply(this, arguments);
62809
62810            this.search.attr("disabled", true);
62811        },
62812
62813        // multi
62814        initSelection: function () {
62815            var data;
62816            if (this.opts.element.val() === "" && this.opts.element.text() === "") {
62817                this.updateSelection([]);
62818                this.close();
62819                // set the placeholder if necessary
62820                this.clearSearch();
62821            }
62822            if (this.select || this.opts.element.val() !== "") {
62823                var self = this;
62824                this.opts.initSelection.call(null, this.opts.element, function(data){
62825                    if (data !== undefined && data !== null) {
62826                        self.updateSelection(data);
62827                        self.close();
62828                        // set the placeholder if necessary
62829                        self.clearSearch();
62830                    }
62831                });
62832            }
62833        },
62834
62835        // multi
62836        clearSearch: function () {
62837            var placeholder = this.getPlaceholder();
62838
62839            if (placeholder !== undefined  && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
62840                this.search.val(placeholder).addClass("select2-default");
62841                // stretch the search box to full width of the container so as much of the placeholder is visible as possible
62842                this.resizeSearch();
62843            } else {
62844                // we set this to " " instead of "" and later clear it on focus() because there is a firefox bug
62845                // that does not properly render the caret when the field starts out blank
62846                this.search.val(" ").width(10);
62847            }
62848        },
62849
62850        // multi
62851        clearPlaceholder: function () {
62852            if (this.search.hasClass("select2-default")) {
62853                this.search.val("").removeClass("select2-default");
62854            } else {
62855                // work around for the space character we set to avoid firefox caret bug
62856                if (this.search.val() === " ") this.search.val("");
62857            }
62858        },
62859
62860        // multi
62861        opening: function () {
62862            this.parent.opening.apply(this, arguments);
62863
62864            this.clearPlaceholder();
62865			this.resizeSearch();
62866            this.focusSearch();
62867        },
62868
62869        // multi
62870        close: function () {
62871            if (!this.opened()) return;
62872            this.parent.close.apply(this, arguments);
62873        },
62874
62875        // multi
62876        focus: function () {
62877            this.close();
62878            this.search.focus();
62879        },
62880
62881        // multi
62882        isFocused: function () {
62883            return this.search.hasClass("select2-focused");
62884        },
62885
62886        // multi
62887        updateSelection: function (data) {
62888            var ids = [], filtered = [], self = this;
62889
62890            // filter out duplicates
62891            $(data).each(function () {
62892                if (indexOf(self.id(this), ids) < 0) {
62893                    ids.push(self.id(this));
62894                    filtered.push(this);
62895                }
62896            });
62897            data = filtered;
62898
62899            this.selection.find(".select2-search-choice").remove();
62900            $(data).each(function () {
62901                self.addSelectedChoice(this);
62902            });
62903            self.postprocessResults();
62904        },
62905
62906        tokenize: function() {
62907            var input = this.search.val();
62908            input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts);
62909            if (input != null && input != undefined) {
62910                this.search.val(input);
62911                if (input.length > 0) {
62912                    this.open();
62913                }
62914            }
62915
62916        },
62917
62918        // multi
62919        onSelect: function (data) {
62920            this.addSelectedChoice(data);
62921            if (this.select || !this.opts.closeOnSelect) this.postprocessResults();
62922
62923            if (this.opts.closeOnSelect) {
62924                this.close();
62925                this.search.width(10);
62926            } else {
62927                if (this.countSelectableResults()>0) {
62928                    this.search.width(10);
62929                    this.resizeSearch();
62930                    this.positionDropdown();
62931                } else {
62932                    // if nothing left to select close
62933                    this.close();
62934                }
62935            }
62936
62937            // since its not possible to select an element that has already been
62938            // added we do not need to check if this is a new element before firing change
62939            this.triggerChange({ added: data });
62940
62941            this.focusSearch();
62942        },
62943
62944        // multi
62945        cancel: function () {
62946            this.close();
62947            this.focusSearch();
62948        },
62949
62950        addSelectedChoice: function (data) {
62951            var enableChoice = !data.locked,
62952                enabledItem = $(
62953                    "<li class='select2-search-choice'>" +
62954                    "    <div></div>" +
62955                    "    <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" +
62956                    "</li>"),
62957                disabledItem = $(
62958                    "<li class='select2-search-choice select2-locked'>" +
62959                    "<div></div>" +
62960                    "</li>");
62961            var choice = enableChoice ? enabledItem : disabledItem,
62962                id = this.id(data),
62963                val = this.getVal(),
62964                formatted;
62965
62966            formatted=this.opts.formatSelection(data, choice.find("div"));
62967            if (formatted != undefined) {
62968                choice.find("div").replaceWith("<div>"+this.opts.escapeMarkup(formatted)+"</div>");
62969            }
62970
62971            if(enableChoice){
62972              choice.find(".select2-search-choice-close")
62973                  .bind("mousedown", killEvent)
62974                  .bind("click dblclick", this.bind(function (e) {
62975                  if (!this.enabled) return;
62976
62977                  $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){
62978                      this.unselect($(e.target));
62979                      this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
62980                      this.close();
62981                      this.focusSearch();
62982                  })).dequeue();
62983                  killEvent(e);
62984              })).bind("focus", this.bind(function () {
62985                  if (!this.enabled) return;
62986                  this.container.addClass("select2-container-active");
62987                  this.dropdown.addClass("select2-drop-active");
62988              }));
62989            }
62990
62991            choice.data("select2-data", data);
62992            choice.insertBefore(this.searchContainer);
62993
62994            val.push(id);
62995            this.setVal(val);
62996        },
62997
62998        // multi
62999        unselect: function (selected) {
63000            var val = this.getVal(),
63001                data,
63002                index;
63003
63004           
63004 selected = selected.closest(".select2-search-choice");
63005
63006            if (selected.length === 0) {
63007                throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
63008            }
63009
63010            data = selected.data("select2-data");
63011
63012            index = indexOf(this.id(data), val);
63013
63014            if (index >= 0) {
63015                val.splice(index, 1);
63016                this.setVal(val);
63017                if (this.select) this.postprocessResults();
63018            }
63019            selected.remove();
63020            this.triggerChange({ removed: data });
63021        },
63022
63023        // multi
63024        postprocessResults: function () {
63025            var val = this.getVal(),
63026                choices = this.results.find(".select2-result-selectable"),
63027                compound = this.results.find(".select2-result-with-children"),
63028                self = this;
63029
63030            choices.each2(function (i, choice) {
63031                var id = self.id(choice.data("select2-data"));
63032                if (indexOf(id, val) >= 0) {
63033                    choice.addClass("select2-disabled").removeClass("select2-result-selectable");
63034                } else {
63035                    choice.removeClass("select2-disabled").addClass("select2-result-selectable");
63036                }
63037            });
63038
63039            compound.each2(function(i, e) {
63040                if (!e.is('.select2-result-selectable') && e.find(".select2-result-selectable").length==0) {  // FIX FOR HIRECHAL DATA
63041                    e.addClass("select2-disabled");
63042                } else {
63043                    e.removeClass("select2-disabled");
63044                }
63045            });
63046
63047            if (this.highlight() == -1){
63048                choices.each2(function (i, choice) {
63049                    if (!choice.hasClass("select2-disabled") && choice.hasClass("select2-result-selectable")) {
63050                        self.highlight(0);
63051                        return false;
63052                    }
63053                });
63054            }
63055
63056        },
63057
63058        // multi
63059        resizeSearch: function () {
63060
63061            var minimumWidth, left, maxWidth, containerLeft, searchWidth,
63062            	sideBorderPadding = getSideBorderPadding(this.search);
63063
63064            minimumWidth = measureTextWidth(this.search) + 10;
63065
63066            left = this.search.offset().left;
63067
63068            maxWidth = this.selection.width();
63069            containerLeft = this.selection.offset().left;
63070
63071            searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
63072            if (searchWidth < minimumWidth) {
63073                searchWidth = maxWidth - sideBorderPadding;
63074            }
63075
63076            if (searchWidth < 40) {
63077                searchWidth = maxWidth - sideBorderPadding;
63078            }
63079
63080            if (searchWidth <= 0) {
63081              searchWidth = minimumWidth
63082            }
63083
63084            this.search.width(searchWidth);
63085        },
63086
63087        // multi
63088        getVal: function () {
63089            var val;
63090            if (this.select) {
63091                val = this.select.val();
63092                return val === null ? [] : val;
63093            } else {
63094                val = this.opts.element.val();
63095                return splitVal(val, this.opts.separator);
63096            }
63097        },
63098
63099        // multi
63100        setVal: function (val) {
63101            var unique;
63102            if (this.select) {
63103                this.select.val(val);
63104            } else {
63105                unique = [];
63106                // filter out duplicates
63107                $(val).each(function () {
63108                    if (indexOf(this, unique) < 0) unique.push(this);
63109                });
63110                this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
63111            }
63112        },
63113
63114        // multi
63115        val: function () {
63116            var val, data = [], self=this;
63117
63118            if (arguments.length === 0) {
63119                return this.getVal();
63120            }
63121
63122            val = arguments[0];
63123
63124            if (!val) {
63125                this.opts.element.val("");
63126                this.updateSelection([]);
63127                this.clearSearch();
63128                this.triggerChange();
63129                return;
63130            }
63131
63132            // val is a list of ids
63133            this.setVal(val);
63134
63135            if (this.select) {
63136                this.select.find(":selected").each(function () {
63137                    data.push({id: $(this).attr("value"), text: $(this).text()});
63138                });
63139                this.updateSelection(data);
63140                this.triggerChange();
63141            } else {
63142                if (this.opts.initSelection === undefined) {
63143                    throw new Error("val() cannot be called if initSelection() is not defined")
63144                }
63145
63146                this.opts.initSelection(this.opts.element, function(data){
63147                    var ids=$(data).map(self.id);
63148                    self.setVal(ids);
63149                    self.updateSelection(data);
63150                    self.clearSearch();
63151                    self.triggerChange();
63152                });
63153            }
63154            this.clearSearch();
63155        },
63156
63157        // multi
63158        onSortStart: function() {
63159            if (this.select) {
63160                throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
63161            }
63162
63163            // collapse search field into 0 width so its container can be collapsed as well
63164            this.search.width(0);
63165            // hide the container
63166            this.searchContainer.hide();
63167        },
63168
63169        // multi
63170        onSortEnd:function() {
63171
63172            var val=[], self=this;
63173
63174            // show search and move it to the end of the list
63175            this.searchContainer.show();
63176            // make sure the search container is the last item in the list
63177            this.searchContainer.appendTo(this.searchContainer.parent());
63178            // since we collapsed the width in dragStarted, we resize it here
63179            this.resizeSearch();
63180
63181            // update selection
63182
63183            this.selection.find(".select2-search-choice").each(function() {
63184                val.push(self.opts.id($(this).data("select2-data")));
63185            });
63186            this.setVal(val);
63187            this.triggerChange();
63188        },
63189
63190        // multi
63191        data: function(values) {
63192            var self=this, ids;
63193            if (arguments.length === 0) {
63194                 return this.selection
63195                     .find(".select2-search-choice")
63196                     .map(function() { return $(this).data("select2-data"); })
63197                     .get();
63198            } else {
63199                if (!values) { values = []; }
63200                ids = $.map(values, function(e) { return self.opts.id(e)});
63201                this.setVal(ids);
63202                this.updateSelection(values);
63203                this.clearSearch();
63204            }
63205        }
63206    });
63207
63208    $.fn.select2 = function () {
63209
63210        var args = Array.prototype.slice.call(arguments, 0),
63211            opts,
63212            select2,
63213            value, multiple, allowedMethods = ["val", "destroy", "opened", "open", "close", "focu
63213s", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable", "positionDropdown", "data"];
63214
63215        this.each(function () {
63216            if (args.length === 0 || typeof(args[0]) === "object") {
63217                opts = args.length === 0 ? {} : $.extend({}, args[0]);
63218                opts.element = $(this);
63219
63220                if (opts.element.get(0).tagName.toLowerCase() === "select") {
63221                    multiple = opts.element.attr("multiple");
63222                } else {
63223                    multiple = opts.multiple || false;
63224                    if ("tags" in opts) {opts.multiple = multiple = true;}
63225                }
63226
63227                select2 = multiple ? new MultiSelect2() : new SingleSelect2();
63228                select2.init(opts);
63229            } else if (typeof(args[0]) === "string") {
63230
63231                if (indexOf(args[0], allowedMethods) < 0) {
63232                    throw "Unknown method: " + args[0];
63233                }
63234
63235                value = undefined;
63236                select2 = $(this).data("select2");
63237                if (select2 === undefined) return;
63238                if (args[0] === "container") {
63239                    value=select2.container;
63240                } else {
63241                    value = select2[args[0]].apply(select2, args.slice(1));
63242                }
63243                if (value !== undefined) {return false;}
63244            } else {
63245                throw "Invalid arguments to select2 plugin: " + args;
63246            }
63247        });
63248        return (value === undefined) ? this : value;
63249    };
63250
63251    // plugin defaults, accessible to users
63252    $.fn.select2.defaults = {
63253        width: "copy",
63254        closeOnSelect: true,
63255        openOnEnter: true,
63256        containerCss: {},
63257        dropdownCss: {},
63258        containerCssClass: "",
63259        dropdownCssClass: "",
63260        formatResult: function(result, container, query) {
63261            var markup=[];
63262            markMatch(result.text, query.term, markup);
63263            return markup.join("");
63264        },
63265        formatSelection: function (data, container) {
63266            return data ? data.text : undefined;
63267        },
63268        sortResults: function (results, container, query) {
63269            return results;
63270        },
63271        formatResultCssClass: function(data) {return undefined;},
63272        formatNoMatches: function () { return "No matches found"; },
63273        formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); },
63274        formatInputTooLong: function (input, max) { var n = input.length - max; return "Please enter " + n + " less character" + (n == 1? "" : "s"); },
63275        formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
63276        formatLoadMore: function (pageNumber) { return "Loading more results..."; },
63277        formatSearching: function () { return "Searching..."; },
63278        minimumResultsForSearch: 0,
63279        minimumInputLength: 0,
63280        maximumInputLength: null,
63281        maximumSelectionSize: 0,
63282        id: function (e) { return e.id; },
63283        matcher: function(term, text) {
63284            return text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
63285        },
63286        separator: ",",
63287        tokenSeparators: [],
63288        tokenizer: defaultTokenizer,
63289        escapeMarkup: function (markup) {
63290            if (markup && typeof(markup) === "string") {
63291                return markup.replace(/&/g, "&amp;");
63292            }
63293            return markup;
63294        },
63295        blurOnChange: false
63296    };
63297
63298    // exports
63299    window.Select2 = {
63300        query: {
63301            ajax: ajax,
63302            local: local,
63303            tags: tags
63304        }, util: {
63305            debounce: debounce,
63306            markMatch: markMatch
63307        }, "class": {
63308            "abstract": AbstractSelect2,
63309            "single": SingleSelect2,
63310            "multi": MultiSelect2
63311        }
63312    };
63313
63314}(jQuery));
63315(function() {
63316  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
63317    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63318    hasProp = {}.hasOwnProperty;
63319
63320  App.Pages.TutorialsEdit = (function(superClass) {
63321    extend(TutorialsEdit, superClass);
63322
63323    function TutorialsEdit() {
63324      this.attachRedactor = bind(this.attachRedactor, this);
63325      this.onStepRemove = bind(this.onStepRemove, this);
63326      this.onStepInsert = bind(this.onStepInsert, this);
63327      this.removePrimaryImage = bind(this.removePrimaryImage, this);
63328      this.initUploader = bind(this.initUploader, this);
63329      this.handleFormSubmit = bind(this.handleFormSubmit, this);
63330      this.onSoftwareChange = bind(this.onSoftwareChange, this);
63331      this.onCategoriesChange = bind(this.onCategoriesChange, this);
63332      return TutorialsEdit.__super__.constructor.apply(this, arguments);
63333    }
63334
63335    TutorialsEdit.prototype.initialize = function() {
63336      var $steps;
63337      this.$form = $('.edit_tutorial');
63338      this.imagesPath = "/tutorials/" + gon.tutorial_id + "/images";
63339      this.$dropZone = $('.js-primary-image-dropzone');
63340      this.$uploadProgress = $('.js-primary-image-upload-progress');
63341      this.$uploadText = $('.upload-dropzone__text');
63342      this.$primaryImage = $('.tutorial__primary-image');
63343      this.$primaryImageContainer = $('.js-primary-image-container');
63344      this.reindexStepTitles = true;
63345      this.$categoriesCountField = $('#categories-count');
63346      this.$softwareCountField = $('#software-count');
63347      this.$imageUploadCounter = $('#image-upload-counter');
63348      if (gon.tutorial_error_notification) {
63349        this.displayErrorNotification();
63350      }
63351      this.initUploader();
63352      this.attachRedactor($('.step-description'));
63353      $steps = $('.js-steps');
63354      $steps.on('cocoon:after-insert', this.onStepInsert);
63355      $steps.on('cocoon:after-remove', this.onStepRemove);
63356      $('.step-position').on('change', (function(_this) {
63357        return function() {
63358          return _this.reindexStepTitles = false;
63359        };
63360      })(this));
63361      $('#tutorial_categories, #tutorial_software').select2({
63362        containerCssClass: 'custom-select2',
63363        width: 'resolve'
63364      });
63365      $('#tutorial_categories').on('change', this.onCategoriesChange);
63366      $('#tutorial_software').on('change', this.onSoftwareChange);
63367      $('#tutorial_tags').on('change', this.onTagsChange);
63368      this.$form.on('submit', this.handleFormSubmit);
63369      $("#tutorial_tags").select2({
63370        containerCssClass: 'custom-select2',
63371        tags: [],
63372        tokenSeparators: [',', ' ']
63373      });
63374      $('.js-primary-image-form__remove-link').on('ajax:success', this.removePrimaryImage);
63375      this.numberOfSelectedCategories = $('#s2id_tutorial_categories').find('.select2-search-choice').length;
63376      this.$categoriesCountField.val(this.numberOfSelectedCategories);
63377      this.numberOfSelectedSoftware = $('#s2id_tutorial_software').find('.select2-search-choice').length;
63378      this.$softwareCountField.val(this.numberOfSelectedSoftware);
63379      return this.initValidation();
63380    };
63381
63382    TutorialsEdit.prototype.onCategoriesChange = function() {
63383      this.$categoriesCountField.val($('#s2id_tutorial_categories').find('.select2-search-choice').length);
63384      return this.$categoriesCountField.valid();
63385    };
63386
63387    TutorialsEdit.prototype.onSoftwareChange = function() {
63388      this.$softwareCountField.val($('#s2id_tutorial_software').find('.select2-search-choice').length);
63389      return this.$softwareCountField.valid();
63390    };
63391
63392    TutorialsEdit.prototype.onTagsChange = function() {
63393      return $('#tutorial_tags').valid();
63394    };
63395
63396    TutorialsEdit.prototype.displayErrorNotification = function() {
63397      return App.events.publish('app:notification:error', gon.tutorial_error_notification);
63398    };
63399
63400    TutorialsEdit.prototype.handleFormSubmit = function(e) {
63401      var categoriesSelect, currentCategories, currentTags, isValid, tagsInput;
63402      isValid = this.$form.valid();
63403      if (isValid) {
63404        categoriesSelect = $('#tutorial_categories')[0];
63405        currentCategories = $(categoriesSelect).find(':selected').toArray().map((function(_this) {
63406          return function(item) {
63407            return item.text;
63408          };
63409        })(this)).join('_');
63410        tagsInput = $('#tutorial_tags')[0];
63411        currentTags = tagsInput.value.replaceAll(',', '_');
63412        return window.customEventTracker({
63413          name: 'upload_content',
63414          userId: App.config.userId || '',
63415          eventData: {
63416            content_type: 'tutorial',
63417            content_category: currentCategories,
63418            content_tags: currentTags,
63419            file_format: '',
63420            content_subcategory: '',
63421            model_price: 0.00
63422          }
63423        });
63424      }
63425    };
63426
63427    TutorialsEdit.prototype.initUploader = function() {
63428      return $('#tutorial_primary_image').fileupload({
63429        dataType: 'json',
63430        url: "/tutorials/" + gon.tutorial_id + "/images",
63431        autoUpload: true,
63432        dropZone: this.$dropZone,
63433        formData: {
63434          _method: 'post',
63435          primary: true
63436        },
63437        paramName: 'file',
63438        add: (function(_this) {
63439          return function(_, data) {
63440            _this.$dropZone.removeClass('no-image');
63441            _this.$uploadText.hide();
63442            _this.$primaryImageContainer.hide();
63443            if (!_this.$uploadProgress.is(':visible')) {
63444              _this.$uploadProgress.removeClass('is-hidden').fadeIn();
63445            }
63446            return data.submit();
63447          };
63448        })(this),
63449        done: (function(_this) {
63450          return function(_, data) {
63451            _this.$uploadProgress.hide();
63452            data = $.parseJSON(data.jqXHR.responseText);
63453            _this.$primaryImage.attr('src', data.image);
63454            _this.$primaryImageContainer.removeClass('is-hidden').fadeIn();
63455            $('.js-primary-image-form__remove-link').attr('href', "/tutorials/" + gon.tutorial_id + "/images/" + data.id);
63456            _this.$imageUploadCounter.val(1);
63457            return _this.$imageUploadCounter.valid();
63458          };
63459        })(this)
63460      });
63461    };
63462
63463    TutorialsEdit.prototype.removePrimaryImage = function() {
63464      this.$primaryImageContainer.hide();
63465      this.$dropZone.addClass('no-image');
63466      this.$uploadText.show().removeClass('is-hidden');
63467      this.$imageUploadCounter.val(0);
63468      return this.$imageUploadCounter.valid();
63469    };
63470
63471    TutorialsEdit.prototype.onStepInsert = function(e, step) {
63472      var displayPos, nextPos, previousStep;
63473      previousStep = step.prev('.nested-fields:visible');
63474      if (previousStep.length > 0) {
63475        nextPos = +previousStep.find('.step-position-input').val() + 1;
63476        displayPos = nextPos + 1;
63477      } else {
63478        nextPos = 0;
63479        displayPos = 1;
63480      }
63481      step.find('.step-position').val("Step " + displayPos);
63482      step.find('.step-position-input').val(nextPos);
63483      return this.attachRedactor(step.find('.step-description'));
63484    };
63485
63486    TutorialsEdit.prototype.onStepRemove = function() {
63487      return $('.nested-fields:visible').each((function(_this) {
63488        return function(index, step) {
63489          if (_this.reindexStepTitles) {
63490            $(step).find('.step-position').val("Step " + (index + 1));
63491            return $(step).find('.step-position-input').val(index);
63492          }
63493        };
63494      })(this));
63495    };
63496
63497    TutorialsEdit.prototype.attachRedactor = function($element) {
63498      return $element.redactor({
63499        plugins: ['fullscreen', 'fontsize', 'fontcolor', 'fontfamily'],
63500        minHeight: 300,
63501        convertDivs: false,
63502        autoresize: false,
63503        imageUpload: this.imagesPath,
63504        imageGetJson: this.imagesPath
63505      });
63506    };
63507
63508    TutorialsEdit.prototype.validationSettings = function() {
63509      return {
63510        ignore: [],
63511        rules: {
63512          'tutorial[title]': {
63513            required: true,
63514            validateCompetitors: true
63515          },
63516          'tutorial[description]': {
63517            validateCompetitors: true
63518          },
63519          'tutorial[tags]': {
63520            validateCompetitors: true
63521          },
63522          categories_count: {
63523            required: true,
63524            min: 1
63525          },
63526          software_count: {
63527            required: true,
63528            min: 1
63529          },
63530          image_upload_counter: {
63531            required: true,
63532            range: [1, 1]
63533          },
63534          publish: {
63535            required: true
63536          }
63537        },
63538        messages: {
63539          categories_count: {
63540            min: 'Please select at least 1 category'
63541          },
63542          software_count: {
63543            min: 'Please select at least 1 software'
63544          },
63545          image_upload_counter: {
63546            range: 'Please upload preview image'
63547          }
63548        },
63549        errorPlacement: function(error, element) {
63550          switch (element.attr('name')) {
63551            case 'categories_count':
63552              return error.insertAfter('#s2id_tutorial_categories');
63553            case 'software_count':
63554              return error.insertAfter('#s2id_tutorial_software');
63555            case 'publish':
63556              return error.appendTo($('.js-publish-checkbox-error'));
63557            default:
63558              return error.insertAfter(element);
63559          }
63560        }
63561      };
63562    };
63563
63564    TutorialsEdit.prototype.initValidation = function() {
63565      return this.$form.validate(this.validationSettings());
63566    };
63567
63568    return TutorialsEdit;
63569
63570  })(App.Core.Page);
63571
63572}).call(this);
63573(function() {
63574  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63575    hasProp = {}.hasOwnProperty;
63576
63577  App.Pages.TutorialsShow = (function(superClass) {
63578    extend(TutorialsShow, superClass);
63579
63580    function TutorialsShow() {
63581      return TutorialsShow.__super__.constructor.apply(this, arguments);
63582    }
63583
63584    TutorialsShow.prototype.widgets = ['comments', 'likes'];
63585
63586    TutorialsShow.prototype.initialize = function() {
63587      if (gon.preview) {
63588        return this.displayPreviewBar();
63589      }
63590    };
63591
63592    TutorialsShow.prototype.displayPreviewBar = function() {
63593      return $('.js-display-preview-bar').show();
63594    };
63595
63596    return TutorialsShow;
63597
63598  })(App.Core.Page);
63599
63600}).call(this);
63601(function() {
63602  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63603    hasProp = {}.hasOwnProperty;
63604
63605  App.Pages.TutorialsSubmitForReview = (function(superClass) {
63606    extend(TutorialsSubmitForReview, superClass);
63607
63608    function TutorialsSubmitForReview() {
63609      return TutorialsSubmitForReview.__super__.constructor.apply(this, arguments);
63610    }
63611
63612    return TutorialsSubmitForReview;
63613
63614  })(App.Pages.TutorialsEdit);
63615
63616}).call(this);
63617(function() {
63618  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63619    hasProp = {}.hasOwnProperty;
63620
63621  App.Pages.TutorialsUpdate = (function(superClass) {
63622    extend(TutorialsUpdate, superClass);
63623
63624    function TutorialsUpdate() {
63625      return TutorialsUpdate.__super__.constructor.apply(this, arguments);
63626    }
63627
63628    return TutorialsUpdate;
63629
63630  })(App.Pages.TutorialsEdit);
63631
63632}).call(this);
63633(function() {
63634  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63635    hasProp = {}.hasOwnProperty;
63636
63637  App.Pages.UsersIndex = (function(superClass) {
63638    extend(UsersIndex, superClass);
63639
63640    function UsersIndex() {
63641      return UsersIndex.__super__.constructor.apply(this, arguments);
63642    }
63643
63644    UsersIndex.prototype.initialize = function() {
63645      return this.bindEvents();
63646    };
63647
63648    UsersIndex.prototype.bindEvents = function() {
63649      return $('.js-user-search-button, .pagination li').on('click', this.showUpdatingSpinner);
63650    };
63651
63652    UsersIndex.prototype.showUpdatingSpinner = function() {
63653      return $('.users-list').addClass('is-updating-with-spinner spinner--top is-fixed');
63654    };
63655
63656    return UsersIndex;
63657
63658  })(App.Core.Page);
63659
63660}).call(this);
63661(function() {
63662  var base,
63663    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63664    hasProp = {}.hasOwnProperty;
63665
63666  if ((base = App.Pages).Users == null) {
63667    base.Users = {};
63668  }
63669
63670  App.Pages.Users.PasswordsEdit = (function(superClass) {
63671    extend(PasswordsEdit, superClass);
63672
63673    function PasswordsEdit() {
63674      return PasswordsEdit.__super__.constructor.apply(this, arguments);
63675    }
63676
63677    PasswordsEdit.prototype.initialize = function() {
63678      this.$resetPasswordForm = $('.new_user');
63679      return this.initValidation();
63680    };
63681
63682    PasswordsEdit.prototype.validationSettings = function() {
63683      return {
63684        rules: {
63685          'user[password]': {
63686            required: true,
63687            minlength: 8,
63688            validatePassword: true
63689          },
63690          'user[password_confirmation]': {
63691            required: true,
63692            minlength: 8,
63693            equalTo: '#user_password'
63694          }
63695        }
63696      };
63697    };
63698
63699    PasswordsEdit.prototype.initValidation = function() {
63700      return this.$resetPasswordForm.validate(this.validationSettings());
63701    };
63702
63703    return PasswordsEdit;
63704
63705  })(App.Core.Page);
63706
63707}).call(this);
63708(function() {
63709  var base,
63710    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63711    hasProp = {}.hasOwnProperty;
63712
63713  if ((base = App.Pages).Users == null) {
63714    base.Users = {};
63715  }
63716
63717  App.Pages.Users.PasswordsNew = (function(superClass) {
63718    extend(PasswordsNew, superClass);
63719
63720    function PasswordsNew() {
63721      return PasswordsNew.__super__.constructor.apply(this, arguments);
63722    }
63723
63724    PasswordsNew.prototype.initialize = function() {
63725      this.$forgotYourPasswordForm = $('.new_user');
63726      return this.initValidation();
63727    };
63728
63729    PasswordsNew.prototype.validationSettings = function() {
63730      return {
63731        rules: {
63732          'user[email]': {
63733            required: true,
63734            email: true
63735          }
63736        }
63737      };
63738    };
63739
63740    PasswordsNew.prototype.initValidation = function() {
63741      return this.$forgotYourPasswordForm.validate(this.validationSettings());
63742    };
63743
63744    return PasswordsNew;
63745
63746  })(App.Core.Page);
63747
63748}).call(this);
63749(function() {
63750  var base,
63751    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63752    hasProp = {}.hasOwnProperty;
63753
63754  if ((base = App.Pages).Users == null) {
63755    base.Users = {};
63756  }
63757
63758  App.Pages.Users.SessionsNew = (function(superClass) {
63759    extend(SessionsNew, superClass);
63760
63761    function SessionsNew() {
63762      return SessionsNew.__super__.constructor.apply(this, arguments);
63763    }
63764
63765    SessionsNew.prototype.initialize = function() {
63766      this.$signInForm = $('#new_user');
63767      return this.initValidation();
63768    };
63769
63770    SessionsNew.prototype.validationSettings = function() {
63771      return {
63772        rules: {
63773          'user[login]': {
63774            required: true
63775          },
63776          'user[password]': {
63777            required: true
63778          }
63779        }
63780      };
63781    };
63782
63783    SessionsNew.prototype.initValidation = function() {
63784      return this.$signInForm.validate(this.validationSettings());
63785    };
63786
63787    return SessionsNew;
63788
63789  })(App.Core.Page);
63790
63791}).call(this);
63792(function() {
63793  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63794    hasProp = {}.hasOwnProperty;
63795
63796  App.Pages.Users.SessionsCreate = (function(superClass) {
63797    extend(SessionsCreate, superClass);
63798
63799    function SessionsCreate() {
63800      return SessionsCreate.__super__.constructor.apply(this, arguments);
63801    }
63802
63803    return SessionsCreate;
63804
63805  })(App.Pages.Users.SessionsNew);
63806
63807}).call(this);
63808(function() {
63809  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63810    hasProp = {}.hasOwnProperty;
63811
63812  App.Pages.Users.SessionsFailureHtml = (function(superClass) {
63813    extend(SessionsFailureHtml, superClass);
63814
63815    function SessionsFailureHtml() {
63816      return SessionsFailureHtml.__super__.constructor.apply(this, arguments);
63817    }
63818
63819    return SessionsFailureHtml;
63820
63821  })(App.Pages.Users.SessionsNew);
63822
63823}).call(this);
63824(function() {
63825  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
63826    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63827    hasProp = {}.hasOwnProperty;
63828
63829  App.Pages.UsersShow = (function(superClass) {
63830    extend(UsersShow, superClass);
63831
63832    function UsersShow() {
63833      this.lazyloadImages = bind(this.lazyloadImages, this);
63834      return UsersShow.__super__.constructor.apply(this, arguments);
63835    }
63836
63837    UsersShow.prototype.widgets = ['follow'];
63838
63839    UsersShow.prototype.initialize = function() {
63840      this.initTooltip();
63841      if ($('.tab-content').exists()) {
63842        return this.initTabs();
63843      }
63844    };
63845
63846    UsersShow.prototype.initTooltip = function() {
63847      $('.js-tooltip-with-list').tooltipster({
63848        theme: 'tooltip--light tooltip--popover tooltip--is-list',
63849        animation: 'grow',
63850        interactive: true,
63851        content: $('#hire-me-button-tooltip')
63852      });
63853      return $('.js-tooltip-hire-me-template').tooltipster({
63854        theme: 'tooltip--light tooltip--popover',
63855        animation: 'grow',
63856        interactive: true,
63857        content: 'Send Project invitation to your client.'
63858      });
63859    };
63860
63861    UsersShow.prototype.initTabs = function() {
63862      $('.user-content__tabs').easytabs({
63863        animate: true,
63864        panelActiveClass: 'is-active',
63865        panelContext: $('.user-content__content'),
63866        tabActiveClass: 'is-active',
63867        updateHash: false,
63868        defaultTab: $('.user-content .tabs li').first()
63869      }).bind('easytabs:after', this.lazyloadImages);
63870      return this.lazyloadImages();
63871    };
63872
63873    UsersShow.prototype.lazyloadImages = function() {
63874      return new LazyLoad({
63875        elements_selector: '.js-lazy-img'
63876      });
63877    };
63878
63879    return UsersShow;
63880
63881  })(App.Core.Page);
63882
63883}).call(this);
63884(function() {
63885  App.Pages.UsersThanks = (function() {
63886    function UsersThanks() {}
63887
63888    UsersThanks.prototype.initialize = function() {
63889      return $(window).load(function() {
63890        return App.events.publish('app:element:focus', $('.js-thanks'));
63891      });
63892    };
63893
63894    return UsersThanks;
63895
63896  })();
63897
63898}).call(this);
63899(function() {
63900  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
63901    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
63902    hasProp = {}.hasOwnProperty;
63903
63904  App.Pages.WelcomeIndex = (function(superClass) {
63905    extend(WelcomeIndex, superClass);
63906
63907    function WelcomeIndex() {
63908      this.setSelectedContent = bind(this.setSelectedContent, this);
63909      this.bindEvents = bind(this.bindEvents, this);
63910      return WelcomeIndex.__super__.constructor.apply(this, arguments);
63911    }
63912
63913    WelcomeIndex.prototype.initialize = function() {
63914      this.activeBtnClassName = 'btn-primary';
63915      this.inactiveBtnClassName = 'btn-default';
63916      this.$btnGroup = $('.js-segments-buttons');
63917      this.$allSegmentsInfo = $('.js-segments-info');
63918      return this.bindEvents();
63919    };
63920
63921    WelcomeIndex.prototype.bindEvents = function() {
63922      return this.$btnGroup.find('button').on('click', this.setSelectedContent);
63923    };
63924
63925    WelcomeIndex.prototype.setSelectedContent = function(e) {
63926      this.$el = $(e.currentTarget);
63927      this.selectedValue = this.$el.context.innerText.toLowerCase();
63928      this.$activeSegmentContent = this.$allSegmentsInfo.find(".js-" + this.selectedValue + "-segment-content");
63929      this.$btnGroup.find('button').removeClass(this.activeBtnClassName).addClass(this.inactiveBtnClassName);
63930      this.$el.removeClass(this.inactiveBtnClassName).addClass(this.activeBtnClassName);
63931      this.$allSegmentsInfo.find('.segment-info').addClass('is-hidden');
63932      return this.$activeSegmentContent.removeClass('is-hidden');
63933    };
63934
63935    return WelcomeIndex;
63936
63937  })(App.Core.Page);
63938
63939}).call(this);
63940(function() {
63941
63942
63943}).call(this);
63944(function() {
63945
63946
63947}).call(this);
63948(function() {
63949  App.Backbone = {
63950    Models: {},
63951    Collections: {},
63952    Routers: {},
63953    Views: {
63954      Publisher20: {
63955        Printable: {},
63956        Cg: {}
63957      },
63958      Comments: {},
63959      Modals: {}
63960    },
63961    Configs: {}
63962  };
63963
63964}).call(this);
63965(function() {
63966  this.JST || (this.JST = {});
63967  this.JST["chosen"] = Handlebars.template({"1":function(container,depth0,helpers,partials,data) {
63968    return "multiple=\"multiple\"";
63969},"3":function(container,depth0,helpers,partials,data) {
63970    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
63971        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
63972          return parent[propertyName];
63973        }
63974        return undefined
63975    };
63976
63977  return "        <option value=\""
63978    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"id") || (depth0 != null ? lookupProperty(depth0,"id") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}
63978),{"name":"id","hash":{},"data":data,"loc":{"start":{"line":3,"column":23},"end":{"line":3,"column":29}}}) : helper)))
63979    + "\">"
63980    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"text") || (depth0 != null ? lookupProperty(depth0,"text") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"text","hash":{},"data":data,"loc":{"start":{"line":3,"column":31},"end":{"line":3,"column":39}}}) : helper)))
63981    + "</option>\n";
63982},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
63983    var stack1, helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
63984        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
63985          return parent[propertyName];
63986        }
63987        return undefined
63988    };
63989
63990  return "<select name=\""
63991    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"name","hash":{},"data":data,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":22}}}) : helper)))
63992    + "\" id=\""
63993    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"id") || (depth0 != null ? lookupProperty(depth0,"id") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"id","hash":{},"data":data,"loc":{"start":{"line":1,"column":28},"end":{"line":1,"column":34}}}) : helper)))
63994    + "\" data-placeholder=\""
63995    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"placeholder") || (depth0 != null ? lookupProperty(depth0,"placeholder") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"placeholder","hash":{},"data":data,"loc":{"start":{"line":1,"column":54},"end":{"line":1,"column":69}}}) : helper)))
63996    + "\" "
63997    + ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"multiple") : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":1,"column":71},"end":{"line":1,"column":114}}})) != null ? stack1 : "")
63998    + ">\n"
63999    + ((stack1 = lookupProperty(helpers,"each").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"selectOptions") : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":2,"column":4},"end":{"line":4,"column":13}}})) != null ? stack1 : "")
64000    + "</select>";
64001},"useData":true});
64002  return this.JST["chosen"];
64003}).call(this);
64004(function() {
64005  this.JST || (this.JST = {});
64006  this.JST["comments/form"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64007    var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
64008        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64009          return parent[propertyName];
64010        }
64011        return undefined
64012    };
64013
64014  return "<form action=\"\" class=\"comment-form\" method=\"post\"><input name=\"utf8\" type=\"hidden\" /><input name=\"authenticity_token\" type=\"hidden\" /><div class=\"comment-form__author\"><a href=\""
64015    + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? lookupProperty(depth0,"author") : depth0)) != null ? lookupProperty(stack1,"url") : stack1), depth0))
64016    + "\" title=\""
64017    + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? lookupProperty(depth0,"author") : depth0)) != null ? lookupProperty(stack1,"username") : stack1), depth0))
64018    + "\"><img alt=\""
64019    + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? lookupProperty(depth0,"author") : depth0)) != null ? lookupProperty(stack1,"username") : stack1), depth0))
64020    + "\" class=\"avatar\" src=\""
64021    + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? lookupProperty(depth0,"author") : depth0)) != null ? lookupProperty(stack1,"avatar_url") : stack1), depth0))
64022    + "\" /></a></div><div class=\"comment-form__content\"><textarea class=\"field field--compact-text\" name=\"content\" placeholder=\"Your comment\" required=\"\"></textarea><button class=\"js-auth-control cgt-button cgt-button--primary cgt-button--small js-comment-submit\" type=\"submit\"><span>Post comment</span></button></div></form>";
64023},"useData":true});
64024  return this.JST["comments/form"];
64025}).call(this);
64026(function() {
64027  this.JST || (this.JST = {});
64028  this.JST["cookies_policy/disabled_cookies_modal"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64029    var lookupProperty = container.lookupProperty || function(parent, propertyName) {
64030        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64031          return parent[propertyName];
64032        }
64033        return undefined
64034    };
64035
64036  return "<div class=\"overlay-modal--background overlay-modal--background_after-open\">\n  <div class=\"overlay-modal--container_after-open overlay-modal-container--xs\" tabindex=\"-1\" role=\"dialog\" aria-label=\"Modal\">\n    <div class=\"overlay-modal\">
64036\n      <button class=\"btn btn-transparent overlay-modal--close-inner js-close-modal\"><i class=\"svg-inline--fa fal fa-times is-grey-dark\" data-fa-transform=\"\"></i></button>\n      <div class=\"overlay-modal--content overlay-modal--cookies\">\n        <div class=\"l-inner-large\">\n          <h2 class=\"modal-title\">"
64037    + container.escapeExpression((lookupProperty(helpers,"I18n")||(depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"disabled_cookies_modal.title",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":7,"column":34},"end":{"line":7,"column":73}}}))
64038    + "</h2>\n          <p class=\"text-default\">\n            "
64039    + container.escapeExpression((lookupProperty(helpers,"I18n")||(depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"disabled_cookies_modal.text_1",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":9,"column":12},"end":{"line":9,"column":52}}}))
64040    + "\n          </p>\n          <p class=\"text-default\">\n            "
64041    + container.escapeExpression((lookupProperty(helpers,"I18n")||(depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"disabled_cookies_modal.text_2",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":12,"column":12},"end":{"line":12,"column":52}}}))
64042    + "\n          </p>\n          <div class=\"footer u-text-right u-mt10\">\n            <button type=\"button\" class=\"btn btn-fat btn-primary js-close-modal\">\n              "
64043    + container.escapeExpression((lookupProperty(helpers,"I18n")||(depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"disabled_cookies_modal.button",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":16,"column":14},"end":{"line":16,"column":54}}}))
64044    + "\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>";
64045},"useData":true});
64046  return this.JST["cookies_policy/disabled_cookies_modal"];
64047}).call(this);
64048(function() {
64049  this.JST || (this.JST = {});
64050  this.JST["cookies_policy/notification_bar"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64051    var lookupProperty = container.lookupProperty || function(parent, propertyName) {
64052        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64053          return parent[propertyName];
64054        }
64055        return undefined
64056    };
64057
64058  return "<div class=\"notice\">\n  <div class=\"notice-content\">\n    <span class=\"close-notice js-cookies-policy-close\">\n      <i class=\"fal fa-times is-not-spaced\"/>\n    </span>\n    "
64059    + container.escapeExpression((lookupProperty(helpers,"I18n")||(depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"layout.cookies_policy.text",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":6,"column":4},"end":{"line":6,"column":41}}}))
64060    + "\n    <a class=\"link--darker\" target=\"_blank\" href=\"/pages/cookie-notice\">"
64061    + container.escapeExpression((lookupProperty(helpers,"I18n")||(depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"layout.cookies_policy.learn_more",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":7,"column":72},"end":{"line":7,"column":115}}}))
64062    + "</a>\n    <button class=\"btn btn-primary btn-sm js-cookies-policy-accept\">"
64063    + container.escapeExpression((lookupProperty(helpers,"I18n")||(depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"layout.cookies_policy.accept",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":8,"column":68},"end":{"line":8,"column":107}}}))
64064    + "</button>\n  </div>\n</div>";
64065},"useData":true});
64066  return this.JST["cookies_policy/notification_bar"];
64067}).call(this);
64068(function() {
64069  this.JST || (this.JST = {});
64070  this.JST["modals/adult_content_agreement_modal"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64071    return "<div class=\"overlay-modal--background overlay-modal--background_after-open\">\n  <div class=\" overlay-modal--container_after-open overlay-modal-container--xs\" tabindex=\"-1\" role=\"dialog\" aria-label=\"Modal\">\n    <div class=\"overlay-modal\">
64071\n      <button class=\"btn btn-transparent overlay-modal--close-inner js-close-modal\"><i class=\"svg-inline--fa fal fa-times is-grey-dark\" data-fa-transform=\"\"></i></button>\n      <div class=\"overlay-modal--content overlay-modal--adult\">\n        <div class=\"l-inner-large\">\n          <h2 class=\"modal-title\">Adult content warning</h2>\n          <p class=\"modal-description\">You're about to see adult content. Please confirm your age.</p>\n          <p class=\"modal-note\"><span>Note:</span> we take no responsibility for any content if you decide to proceed as a minor.</p>\n          <div class=\"footer u-text-right u-mt10\">\n            <button type=\"button\" class=\"btn btn-fat btn-outline-default u-ml10 js-set-adult-cookie\">I'm an adult, continue</button>\n            <button type=\"button\" class=\"btn btn-fat btn-primary js-close-modal\">Cancel</button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>";
64072},"useData":true});
64073  return this.JST["modals/adult_content_agreement_modal"];
64074}).call(this);
64075(function() {
64076  this.JST || (this.JST = {});
64077  this.JST["modals/custom_item_request_modal"] = Handlebars.template({"1":function(container,depth0,helpers,partials,data) {
64078    var stack1, helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
64079        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64080          return parent[propertyName];
64081        }
64082        return undefined
64083    };
64084
64085  return "          <div class=\"form-group row\">\n            <label class=\"col-12 col-md-4 col-form-label col-form-label-sm\">Formats</label>\n            <div class=\"col-12 col-md-8\">\n              <input name=\"custom_model_request[formats]\" type=\"hidden\" value=\""
64086    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"formats") || (depth0 != null ? lookupProperty(depth0,"formats") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"formats","hash":{},"data":data,"loc":{"start":{"line":37,"column":79},"end":{"line":37,"column":90}}}) : helper)))
64087    + "\">\n"
64088    + ((stack1 = lookupProperty(helpers,"each").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"formats") : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":38,"column":14},"end":{"line":40,"column":23}}})) != null ? stack1 : "")
64089    + "            </div>\n          </div>\n";
64090},"2":function(container,depth0,helpers,partials,data) {
64091    return "                <div class=\"active-filters__item not-removable\">"
64092    + container.escapeExpression(container.lambda(depth0, depth0))
64093    + "</div>\n";
64094},"4":function(container,depth0,helpers,partials,data) {
64095    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
64096        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64097          return parent[propertyName];
64098        }
64099        return undefined
64100    };
64101
64102  return "          <div class=\"js-polygons-container\">\n            <div class=\"form-group row js-polygons-row\">\n              <label class=\"col-12 col-md-4 col-form-label col-form-label-sm\">Poly count</label>\n              <div class=\"col-12 col-md-8\">\n                <input name=\"custom_model_request[poly_count]\" type=\"hidden\" value=\""
64103    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"polygons") || (depth0 != null ? lookupProperty(depth0,"polygons") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"polygons","hash":{},"data":data,"loc":{"start":{"line":50,"column":84},"end":{"line":50,"column":96}}}) : helper)))
64104    + "\">\n                <div class=\"active-filters__item not-removable\">"
64105    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"polygons") || (depth0 != null ? lookupProperty(depth0,"polygons") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}
64105),{"name":"polygons","hash":{},"data":data,"loc":{"start":{"line":51,"column":64},"end":{"line":51,"column":76}}}) : helper)))
64106    + "</div>\n              </div>\n            </div>\n          </div>\n";
64107},"6":function(container,depth0,helpers,partials,data) {
64108    var stack1, helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
64109        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64110          return parent[propertyName];
64111        }
64112        return undefined
64113    };
64114
64115  return "          <div class=\"form-group row\">\n            <label class=\"col-12 col-md-4 col-form-label col-form-label-sm\">License</label>\n            <div class=\"col-12 col-md-8\">\n              <input name=\"custom_model_request[license]\" type=\"hidden\" value=\""
64116    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"licenses") || (depth0 != null ? lookupProperty(depth0,"licenses") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"licenses","hash":{},"data":data,"loc":{"start":{"line":61,"column":79},"end":{"line":61,"column":91}}}) : helper)))
64117    + "\">\n"
64118    + ((stack1 = lookupProperty(helpers,"each").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"licenses") : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":62,"column":14},"end":{"line":64,"column":23}}})) != null ? stack1 : "")
64119    + "            </div>\n          </div>\n";
64120},"8":function(container,depth0,helpers,partials,data) {
64121    return "          <div class=\"form-group row\">\n            <label class=\"col-12 col-md-4 col-form-label col-form-label-sm\" for=\"email\">Email</label>\n            <div class=\"col-12 col-md-8\">\n              <input type=\"email\" id=\"email\" class=\"form-control form-control-sm\" name=\"custom_model_request[email]\">\n            </div>\n          </div>\n";
64122},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64123    var stack1, helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
64124        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64125          return parent[propertyName];
64126        }
64127        return undefined
64128    };
64129
64130  return "<div id=\"modal-request-custom-item\" class=\"modal modal--full modal--light modal--request-custom-item\">\n  <button aria-hidden=\"true\" class=\"btn btn--transparent modal--close js-modal-close\" data-dismiss=\"modal\" type=\"button\">\n    <i class=\"far fa-times fa-lg is-grey is-not-spaced\"></i>\n  </button>\n\n  <div class=\"modal-content\">\n    <div class=\"modal-light__header\">\n      Looking for <strong>"
64131    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"keywords") || (depth0 != null ? lookupProperty(depth0,"keywords") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"keywords","hash":{},"data":data,"loc":{"start":{"line":8,"column":26},"end":{"line":8,"column":38}}}) : helper)))
64132    + "</strong> 3D model? Let our team help you find it!\n    </div>\n\n    <div class=\"modal-light__body\">\n      <div class=\"form-heading\">\n        Fill in the form below, and we'll get back to you. Please provide as many details as possible.\n      </div>\n\n      <form id=\"request-custom-item\" action=\"/custom-model-requests\" method=\"post\" data-remote=true class=\"request-form js-request-form\">\n        <input type=\"hidden\" name=\"authenticity_token\" value=\""
64133    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"authenticityToken") || (depth0 != null ? lookupProperty(depth0,"authenticityToken") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}
64133),{"name":"authenticityToken","hash":{},"data":data,"loc":{"start":{"line":17,"column":62},"end":{"line":17,"column":83}}}) : helper)))
64134    + "\">\n        <input type=\"hidden\" name=\"custom_model_request[search_query]\" value=\""
64135    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"keywords") || (depth0 != null ? lookupProperty(depth0,"keywords") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"keywords","hash":{},"data":data,"loc":{"start":{"line":18,"column":78},"end":{"line":18,"column":90}}}) : helper)))
64136    + "\">\n        <div class=\"form-group row\">\n          <label class=\"col-12 col-md-4 col-form-label col-form-label-sm\">Price</label>\n          <div class=\"col-12 col-md-8\">\n            <input class=\"js-price-input\" name=\"custom_model_request[price]\" type=\"hidden\" value=\""
64137    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"price") || (depth0 != null ? lookupProperty(depth0,"price") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"price","hash":{},"data":data,"loc":{"start":{"line":22,"column":98},"end":{"line":22,"column":107}}}) : helper)))
64138    + "\">\n            <div id=\"price-range-request\" class=\"price-range price-range--longer\"></div>\n            <div class=\"active-filters__item\">\n              <span class=\"js-price-filter\">"
64139    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"price") || (depth0 != null ? lookupProperty(depth0,"price") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"price","hash":{},"data":data,"loc":{"start":{"line":25,"column":44},"end":{"line":25,"column":53}}}) : helper)))
64140    + "</span>\n              <span class=\"active-filters__item-remove js-reset-price\">\n                <i class=\"far fa-times is-not-spaced\"></i>\n              </span>\n            </div>\n          </div>\n        </div>\n\n"
64141    + ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? lookupProperty(depth0,"formats") : depth0)) != null ? lookupProperty(stack1,"length") : stack1),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":33,"column":8},"end":{"line":43,"column":15}}})) != null ? stack1 : "")
64142    + "\n"
64143    + ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? lookupProperty(depth0,"polygons") : depth0)) != null ? lookupProperty(stack1,"length") : stack1),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":45,"column":8},"end":{"line":55,"column":15}}})) != null ? stack1 : "")
64144    + "\n"
64145    + ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? lookupProperty(depth0,"licenses") : depth0)) != null ? lookupProperty(stack1,"length") : stack1),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":57,"column":8},"end":{"line":67,"column":15}}})) != null ? stack1 : "")
64146    + "\n        <div class=\"form-group row\">\n          <label class=\"col-12 col-md-4 col-form-label col-form-label-sm\">Additional specifications</label>\n          <div class=\"col-12 col-md-8 js-checkboxes-container\">\n            <div class=\"checkbox-block\">\n              <input type=\"checkbox\" name=\"custom_model_request[selected_checkboxes[print_ready]]\" id=\"print_ready_request\" value=true class=\"checkbox is-spaced\">\n              <label class=\"col-form-label col-form-label-sm\" for=\"print_ready_request\">3D Print</label>\n            </div>\n\n            <div class=\"checkbox-block\">\n              <input type=\"checkbox\" name=\"custom_model_request[selected_checkboxes[low_poly]]\" id=\"low_poly_request\" value=true class=\"checkbox is-spaced\">\n              <label class=\"col-form-label col-form-label-sm\" for=\"low_poly_request\">Low-poly</label>\n            </div>\n\n            <div class=\"checkbox-block\">\n              <input type=\"checkbox\" name=\"custom_model_request[selected_checkboxes[animated]]\" id=\"animated_request\" value=true class=\"checkbox is-spaced\">\n              <label class=\"col-form-label col-form-label-sm\" for=\"animated_request\">Animated</label>\n            </div>\n\n            <div class=\"checkbox-block\">\n              <input type=\"checkbox\" name=\"custom_model_request[selected_checkboxes[pbr]]\" id=\"pbr_request\" value=true class=\"checkbox is-spaced\">\n              <label class=\"col-form-label col-form-label-sm\" for=\"pbr_request\">PBR</label>\n            </div>\n\n            <div class=\"checkbox-block\">\n              <input type=\"checkbox\" name=\"custom_model_request[selected_checkboxes[rigged]]\" id=\"rigged_request\" value=true class=\"checkbox is-spaced\">\n              <label class=\"col-form-label col-form-label-sm\" for=\"rigged_request\">Rigged</label>\n            </div>\n\n            <div class=\"checkbox-block\">\n              <input type=\"checkbox\" name=\"custom_model_request[selected_checkboxes[
64146collection]]\" id=\"collection_request\" value=true class=\"checkbox is-spaced\">\n              <label class=\"col-form-label col-form-label-sm\" for=\"collection_request\">Collection</label>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"form-group row\">\n          <label class=\"col-12 col-md-4 col-form-label col-form-label-sm\" for=\"details\">Details</label>\n          <div class=\"col-12 col-md-8\">\n            <textarea id=\"details\" class=\"form-control form-control-sm\" name=\"custom_model_request[details]\" rows=\"5\" />\n          </div>\n        </div>\n\n        <div class=\"form-group row\">\n          <label class=\"col-12 col-md-4 col-form-label col-form-label-sm\" for=\"deadline\">Latest date you need the model on</label>\n          <div class=\"col-12 col-md-8\">\n            <input id=\"deadline\" class=\"form-control form-control-sm js-deadline-field\" name=\"custom_model_request[deadline]\">\n          </div>\n        </div>\n\n"
64147    + ((stack1 = lookupProperty(helpers,"unless").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"signedIn") : depth0),{"name":"unless","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":118,"column":8},"end":{"line":125,"column":19}}})) != null ? stack1 : "")
64148    + "\n        <div class=\"row\">\n          <div class=\"col-12 col-md-3 offset-md-4\">\n            <button class=\"btn btn-primary btn-block\" type=\"submit\">Submit</button>\n          </div>\n        </div>\n      </form>\n    </div>\n\n    <div class=\"modal-light__success-screen js-success-screen is-hidden\">\n      <p>Your request was submitted successfully! Our team will get in touch with you shortly.</p>\n    </div>\n  </div>\n</div>";
64149},"useData":true});
64150  return this.JST["modals/custom_item_request_modal"];
64151}).call(this);
64152(function() {
64153  this.JST || (this.JST = {});
64154  this.JST["payment_agreement/notification_bar"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64155    return "<div class=\"notice is-secondary-color\">\n    In order to get your next payment, please update required information in CGTrader agreement\n    <a href=\"/profile/payment_agreements\" class=\"btn btn-white btn-sm\">Here</a>\n</div>";
64156},"useData":true});
64157  return this.JST["payment_agreement/notification_bar"];
64158}).call(this);
64159(function() {
64160  this.JST || (this.JST = {});
64161  this.JST["search_suggestion"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64162    var stack1, helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
64163        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64164          return parent[propertyName];
64165        }
64166        return undefined
64167    };
64168
64169  return "<span class=\"site-search-suggestions__col-suggestion\">\n  <a href=\"/search?"
64170    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"params") || (depth0 != null ? lookupProperty(depth0,"params") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"params","hash":{},"data":data,"loc":{"start":{"line":2,"column":19},"end":{"line":2,"column":29}}}) : helper)))
64171    + "&suggested=1\" class=\"js-suggestion-keyword\" data-keyword=\""
64172    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"suggestion") || (depth0 != null ? lookupProperty(depth0,"suggestion") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"suggestion","hash":{},"data":data,"loc":{"start":{"line":2,"column":87},"end":{"line":2,"column":101}}}) : helper)))
64173    + "\" id=\"suggestion_"
64174    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"suggestion_id") || (depth0 != null ? lookupProperty(depth0,"suggestion_id") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}
64174),{"name":"suggestion_id","hash":{},"data":data,"loc":{"start":{"line":2,"column":118},"end":{"line":2,"column":135}}}) : helper)))
64175    + "\">\n    <img src=\""
64176    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"thumbnailUrl") || (depth0 != null ? lookupProperty(depth0,"thumbnailUrl") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"thumbnailUrl","hash":{},"data":data,"loc":{"start":{"line":3,"column":14},"end":{"line":3,"column":30}}}) : helper)))
64177    + "\" alt=\"\">\n      "
64178    + ((stack1 = ((helper = (helper = lookupProperty(helpers,"suggestionHtml") || (depth0 != null ? lookupProperty(depth0,"suggestionHtml") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"suggestionHtml","hash":{},"data":data,"loc":{"start":{"line":4,"column":6},"end":{"line":4,"column":26}}}) : helper))) != null ? stack1 : "")
64179    + "\n  </a>\n</span>";
64180},"useData":true});
64181  return this.JST["search_suggestion"];
64182}).call(this);
64183(function() {
64184  this.JST || (this.JST = {});
64185  this.JST["support/notification_bar"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64186    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
64187        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64188          return parent[propertyName];
64189        }
64190        return undefined
64191    };
64192
64193  return "<div class=\"site-admin-control-warning\">\n    <div class=\"container-fixed\">\n        <div class=\"u-float-left\">\n            <i class=\"fas fa-exclamation-triangle\"></i>\n            Under <a href=\"/"
64194    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"username") || (depth0 != null ? lookupProperty(depth0,"username") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"username","hash":{},"data":data,"loc":{"start":{"line":5,"column":28},"end":{"line":5,"column":40}}}) : helper)))
64195    + "\">"
64196    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"username") || (depth0 != null ? lookupProperty(depth0,"username") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"username","hash":{},"data":data,"loc":{"start":{"line":5,"column":42},"end":{"line":5,"column":54}}}) : helper)))
64197    + "</a> support.\n        </div>\n        <div class=\"u-float-right\">\n            <a href=\"/stop_impersonating\">\n                <i class=\"fas fa-unlock-alt fa-lg\"></i> End support session\n            </a>\n        </div>\n    </div>\n</div>";
64198},"useData":true});
64199  return this.JST["support/notification_bar"];
64200}).call(this);
64201(function() {
64202  this.JST || (this.JST = {});
64203  this.JST["tip_for_designer/notification_bar"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64204    var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
64205        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64206          return parent[propertyName];
64207        }
64208        return undefined
64209    };
64210
64211  return "<div class=\"notice\">\n    <div class=\"notice-content\">\n        <span class=\"close-notice js-tip-close\">\n            <i class=\"fal fa-times is-not-spaced\" />\n        </span>\n        "
64212    + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"body") || (depth0 != null ? lookupProperty(depth0,"body") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"body","hash":{},"data":data,"loc":{"start":{"line":6,"column":8},"end":{"line":6,"column":16}}}) : helper)))
64213    + "\n    </div>\n</div>";
64214},"useData":true});
64215  return this.JST["tip_for_designer/notification_bar"];
64216}).call(this);
64217(function() {
64218  this.JST || (this.JST = {});
64219  this.JST["translations_notice/notification_bar"] = Handlebars.template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
64220    var lookupProperty = container.lookupProperty || function(parent, propertyName) {
64221        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
64222          return parent[propertyName];
64223        }
64224        return undefined
64225    };
64226
64227  return "<div class=\"notice notice-float notice-light notice-translation\">\n  <span class=\"close-notice\">\n    <i class=\"fal fa-times is-not-spaced\"/>\n  </span>\n  <div class=\"notice-content\">\n    "
64228    + container.escapeExpression((lookupProperty(helpers,"I18n")||(
64228depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"layout.translations_notice.text",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":6,"column":4},"end":{"line":6,"column":46}}}))
64229    + "\n    <a href=\"\">"
64230    + container.escapeExpression((lookupProperty(helpers,"I18n")||(depth0 && lookupProperty(depth0,"I18n"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"layout.translations_notice.cta",{"name":"I18n","hash":{},"data":data,"loc":{"start":{"line":7,"column":15},"end":{"line":7,"column":56}}}))
64231    + " 🇺🇸</a>\n  </div>\n</div>";
64232},"useData":true});
64233  return this.JST["translations_notice/notification_bar"];
64234}).call(this);
64235(function() {
64236  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64237    hasProp = {}.hasOwnProperty;
64238
64239  App.Backbone.Views.Carousel = (function(superClass) {
64240    extend(Carousel, superClass);
64241
64242    function Carousel() {
64243      return Carousel.__super__.constructor.apply(this, arguments);
64244    }
64245
64246    Carousel.prototype.events = {
64247      'click .js-carousel-trigger': 'handleCarouselClick'
64248    };
64249
64250    Carousel.prototype.states = {
64251      active: 'is-active',
64252      hidden: 'is-hidden',
64253      loaded: 'is-loaded'
64254    };
64255
64256    Carousel.prototype.initialize = function(options) {
64257      this.el = options.el;
64258      this.$trigger = options.$trigger;
64259      this.$tooltipster = options.$tooltipster;
64260      this.current = 0;
64261      _.bindAll(this, 'reset');
64262      this.render();
64263      this.cacheUI();
64264      this.bindTooltipsterReposition();
64265      this.activate(this.current);
64266      this.subscribe();
64267      return this;
64268    };
64269
64270    Carousel.prototype.subscribe = function() {
64271      if (!this.$trigger) {
64272        return;
64273      }
64274      return this.$trigger.on('click', _.bind(this.handleCarouselClick, this));
64275    };
64276
64277    Carousel.prototype.unsubscribe = function() {
64278      if (!this.$trigger) {
64279        return;
64280      }
64281      return this.$trigger.off('click', _.bind(this.handleCarouselClick, this));
64282    };
64283
64284    Carousel.prototype.render = function() {
64285      var $container, figcaption, images, itemTitle, items, loaderClasses;
64286      $container = this.$el.find('.js-carousel-items');
64287      itemTitle = this.$el.find('.content-box__content').data('name');
64288      figcaption = "<figcaption class='content-box__title'>" + itemTitle + "</figcaption>";
64289      items = $container.data('images');
64290      this.$el.find('.content-box__price').removeClass('title-is-hidden').addClass('discount-is-visible');
64291      if (!(items && items.length)) {
64292        return this;
64293      }
64294      loaderClasses = 'is-loading is-loading-image js-carousel-slide';
64295      images = _.map(items, function(item) {
64296        return $('<img>').attr({
64297          src: '',
64298          alt: '',
64299          'data-src': item
64300        }).addClass(loaderClasses)[0].outerHTML;
64301      });
64302      $container.html(images.join());
64303      $container.append(figcaption);
64304      return this;
64305    };
64306
64307    Carousel.prototype.cacheUI = function() {
64308      return this.$items = this.$el.find('.js-carousel-slide');
64309    };
64310
64311    Carousel.prototype.bindTooltipsterReposition = function() {
64312      return _.forEach(this.$items, (function(_this) {
64313        return function(item) {
64314          return $(item).load(function() {
64315            if (_this.$tooltipster.hasClass('tooltipstered')) {
64316              return _this.$tooltipster.tooltipster('reposition');
64317            }
64318          });
64319        };
64320      })(this));
64321    };
64322
64323    Carousel.prototype.findNext = function(offset, direction) {
64324      var movement, next;
64325      movement = direction === 'prev' ? -1 : 1;
64326      next = offset + movement;
64327      if (next < 0) {
64328        next = this.$items.length - 1;
64329      } else if (next >= this.$items.length) {
64330        next = 0;
64331      }
64332      return next;
64333    };
64334
64335    Carousel.prototype.move = function(direction) {
64336      var next;
64337      next = this.findNext(this.current, direction);
64338      return this.activate(next, direction);
64339    };
64340
64341    Carousel.prototype.activate = function(item, direction) {
64342      var after_next, next, previous;
64343      this.$items.removeClass(this.states.active).addClass(this.states.hidden);
64344      this.$items.eq(item).removeClass(this.states.hidden).addClass(this.states.active);
64345      this.current = item;
64346      if (direction == null) {
64347        return;
64348      }
64349      next = this.findNext(this.current, direction);
64350      this.preload(next);
64351      after_next = this.findNext(next, direction);
64352      this.preload(after_next);
64353      previous = this.findNext(this.current, this.oppositeDirection(direction));
64354      return this.preload(previous);
64355    };
64356
64357    Carousel.prototype.preload = function(item) {
64358      var $item, img, imgLoad;
64359      $item = this.$items.eq(item);
64360      if ($item.hasClass(this.states.loaded)) {
64361        return;
64362      }
64363      img = new Image();
64364      img.src = $item.data('src');
64365      imgLoad = imagesLoaded(img);
64366      return imgLoad.on('done', (function(_this) {
64367        return function() {
64368          return $item.attr('src', img.src).removeClass('is-loading is-loading-image').addClass(_this.states.loaded);
64369        };
64370      })(this));
64371    };
64372
64373    Carousel.prototype.remove = function() {
64374      this.stopListening();
64375      this.unsubscribe();
64376      return this.$el.remove();
64377    };
64378
64379    Carousel.prototype.reset = function(origin, callback) {
64380      this.preload(0);
64381      this.activate(0, 'next');
64382      if (callback != null) {
64383        return callback();
64384      }
64385    };
64386
64387    Carousel.prototype.handleCarouselClick = function(event) {
64388      var direction;
64389      direction = $(event.currentTarget).data('direction');
64390      return this.move(direction);
64391    };
64392
64393    Carousel.prototype.oppositeDirection = function(direction) {
64394      if (direction === 'prev') {
64395        return 'next';
64396      } else {
64397        return 'prev';
64398      }
64399    };
64400
64401    return Carousel;
64402
64403  })(Backbone.View);
64404
64405}).call(this);
64406(function() {
64407  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64408    hasProp = {}.hasOwnProperty;
64409
64410  App.Backbone.Views.ChosenView = (function(superClass) {
64411    extend(ChosenView, superClass);
64412
64413    ChosenView.prototype.template = JST["chosen"];
64414
64415    function ChosenView(options) {
64416      this.viewOptions = {
64417        id: options.id,
64418        placeholder: options.placeholder,
64419        onChange: options.onChange,
64420        required: options.required,
64421        multiple: options.multiple,
64422        name: (options.name != null ? options.name : options.id),
64423        allow_single_deselect: ((options.multiple == null) && (options.allow_single_deselect != null) ? true : false)
64424      };
64425      ChosenView.__super__.constructor.apply(this, arguments);
64426    }
64427
64428    ChosenView.prototype.render = function(options) {
64429      var ref, select;
64430      this.viewOptions.value = options.value;
64431      this.viewOptions.selectOptions = options.collection;
64432      $(this.el).find('select').chosen('destroy');
64433      $(this.el).html(this.template(this.viewOptions));
64434      select = $(this.el).find('select');
64435      select.val(this.viewOptions.value);
64436      if (this.viewOptions.required) {
64437        select.attr('required', 'required');
64438      }
64439      select.chosen({
64440        width: (ref = this.viewOptions.width) != null ? ref : '100px',
64441        value: this.viewOptions.value,
64442        disable_search_threshold: 10,
64443        placeholder: this.viewOptions.placeholder,
64444        allow_single_deselect: this.viewOptions.allow_single_deselect
64445      }).change(this.viewOptions.onChange);
64446      return this;
64447    };
64448
64449    return ChosenView;
64450
64451  })(Backbone.View);
64452
64453}).call(this);
64454(function() {
64455  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
64456    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64457    hasProp = {}.hasOwnProperty;
64458
64459  App.Backbone.Views.Comments.Form = (function(superClass) {
64460    extend(Form, superClass);
64461
64462    function Form() {
64463      this.onSubmit = bind(this.onSubmit, this);
64464      return Form.__super__.constructor.apply(this, arguments);
64465    }
64466
64467    Form.prototype.template = JST["comments/form"];
64468
64469    Form.prototype.attributes = {
64470      "class": 'comment-form-container'
64471    };
64472
64473    Form.prototype.events = {
64474      'submit': 'onThrottledSubmit'
64475    };
64476
64477    Form.prototype.initialize = function() {
64478      return this.onThrottledSubmit = _.throttle(this.onSubmit, 1500);
64479    };
64480
64481    Form.prototype.render = function() {
64482      var renderData;
64483      renderData = this.model.toJSON();
64484      renderData.author = {
64485        username: App.config.username,
64486        avatar_url: App.config.userAvatarUrl || '/assets/avatar/small_avatar.png'
64487      };
64488      $(this.el).html(this.template(renderData));
64489      Backbone.Syphon.deserialize(this, this.model.toJSON());
64490      return this;
64491    };
64492
64493    Form.prototype.onSubmit = function(e) {
64494      var $button;
64495      e.preventDefault();
64496      e.stopPropagation();
64497      $button = $(this.el).find('.js-comment-submit');
64498      if (!App.config.userSignedIn) {
64499        return;
64500      }
64501      if ($button.hasClass('is-disabled')) {
64502        return;
64503      }
64504      $button.attr('disabled', true).addClass('is-disabled');
64505      this.model.set(Backbone.Syphon.serialize(this));
64506      return this.model.save({}, {
64507        success: (function(_this) {
64508          return function(model, response) {
64509            $button.removeAttr('disabled').removeClass('is-disabled');
64510            App.events.publish('app:comment:posted');
64511            _this.model.set({
64512              content: ''
64513            });
64514            Backbone.Syphon.deserialize(_this, _this.model.toJSON());
64515            $(_this.el).fadeOut();
64516            return location.reload();
64517          };
64518        })(this),
64519        error: function(model, response) {
64520          $button.removeAttr('disabled').removeClass('is-disabled');
64521          return App.events.publish('app:notification:flash:error', response.responseJSON['errors'][0]['detail']);
64522        }
64523      });
64524    };
64525
64526    return Form;
64527
64528  })(Backbone.View);
64529
64530}).call(this);
64531(function() {
64532  var ref,
64533    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64534    hasProp = {}.hasOwnProperty;
64535
64536  if ((ref = App.Backbone) != null) {
64537    ref.Views || (ref.Views = {});
64538  }
64539
64540  App.Backbone.Views.SearchSuggestionViewV2 = (function(superClass) {
64541    extend(SearchSuggestionViewV2, superClass);
64542
64543    function SearchSuggestionViewV2() {
64544      return SearchSuggestionViewV2.__super__.constructor.apply(this, arguments);
64545    }
64546
64547    SearchSuggestionViewV2.prototype.template = JST['search_suggestion'];
64548
64549    SearchSuggestionViewV2.prototype.tagName = 'li';
64550
64551    SearchSuggestionViewV2.prototype.events = {
64552      'mouseenter .js-suggestion-keyword': 'onKeywordHover',
64553      'mouseleave .js-suggestion-keyword': 'onKeywordHoverOut',
64554      'click .js-suggestion-keyword': 'onSearchSuggestionClick'
64555    };
64556
64557    SearchSuggestionViewV2.prototype.initialize = function(options) {
64558      this.options = options;
64559    };
64560
64561    SearchSuggestionViewV2.prototype.serialize = function() {
64562      return {
64563        suggestion: this.options.data.suggestion,
64564        suggestionHtml: this.options.data.suggestionHtml,
64565        suggestion_id: this.options.data.suggestion.replace(/[^a-z0-9]/gi, '_'),
64566        total: this.options.data.total,
64567        thumbnailUrl: this.options.data.thumbnailUrl,
64568        params: $.param({
64569          keywords: this.options.data.suggestion
64570        })
64571      };
64572    };
64573
64574    SearchSuggestionViewV2.prototype.render = function() {
64575      this.$el.html(this.template(this.serialize()));
64576      return this;
64577    };
64578
64579    SearchSuggestionViewV2.prototype.onSearchSuggestionClick = function(event) {
64580      var $link;
64581      event.preventDefault();
64582      $link = $(event.currentTarget);
64583      if (App.Backbone.Routers.ItemsSearch.instance) {
64584        App.Backbone.Routers.ItemsSearch.instance.applyFilter('keywords', $link.data('keyword'));
64585        return App.events.publish('browser:filters:changed');
64586      } else {
64587        return window.location = $link.attr('href');
64588      }
64589    };
64590
64591    return SearchSuggestionViewV2;
64592
64593  })(Backbone.View);
64594
64595}).call(this);
64596(function() {
64597  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64598    hasProp = {}.hasOwnProperty;
64599
64600  App.Backbone.Models.Comment = (function(superClass) {
64601    extend(Comment, superClass);
64602
64603    function Comment() {
64604      return Comment.__super__.constructor.apply(this, arguments);
64605    }
64606
64607    Comment.prototype.urlRoot = '/api/internal/comments';
64608
64609    return Comment;
64610
64611  })(Backbone.Model);
64612
64613  App.Backbone.Collections.CommentsCollection = (function(superClass) {
64614    extend(CommentsCollection, superClass);
64615
64616    function CommentsCollection() {
64617      return CommentsCollection.__super__.constructor.apply(this, arguments);
64618    }
64619
64620    CommentsCollection.prototype.url = '/api/internal/comments';
64621
64622    CommentsCollection.prototype.model = App.Backbone.Models.Comment;
64623
64624    return CommentsCollection;
64625
64626  })(Backbone.Collection);
64627
64628}).call(this);
64629(function() {
64630  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64631    hasProp = {}.hasOwnProperty;
64632
64633  App.Backbone.Models.Conversation = (function(superClass) {
64634    extend(Conversation, superClass);
64635
64636    function Conversation() {
64637      return Conversation.__super__.constructor.apply(this, arguments);
64638    }
64639
64640    Conversation.prototype.urlRoot = '/api/internal/conversations';
64641
64642    return Conversation;
64643
64644  })(Backbone.Model);
64645
64646  App.Backbone.Models.ItemPriceOffer = (function(superClass) {
64647    extend(ItemPriceOffer, superClass);
64648
64649    function ItemPriceOffer() {
64650      return ItemPriceOffer.__super__.constructor.apply(this, arguments);
64651    }
64652
64653    ItemPriceOffer.prototype.urlRoot = '/api/internal/price_offers';
64654
64655    return ItemPriceOffer;
64656
64657  })(Backbone.Model);
64658
64659}).call(this);
64660(function() {
64661  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64662    hasProp = {}.hasOwnProperty;
64663
64664  App.Backbone.Models.FileConversionRequest = (function(superClass) {
64665    extend(FileConversionRequest, superClass);
64666
64667    function FileConversionRequest() {
64668      return FileConversionRequest.__super__.constructor.apply(this, arguments);
64669    }
64670
64671    FileConversionRequest.prototype.url = '/file_conversions';
64672
64673    return FileConversionRequest;
64674
64675  })(Backbone.Model);
64676
64677}).call(this);
64678(function() {
64679  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64680    hasProp = {}.hasOwnProperty;
64681
64682  App.Backbone.Models.FileExtension = (function(superClass) {
64683    extend(FileExtension, superClass);
64684
64685    function FileExtension() {
64686      return FileExtension.__super__.constructor.apply(this, arguments);
64687    }
64688
64689    return FileExtension;
64690
64691  })(Backbone.Model);
64692
64693  App.Backbone.Collections.FileExtensionsCollection = (function(superClass) {
64694    extend(FileExtensionsCollection, superClass);
64695
64696    function FileExtensionsCollection() {
64697      return FileExtensionsCollection.__super__.constructor.apply(this, arguments);
64698    }
64699
64700    FileExtensionsCollection.prototype.model = App.Backbone.Models.FileExtension;
64701
64702    FileExtensionsCollection.prototype.url = function() {
64703      return "/file_conversions/available_extensions";
64704    };
64705
64706    FileExtensionsCollection.prototype.toSelectData = function(options) {
64707      var result;
64708      result = this.map(function(model) {
64709        return {
64710          id: model.get('id'),
64711          text: model.get('file_extension')
64712        };
64713      });
64714      if (options.includeOther) {
64715        result.push({
64716          id: 0,
64717          text: 'Other'
64718        });
64719      }
64720      if (options.includeBlank) {
64721        result = [['', '']].concat(result);
64722      }
64723      return result;
64724    };
64725
64726    return FileExtensionsCollection;
64727
64728  })(Backbone.Collection);
64729
64730}).call(this);
64731(function() {
64732  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
64733    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64734    hasProp = {}.hasOwnProperty;
64735
64736  App.Backbone.Models.FileType = (function(superClass) {
64737    extend(FileType, superClass);
64738
64739    function FileType() {
64740      this.initialize = bind(this.initialize, this);
64741      return FileType.__super__.constructor.apply(this, arguments);
64742    }
64743
64744    FileType.prototype.initialize = function() {
64745      this.renderers = new App.Backbone.Collections.RenderersCollection(this.attributes.renderers);
64746      return delete this.attributes.renderers;
64747    };
64748
64749    return FileType;
64750
64751  })(Backbone.Model);
64752
64753  App.Backbone.Collections.FileTypesCollection = (function(superClass) {
64754    extend(FileTypesCollection, superClass);
64755
64756    function FileTypesCollection() {
64757      return FileTypesCollection.__super__.constructor.apply(this, arguments);
64758    }
64759
64760    FileTypesCollection.prototype.model = App.Backbone.Models.FileType;
64761
64762    FileTypesCollection.prototype.toSelectData = function() {
64763      return this.map(function(model) {
64764        return {
64765          id: model.get('id'),
64766          text: model.get('title')
64767        };
64768      });
64769    };
64770
64771    return FileTypesCollection;
64772
64773  })(Backbone.Collection);
64774
64775}).call(this);
64776(function() {
64777  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64778    hasProp = {}.hasOwnProperty;
64779
64780  App.Backbone.Models.Format = (function(superClass) {
64781    extend(Format, superClass);
64782
64783    function Format() {
64784      return Format.__super__.constructor.apply(this, arguments);
64785    }
64786
64787    return Format;
64788
64789  })(Backbone.Model);
64790
64791  App.Backbone.Collections.FormatsCollection = (function(superClass) {
64792    extend(FormatsCollection, superClass);
64793
64794    function FormatsCollection() {
64795      return FormatsCollection.__super__.constructor.apply(this, arguments);
64796    }
64797
64798    FormatsCollection.prototype.model = App.Backbone.Models.Format;
64799
64800    FormatsCollection.prototype.findById = function(formatId) {
64801      return this.detect(function(format) {
64802        return format.id === formatId;
64803      });
64804    };
64805
64806    FormatsCollection.prototype.findByFileExtensions = function(fileExtensions) {
64807      return this.detect(function(format) {
64808        return _.difference(fileExtensions, format.get('formats')).length === 0;
64809      });
64810    };
64811
64812    FormatsCollection.prototype.subtract = function(otherCollection) {
64813      var subtractArray;
64814      subtractArray = this.select(function(format) {
64815        return otherCollection.findById(format.id) == null;
64816      });
64817      return new App.Backbone.Collections.FormatsCollection(subtractArray);
64818    };
64819
64820    FormatsCollection.prototype.toSelectData = function() {
64821      return this.map(function(model) {
64822        return {
64823          id: model.get('id'),
64824          text: model.get('name')
64825        };
64826      });
64827    };
64828
64829    return FormatsCollection;
64830
64831  })(Backbone.Collection);
64832
64833}).call(this);
64834(function() {
64835  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64836    hasProp = {}.hasOwnProperty;
64837
64838  App.Backbone.Models.PrinterType = (function(superClass) {
64839    extend(PrinterType, superClass);
64840
64841    function PrinterType() {
64842      return PrinterType.__super__.constructor.apply(this, arguments);
64843    }
64844
64845    return PrinterType;
64846
64847  })(Backbone.Model);
64848
64849  App.Backbone.Collections.PrinterTypesCollection = (function(superClass) {
64850    extend(PrinterTypesCollection, superClass);
64851
64852    function PrinterTypesCollection() {
64853      return PrinterTypesCollection.__super__.constructor.apply(this, arguments);
64854    }
64855
64856    PrinterTypesCollection.prototype.model = App.Backbone.Models.PrinterType;
64857
64858    PrinterTypesCollection.prototype.toSelectData = function() {
64859      return this.map(function(model) {
64860        return {
64861          id: model.get('id'),
64862          text: (model.get('manufacturer')) + " - " + (model.get('name'))
64863        };
64864      });
64865    };
64866
64867    return PrinterTypesCollection;
64868
64869  })(Backbone.Collection);
64870
64871}).call(this);
64872(function() {
64873  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64874    hasProp = {}.hasOwnProperty;
64875
64876  App.Backbone.Models.Software = (function(superClass) {
64877    extend(Software, superClass);
64878
64879    function Software() {
64880      return Software.__super__.constructor.apply(this, arguments);
64881    }
64882
64883    return Software;
64884
64885  })(Backbone.Model);
64886
64887  App.Backbone.Collections.SoftwareCollection = (function(superClass) {
64888    extend(SoftwareCollection, superClass);
64889
64890    function SoftwareCollection() {
64891      return SoftwareCollection.__super__.constructor.apply(this, arguments);
64892    }
64893
64894    SoftwareCollection.prototype.model = App.Backbone.Models.Software;
64895
64896    SoftwareCollection.prototype.url = function() {
64897      return "/file_conversions/available_software";
64898    };
64899
64900    SoftwareCollection.prototype.toSelectData = function(options) {
64901      var result;
64902      result = this.map(function(model) {
64903        return {
64904          id: model.get('id'),
64905          text: model.get('title')
64906        };
64907      });
64908      if (options.includeOther) {
64909        result.push({
64910          id: 0,
64911          text: 'Other'
64912        });
64913      }
64914      if (options.includeBlank) {
64915        result = [['', '']].concat(result);
64916      }
64917      return result;
64918    };
64919
64920    return SoftwareCollection;
64921
64922  })(Backbone.Collection);
64923
64924}).call(this);
64925(function() {
64926  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64927    hasProp = {}.hasOwnProperty;
64928
64929  App.Backbone.Models.User = (function(superClass) {
64930    extend(User, superClass);
64931
64932    function User() {
64933      return User.__super__.constructor.apply(this, arguments);
64934    }
64935
64936    return User;
64937
64938  })(Backbone.Model);
64939
64940}).call(this);
64941(function() {
64942  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64943    hasProp = {}.hasOwnProperty;
64944
64945  App.Backbone.Models.WishList = (function(superClass) {
64946    extend(WishList, superClass);
64947
64948    function WishList() {
64949      return WishList.__super__.constructor.apply(this, arguments);
64950    }
64951
64952    return WishList;
64953
64954  })(Backbone.Model);
64955
64956  App.Backbone.Collections.WishListsCollection = (function(superClass) {
64957    extend(WishListsCollection, superClass);
64958
64959    function WishListsCollection() {
64960      return WishListsCollection.__super__.constructor.apply(this, arguments);
64961    }
64962
64963    WishListsCollection.prototype.model = App.Backbone.Models.WishList;
64964
64965    WishListsCollection.prototype.url = '/api/internal/wish-lists';
64966
64967    WishListsCollection.prototype.parse = function(response) {
64968      return response.data;
64969    };
64970
64971    return WishListsCollection;
64972
64973  })(Backbone.Collection);
64974
64975}).call(this);
64976(function() {
64977  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
64978    hasProp = {}.hasOwnProperty;
64979
64980  App.Backbone.Models.WishListItem = (function(superClass) {
64981    extend(WishListItem, superClass);
64982
64983    function WishListItem() {
64984      return WishListItem.__super__.constructor.apply(this, arguments);
64985    }
64986
64987    WishListItem.prototype.url = function() {
64988      if (this.id != null) {
64989        return "/api/internal/wish-lists/" + this.attributes.data.attributes.wish_list_id + "/items/" + this.id;
64990      } else {
64991        return "/api/internal/wish-lists/" + this.attributes.data.attributes.wish_list_id + "/items";
64992      }
64993    };
64994
64995    return WishListItem;
64996
64997  })(Backbone.Model);
64998
64999}).call(this);
65000(function() {
65001  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
65002    hasProp = {}.hasOwnProperty;
65003
65004  App.Backbone.Routers.ItemsSearch = (function(superClass) {
65005    extend(ItemsSearch, superClass);
65006
65007    function ItemsSearch() {
65008      return ItemsSearch.__super__.constructor.apply(this, arguments);
65009    }
65010
65011    ItemsSearch.prototype.urlBuilder = null;
65012
65013    ItemsSearch.prototype.skipLoading = true;
65014
65015    ItemsSearch.prototype.routes = {
65016      '': 'backToRoot',
65017      '*any': 'index'
65018    };
65019
65020    ItemsSearch.prototype.initialize = function(options) {
65021      this.urlBuilder = new ItemsSearchUrlBuilder;
65022      this.dedicateFileTypeFragments = options.dedicateFileTypeFragments;
65023      return App.Backbone.Routers.ItemsSearch.instance = this;
65024    };
65025
65026    ItemsSearch.prototype.backToRoot = function() {
65027      return window.location = '/';
65028    };
65029
65030    ItemsSearch.prototype.index = function() {
65031      if (this.skipLoading) {
65032        this.skipLoading = false;
65033        this.initUI();
65034        return;
65035      }
65036      return this.fetchItems(function() {
65037        if ($(window).scrollTop() > 400) {
65038          return $('body').clearQueue().scrollTo('#focus-marker', {
65039            duration: 600,
65040            offset: -120,
65041            easing: 'easeInOutCirc'
65042          });
65043        }
65044      });
65045    };
65046
65047    ItemsSearch.prototype.configureUrlBuilder = function() {
65048      this.urlBuilder.set(window.location);
65049      this.urlBuilder.setExtraSource({
65050        file_types: JSON.parse($('#active_file_types').val())
65051      });
65052      this.urlBuilder.setDedicateFileTypeFragments(this.dedicateFileTypeFragments);
65053      return this.urlBuilder.setProductType(App.config.currentProductType);
65054    };
65055
65056    ItemsSearch.prototype.applyFilter = function(key, value, options) {
65057      var newUrl;
65058      if (options == null) {
65059        options = {};
65060      }
65061      this.configureUrlBuilder();
65062      newUrl = this.urlBuilder.applyFilter(key, value);
65063      if (options.reload) {
65064        return window.location = newUrl;
65065      } else {
65066        return this.navigate(newUrl, {
65067          trigger: true
65068        });
65069      }
65070    };
65071
65072    ItemsSearch.prototype.removeFilter = function(key, subValue) {
65073      var newUrl, oldUrl;
65074      if (subValue == null) {
65075        subValue = null;
65076      }
65077      this.configureUrlBuilder();
65078      newUrl = this.urlBuilder.removeFilter(key, subValue);
65079      oldUrl = "" + window.location.pathname + window.location.search;
65080      this.navigate(newUrl, {
65081        trigger: true
65082      });
65083      if (newUrl === oldUrl) {
65084        return this.index();
65085      }
65086    };
65087
65088    ItemsSearch.prototype.navigateToCategory = function(newLocation) {
65089      this.configureUrlBuilder();
65090      this.urlBuilder.set({
65091        pathname: newLocation,
65092        search: window.location.search
65093      });
65094      return this.navigate(this.urlBuilder.build(), {
65095        trigger: true
65096      });
65097    };
65098
65099    ItemsSearch.prototype.fetchItems = function(callback) {
65100      var ref;
65101      $('.content-box-wrapper').addClass('is-updating-with-spinner spinner--top is-fixed');
65102      if ((ref = this.currentRequest) != null) {
65103        ref.abort();
65104      }
65105      return this.currentRequest = $.getScript("" + window.location.pathname + window.location.search, (function(_this) {
65106        return function() {
65107          _this.updateUI();
65108          return typeof callback === "function" ? callback() : void 0;
65109        };
65110      })(this));
65111    };
65112
65113    ItemsSearch.prototype.initUI = function() {
65114      App.events.publish('app:ui:refresh', $('.js-filter-block-container'));
65115      App.events.publish('items:init-price-range-filter', $('#price_range'));
65116      App.events.publish('items:init-sticky-filters-block', $('.js-sticky'));
65117      return App.events.publish('items:init-license-filter-tooltip', $('.js-license-filter-tooltip'));
65118    };
65119
65120    ItemsSearch.prototype.updateUI = function() {
65121      this.initUI();
65122      return App.events.publish('app:product:bind', $('#products-list .content-box'));
65123    };
65124
65125    return ItemsSearch;
65126
65127  })(Backbone.Router);
65128
65129}).call(this);
65130(function() {
65131  var base;
65132
65133  if ((base = $.fn).loadDynamicIframe == null) {
65134    base.loadDynamicIframe = function() {
65135      return this.each(function(index, el) {
65136        var src;
65137        src = $(el).data('src');
65138        if (el.src !== src) {
65139          return el.src = src;
65140        }
65141      });
65142    };
65143  }
65144
65145}).call(this);
65146(function() {
65147  if (window.mute == null) {
65148    window.mute = function(event) {
65149      return (event != null ? event.preventDefault() : void 0) && (event != null ? event.stopPropagation() : void 0);
65150    };
65151  }
65152
65153}).call(this);
65154(function() {
65155  var base;
65156
65157  if ((base = $.fn).renderFormErrors == null) {
65158    base.renderFormErrors = function(errors) {
65159      var attribute, el, error, id, input, missingAttributeErrors;
65160      if (!this.is('form')) {
65161        throw 'Can\'t render errors. Use appropriate DOM element.';
65162      }
65163      id = this.attr('id');
65164      missingAttributeErrors = [];
65165      this.find('label.error.error-manually-added').remove();
65166      for (attribute in errors) {
65167        error = errors[attribute];
65168        if (attribute === 'custom_errors') {
65169          continue;
65170        }
65171        input = this.find("#" + id + "-" + attribute);
65172        if (input[0]) {
65173          el = "<label class='error error-manually-added u-text-justify u-block' for='" + id + "-" + attribute + "'>" + error + "</label>";
65174          input.parent().append(el);
65175        } else {
65176          if (errors.custom_errors && errors.custom_errors[attribute]) {
65177            missingAttributeErrors.push(errors.custom_errors[attribute]);
65178          }
65179        }
65180      }
65181      if (missingAttributeErrors[0]) {
65182        App.events.publish("app:notification:flash:error", missingAttributeErrors.join(' '));
65183      }
65184      return true;
65185    };
65186  }
65187
65188}).call(this);
65189(function() {
65190  if (window.guid == null) {
65191    window.guid = function() {
65192      var s4;
65193      s4 = function() {
65194        return Math.floor((1 + Math.random(new Date().getTime())) * 0x10000).toString(16).substring(1);
65195      };
65196      return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4();
65197    };
65198  }
65199
65200}).call(this);
65201(function() {
65202  var base;
65203
65204  if ((base = $.fn).hideOnOutsideClick == null) {
65205    base.hideOnOutsideClick = function(hideFunction) {
65206      return $(document).mouseup((function(_this) {
65207        return function(e) {
65208          if (!_this.is(e.target) && _this.has(e.target).length === 0) {
65209            return typeof hideFunction === "function" ? hideFunction(e) : void 0;
65210          }
65211        };
65212      })(this));
65213    };
65214  }
65215
65216}).call(this);
65217(function() {
65218  $.fn.exists = function() {
65219    return this.length > 0;
65220  };
65221
65222}).call(this);
65223(function() {
65224  var base;
65225
65226  if ((base = $.fn).renderSerializedErrors == null) {
65227    base.renderSerializedErrors = function(errors) {
65228      var attribute, el, error, errorMessages, id, input, parts;
65229      if (!this.is('form')) {
65230        throw 'Can\'t render errors. Use appropriate DOM element.';
65231      }
65232      id = this.attr('id');
65233      errorMessages = [];
65234      this.find('label.error.error-manually-added').remove();
65235      for (error in errors) {
65236        parts = errors[error].source.pointer.split('/');
65237        attribute = parts[parts.length - 1];
65238        input = this.find("#" + id + "-" + attribute);
65239        if (input[0]) {
65240          el = "<label class=\"error error-manually-added u-text-justify u-block\" for=\"" + id + "-" + attribute + "\">\n  " + errors[error].detail + "\n</label>";
65241          input.parent().append(el);
65242        } else {
65243          errorMessages.push(errors[error].detail);
65244        }
65245      }
65246      if (errorMessages[0]) {
65247        App.events.publish("app:notification:flash:error", errorMessages.join(' '));
65248      }
65249      return true;
65250    };
65251  }
65252
65253}).call(this);
65254(function() {
65255  var base, base1;
65256
65257  $.fn.isOnScreen = function() {
65258    var bounds, viewport, win;
65259    win = $(window);
65260    viewport = {
65261      top: win.scrollTop(),
65262      left: win.scrollLeft()
65263    };
65264    viewport.right = viewport.left + win.width();
65265    viewport.bottom = viewport.top + win.height();
65266    bounds = this.offset();
65267    bounds.right = bounds.left + this.outerWidth();
65268    bounds.bottom = bounds.top + this.outerHeight();
65269    return !(viewport.right < bounds.left || viewport.left >
65269 bounds.right || viewport.bottom < bounds.top || viewport.top > bounds.bottom);
65270  };
65271
65272  if ((base = $.fn).exists == null) {
65273    base.exists = function() {
65274      return this.length > 0;
65275    };
65276  }
65277
65278  if ((base1 = $.fn).select2 == null) {
65279    base1.select2 = function() {};
65280  }
65281
65282}).call(this);
65283(function() {
65284  var bootApp, hasBlockingLoader, input, loadApp;
65285
65286  loadApp = function() {
65287    App.events.publish('app:config:load', $('#application-settings').data());
65288    App.events.publish('app:page:execute');
65289    return App.events.publish('app:ui:init');
65290  };
65291
65292  input = document.querySelector('#browser-cache-test');
65293
65294  bootApp = function() {
65295    if (input != null) {
65296      if (input.value) {
65297        window.location = window.location.toString().replace(location.hash, '');
65298      } else {
65299        loadApp();
65300      }
65301      return input.value = (new Date).getTime();
65302    } else {
65303      return loadApp();
65304    }
65305  };
65306
65307  hasBlockingLoader = document.querySelector('.js-loader-container') != null;
65308
65309  if (hasBlockingLoader) {
65310    if (document.readyState === 'loading') {
65311      document.addEventListener('DOMContentLoaded', bootApp);
65312    } else {
65313      setTimeout(bootApp, 0);
65314    }
65315  } else {
65316    window.addEventListener('load', bootApp);
65317  }
65318
65319}).call(this);
65320
65321/* Blob.js
65322 * A Blob implementation.
65323 * 2018-01-12
65324 *
65325 * By Eli Grey, http://eligrey.com
65326 * By Devin Samarin, https://github.com/dsamarin
65327 * License: MIT
65328 *   See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
65329 */
65330
65331/*global self, unescape */
65332/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
65333  plusplus: true */
65334
65335/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
65336
65337
65338(function(global) {
65339	(function (factory) {
65340		if (typeof define === "function" && define.amd) {
65341			// AMD. Register as an anonymous module.
65342			define(["exports"], factory);
65343		} else if (typeof exports === "object" && typeof exports.nodeName !== "string") {
65344			// CommonJS
65345			factory(exports);
65346		} else {
65347			// Browser globals
65348			factory(global);
65349		}
65350	})(function (exports) {
65351		"use strict";
65352
65353		exports.URL = global.URL || global.webkitURL;
65354
65355		if (global.Blob && global.URL) {
65356			try {
65357				new Blob;
65358				return;
65359			} catch (e) {
65360				// Blob did not instantiate, time to polyfill
65361			}
65362		}
65363
65364		// Internally we use a BlobBuilder implementation to base Blob off of
65365		// in order to support older browsers that only have BlobBuilder
65366		var BlobBuilder = global.BlobBuilder || global.WebKitBlobBuilder || global.MozBlobBuilder || (function() {
65367			var get_class = function(object) {
65368					return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
65369				}
65370				, FakeBlobBuilder = function BlobBuilder() {
65371					this.data = [];
65372				}
65373				, FakeBlob = function Blob(data, type, encoding) {
65374					this.data = data;
65375					this.size = data.length;
65376					this.type = type;
65377					this.encoding = encoding;
65378				}
65379				, FBB_proto = FakeBlobBuilder.prototype
65380				, FB_proto = FakeBlob.prototype
65381				, FileReaderSync = global.FileReaderSync
65382				, FileException = function(type) {
65383					this.code = this[this.name = type];
65384				}
65385				, file_ex_codes = (
65386					"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
65387					+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
65388				).split(" ")
65389				, file_ex_code = file_ex_codes.length
65390				, real_URL = global.URL || global.webkitURL || exports
65391				, real_create_object_URL = real_URL.createObjectURL
65392				, real_revoke_object_URL = real_URL.revokeObjectURL
65393				, URL = real_URL
65394				, btoa = global.btoa
65395				, atob = global.atob
65396
65397				, ArrayBuffer = global.ArrayBuffer
65398				, Uint8Array = global.Uint8Array
65399
65400				, origin = /^[\w-]+:\/*\[?[\w.:-]+\]?(?::[0-9]+)?/
65401			;
65402			FakeBlob.fake = FB_proto.fake = true;
65403			while (file_ex_code--) {
65404				FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
65405			}
65406			// Polyfill URL
65407			if (!real_URL.createObjectURL) {
65408				URL = exports.URL = function(uri) {
65409					var uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
65410						, uri_origin
65411					;
65412					uri_info.href = uri;
65413					if (!("origin" in uri_info)) {
65414						if (uri_info.protocol.toLowerCase() === "data:") {
65415							uri_info.origin = null;
65416						} else {
65417							uri_origin = uri.match(origin);
65418							uri_info.origin = uri_origin && uri_origin[1];
65419						}
65420					}
65421					return uri_info;
65422				};
65423			}
65424			URL.createObjectURL = function(blob) {
65425				var
65426					type = blob.type
65427					, data_URI_header
65428				;
65429				if (type === null) {
65430					type = "application/octet-stream";
65431				}
65432				if (blob instanceof FakeBlob) {
65433					data_URI_header = "data:" + type;
65434					if (blob.encoding === "base64") {
65435						return data_URI_header + ";base64," + blob.data;
65436					} else if (blob.encoding === "URI") {
65437						try {
65438							return `${data_URI_header},${decodeURIComponent(blob.data)}`
65439						} catch (e) {
65440							if (typeof window !== 'undefined' && window.Sentry && window.Sentry.captureException) {
65441								window.Sentry.captureException(new Error('Blob data URI decoding failed'), {
65442									tags: {
65443										error_type: 'url_decode_failure',
65444										library: 'blob_polyfill',
65445										component: 'blob_create_object_url',
65446									},
65447									extra: {
65448										data_data: blob.data,
65449										data_encoding: blob.encoding,
65450										data_type: blob.type,
65451										error_message: e.message,
65452										stack_trace: e.stack,
65453										timestamp: new Date().toISOString(),
65454										page_url: typeof window !== 'undefined' ? window.location.href : 'unknown',
65455										level: 'warning',
65456									}
65457								})
65458							}
65459							// Fallback to original data if URI is malformed
65460							return `${data_URI_header},${blob.data}`
65461						}
65462					} if (btoa) {
65463						return data_URI_header + ";base64," + btoa(blob.data);
65464					} else {
65465						return data_URI_header + "," + encodeURIComponent(blob.data);
65466					}
65467				} else if (real_create_object_URL) {
65468					return real_create_object_URL.call(real_URL, blob);
65469				}
65470			};
65471			URL.revokeObjectURL = function(object_URL) {
65472				if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
65473					real_revoke_object_URL.call(real_URL, object_URL);
65474				}
65475			};
65476			FBB_proto.append = function(data/*, endings*/) {
65477				var bb = this.data;
65478				// decode data to a binary string
65479				if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
65480					var str = ""
65481						, buf = new Uint8Array(data)
65482						, i = 0
65483						, buf_len = buf.length
65484					;
65485					for (; i < buf_len; i++) {
65486						str += String.fromCharCode(buf[i]);
65487					}
65488					bb.push(str);
65489				} else if (get_class(data) === "Blob" || get_class(data) === "File") {
65490					if (FileReaderSync) {
65491						var fr = new FileReaderSync;
65492						bb.push(fr.readAsBinaryString(data));
65493					} else {
65494						// async FileReader won't work as BlobBuilder is sync
65495						throw new FileException("NOT_READABLE_ERR");
65496					}
65497				} else if (data instanceof FakeBlob) {
65498					if (data.encoding === "base64" && atob) {
65499						bb.push(atob(data.data));
65500					} else if (data.encoding === "URI") {
65501						try {
65502							bb.push(decodeURIComponent(data.data))
65503						} catch (e) {
65504							window.Sentry.captureException(new Error('Blob data URI decoding failed'), {
65505								tags: {
65506									error_type: 'url_decode_failure',
65507									library: 'blob_polyfill',
65508									component: 'blob_append',
65509								},
65510								extra: {
65511									data_data: data.data,
65512									data_encoding: data.encoding,
65513									data_type: data.type,
65514									error_message: e.message,
65515									stack_trace: e.stack,
65516									timestamp: new Date().toISOString(),
65517									page_url: typeof window !== 'undefined' ? window.location.href : 'unknown',
65518									level: 'warning',
65519								}
65520							})
65521							// Fallback to original data if URI is malformed
65522							bb.push(data.data)
65523						}
65524					} else if (data.encoding === "raw") {
65525						bb.push(data.data);
65526					}
65527				} else {
65528					if (typeof data !== "string") {
65529						data += ""; // convert unsupported types to strings
65530					}
65531					// decode UTF-16 to binary string
65532					bb.push(unescape(encodeURIComponent(data)));
65533				}
65534			};
65535			FBB_proto.getBlob = function(type) {
65536				if (!arguments.length) {
65537					type = null;
65538				}
65539				return new FakeBlob(this.data.join(""), type, "raw");
65540			};
65541			FBB_proto.toString = function() {
65542				return "[object BlobBuilder]";
65543			};
65544			FB_proto.slice = function(start, end, type) {
65545				var args = arguments.length;
65546				if (args < 3) {
65547					type = null;
65548				}
65549				return new FakeBlob(this.data.slice(start, args > 1 ? end : this.data.length)
65550					, type
65551					, this.encoding
65552				);
65553			};
65554			FB_proto.toString = function() {
65555				return "[object Blob]";
65556			};
65557			FB_proto.close = function() {
65558				this.size = 0;
65559				delete this.data;
65560			};
65561			return FakeBlobBuilder;
65562		}());
65563
65564		exports.Blob = function(blobParts, options) {
65565			var type = options ? (options.type || "") : "";
65566			var builder = new BlobBuilder();
65567			if (blobParts) {
65568				for (var i = 0, len = blobParts.length; i < len; i++) {
65569					if (Uint8Array && blobParts[i] instanceof Uint8Array) {
65570						builder.append(blobParts[i].buffer);
65571					}
65572					else {
65573						builder.append(blobParts[i]);
65574					}
65575				}
65576			}
65577			var blob = builder.getBlob(type);
65578			if (!blob.slice && blob.webkitSlice) {
65579				blob.slice = blob.webkitSlice;
65580			}
65581			return blob;
65582		};
65583
65584		var getPrototypeOf = Object.getPrototypeOf || function(object) {
65585			return object.__proto__;
65586		};
65587		exports.Blob.prototype = getPrototypeOf(new exports.Blob());
65588	});
65589})(
65590	typeof self !== "undefined" && self ||
65591	typeof window !== "undefined" && window ||
65592	typeof global !== "undefined" && global ||
65593	this
65594);
65595;(function(){var k;function l(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}var m="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,d){a!=Array.prototype&&a!=Object.prototype&&(a[b]=d.value)},n="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this;function p(){p=function(){};n.Symbol||(n.Symbol=r)}var r=function(){var a=0;return function(b){return"jscomp_symbol_"+(b||"")+a++}}();
65596function u(){p();var a=n.Symbol.iterator;a||(a=n.Symbol.iterator=n.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&m(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return v(l(this))}});u=function(){}}function v(a){u();a={next:a};a[n.Symbol.iterator]=function(){return this};return a}function x(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:l(a)}}var y;
65597if("function"==typeof Object.setPrototypeOf)y=Object.setPrototypeOf;else{var z;a:{var A={o:!0},B={};try{B.__proto__=A;z=B.o;break a}catch(a){}z=!1}y=z?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null}var C=y;function D(){this.g=!1;this.c=null;this.m=void 0;this.b=1;this.l=this.s=0;this.f=null}function E(a){if(a.g)throw new TypeError("Generator is already running");a.g=!0}D.prototype.h=function(a){this.m=a};
65598D.prototype.i=function(a){this.f={u:a,v:!0};this.b=this.s||this.l};D.prototype["return"]=function(a){this.f={"return":a};this.b=this.l};function F(a,b,d){a.b=d;return{value:b}}function G(a){this.w=a;this.j=[];for(var b in a)this.j.push(b);this.j.reverse()}function H(a){this.a=new D;this.A=a}H.prototype.h=function(a){E(this.a);if(this.a.c)return I(this,this.a.c.next,a,this.a.h);this.a.h(a);return J(this)};
65599function K(a,b){E(a.a);var d=a.a.c;if(d)return I(a,"return"in d?d["return"]:function(a){return{value:a,done:!0}},b,a.a["return"]);a.a["return"](b);return J(a)}H.prototype.i=function(a){E(this.a);if(this.a.c)return I(this,this.a.c["throw"],a,this.a.h);this.a.i(a);return J(this)};
65600function I(a,b,d,c){try{var e=b.call(a.a.c,d);if(!(e instanceof Object))throw new TypeError("Iterator result "+e+" is not an object");if(!e.done)return a.a.g=!1,e;var f=e.value}catch(g){return a.a.c=null,a.a.i(g),J(a)}a.a.c=null;c.call(a.a,f);return J(a)}function J(a){for(;a.a.b;)try{var b=a.A(a.a);if(b)return a.a.g=!1,{value:b.value,done:!1}}catch(d){a.a.m=void 0,a.a.i(d)}a.a.g=!1;if(a.a.f){b=a.a.f;a.a.f=null;if(b.v)throw b.u;return{value:b["return"],done:!0}}return{value:void 0,done:!0}}
65601function L(a){this.next=function(b){return a.h(b)};this["throw"]=function(b){return a.i(b)};this["return"]=function(b){return K(a,b)};u();this[Symbol.iterator]=function(){return this}}function M(a,b){var d=new L(new H(b));C&&C(d,a.prototype);return d}
65602if("undefined"===typeof FormData||!FormData.prototype.keys){var N=function(a,b){for(var d=0;d<a.length;d++)b(a[d])},O=function(a,b,d){if(2>arguments.length)throw new TypeError("2 arguments required, but only "+arguments.length+" present.");return b instanceof Blob?[a+"",b,void 0!==d?d+"":"string"===typeof b.name?b.name:"blob"]:[a+"",b+""]},P=function(a){if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");return[a+""]},Q=function(a){var b=x(a);a=b.next().value;b=b.next().value;
65603a instanceof Blob&&(a=new File([a],b,{type:a.type,lastModified:a.lastModified}));return a},R="object"===typeof window?window:"object"===typeof self?self:this,S=R.FormData,T=R.XMLHttpRequest&&R.XMLHttpRequest.prototype.send,U=R.Request&&R.fetch;p();var V=R.Symbol&&Symbol.toStringTag,W=new WeakMap,X=Array.from||function(a){return[].slice.call(a)};V&&(Blob.prototype[V]||(Blob.prototype[V]="Blob"),"File"in R&&!File.prototype[V]&&(File.prototype[V]="File"));try{new File([],"")}catch(a){R.File=function(b,
65604d,c){b=new Blob(b,c);c=c&&void 0!==c.lastModified?new Date(c.lastModified):new Date;Object.defineProperties(b,{name:{value:d},lastModifiedDate:{value:c},lastModified:{value:+c},toString:{value:function(){return"[object File]"}}});V&&Object.defineProperty(b,V,{value:"File"});return b}}p();u();var Y=function(a){W.set(this,Object.create(null));if(!a)return this;var b=this;N(a.elements,function(a){a.name&&!a.disabled&&"submit"!==a.type&&"button"!==a.type&&("file"===a.type?N(a.files||[],function(c){b.append(a.name,
65605c)}):"select-multiple"===a.type||"select-one"===a.type?N(a.options,function(c){!c.disabled&&c.selected&&b.append(a.name,c.value)}):"checkbox"===a.type||"radio"===a.type?a.checked&&b.append(a.name,a.value):b.append(a.name,a.value))})};k=Y.prototype;k.append=function(a,b,d){var c=W.get(this);c[a]||(c[a]=[]);c[a].push([b,d])};k["delete"]=function(a){delete W.get(this)[a]};k.entries=function b(){var d=this,c,e,f,g,h,q;return M(b,function(b){switch(b.b){case 1:c=W.get(d),f=new G(c);case 2:var t;a:{for(t=
65606f;0<t.j.length;){var w=t.j.pop();if(w in t.w){t=w;break a}}t=null}if(null==(e=t)){b.b=0;break}g=x(c[e]);h=g.next();case 5:if(h.done){b.b=2;break}q=h.value;return F(b,[e,Q(q)],6);case 6:h=g.next(),b.b=5}})};
65606k.forEach=function(b,d){for(var c=x(this),e=c.next();!e.done;e=c.next()){var f=x(e.value);e=f.next().value;f=f.next().value;b.call(d,f,e,this)}};k.get=function(b){var d=W.get(this);return d[b]?Q(d[b][0]):null};k.getAll=function(b){return(W.get(this)[b]||[]).map(Q)};k.has=function(b){return b in
65607W.get(this)};k.keys=function d(){var c=this,e,f,g,h,q;return M(d,function(d){1==d.b&&(e=x(c),f=e.next());if(3!=d.b){if(f.done){d.b=0;return}g=f.value;h=x(g);q=h.next().value;return F(d,q,3)}f=e.next();d.b=2})};k.set=function(d,c,e){W.get(this)[d]=[[c,e]]};k.values=function c(){var e=this,f,g,h,q,w;return M(c,function(c){1==c.b&&(f=x(e),g=f.next());if(3!=c.b){if(g.done){c.b=0;return}h=g.value;q=x(h);q.next();w=q.next().value;return F(c,w,3)}g=f.next();c.b=2})};Y.prototype._asNative=function(){for(var c=
65608new S,e=x(this),f=e.next();!f.done;f=e.next()){var g=x(f.value);f=g.next().value;g=g.next().value;c.append(f,g)}return c};Y.prototype._blob=function(){for(var c="----formdata-polyfill-"+Math.random(),e=[],f=x(this),g=f.next();!g.done;g=f.next()){var h=x(g.value);g=h.next().value;h=h.next().value;e.push("--"+c+"\r\n");h instanceof Blob?e.push('Content-Disposition: form-data; name="'+g+'"; filename="'+h.name+'"\r\n',"Content-Type: "+(h.type||"application/octet-stream")+"\r\n\r\n",h,"\r\n"):e.push('Content-Disposition: form-data; name="'+
65609g+'"\r\n\r\n'+h+"\r\n")}e.push("--"+c+"--");return new Blob(e,{type:"multipart/form-data; boundary="+c})};Y.prototype[Symbol.iterator]=function(){return this.entries()};Y.prototype.toString=function(){return"[object FormData]"};V&&(Y.prototype[V]="FormData");[["append",O],["delete",P],["get",P],["getAll",P],["has",P],["set",O]].forEach(function(c){var e=Y.prototype[c[0]];Y.prototype[c[0]]=function(){return e.apply(this,c[1].apply(this,X(arguments)))}});T&&(XMLHttpRequest.prototype.send=function(c){c instanceof
65610Y?(c=c._blob(),this.setRequestHeader("Content-Type",c.type),T.call(this,c)):T.call(this,c)});if(U){var Z=R.fetch;R.fetch=function(c,e){e&&e.body&&e.body instanceof Y&&(e.body=e.body._blob());return Z(c,e)}}R.FormData=Y};
65611})();
65612
65613
65614(function() {
65615
65616
65617}).call(this);
65618const isInCheckoutPage = window.location.pathname && window.location.pathname.includes('/checkout')
65619const availableCountriesForLocalPayments = ['BE', 'AT', 'DE', 'NL', 'IT', 'ES', 'PL']
65620const merchantAccountId = 'cgtrader'
65621
65622var braintreeSdkUnavailable = false
65623
65624function showTrustlyButton() {
65625  // $(trustlyButton).removeClass('is-hidden')
65626}
65627
65628function prepareTrackViewItemList(attributes) {
65629  const {
65630    searchQuery,
65631    id,
65632    title,
65633    affiliation,
65634    index,
65635    authorUsername,
65636    category,
65637    subcategory,
65638    itemCategory3,
65639    itemCategory4,
65640    itemCategory5,
65641    productType,
65642    locationId,
65643    price,
65644    discount,
65645  } = attributes || {}
65646
65647  const trackable_attributes = {
65648    item_id: String(id),
65649    item_name: title,
65650    affiliation: affiliation,
65651    currency: 'USD',
65652    index: parseInt(index) || null,
65653    item_brand: authorUsername,
65654    item_category: category,
65655    item_category2: subcategory,
65656    item_category3: itemCategory3,
65657    item_category4: itemCategory4,
65658    item_category5: itemCategory5,
65659    item_list_id: searchQuery,
65660    item_list_name: searchQuery,
65661    item_variant: productType,
65662    location_id: locationId && locationId.join('_'),
65663    price: parseFloat(price),
65664    quantity: 1,
65665  }
65666
65667  if (discount > 0) {
65668    trackable_attributes.price = trackable_attributes.price + parseFloat(discount)
65669    trackable_attributes.discount = parseFloat(discount)
65670  } else {
65671    trackable_attributes.discount = 0
65672  }
65673
65674  return trackable_attributes
65675}
65676
65677function enableLocalPayments() {
65678  switch (countryCode) {
65679    case 'BE':
65680      $(bancontactButton).removeClass('is-hidden')
65681      $(klarnaButton).removeClass('is-hidden')
65682      break
65683    case 'AT':
65684      $(epsButton).removeClass('is-hidden')
65685      $(klarnaButton).removeClass('is-hidden')
65686      break
65687    case 'DE':
65688      $(giropayButton).removeClass('is-hidden')
65689      $(klarnaButton).removeClass('is-hidden')
65690      showTrustlyButton()
65691      break
65692    case 'NL':
65693      $(idealButton).removeClass('is-hidden')
65694      $(klarnaButton).removeClass('is-hidden')
65695      showTrustlyButton()
65696      break
65697    case 'IT':
65698      $(mybankButton).removeClass('is-hidden')
65699      $(klarnaButton).removeClass('is-hidden')
65700      $(satispayButton).removeClass('is-hidden')
65701      break
65702    case 'ES':
65703      $(klarnaButton).removeClass('is-hidden')
65704      showTrustlyButton()
65705      break
65706    case 'PL':
65707      $(p24Button).removeClass('is-hidden')
65708      break
65709    case 'AT':
65710      showTrustlyButton()
65711      break
65712    case 'EE':
65713      showTrustlyButton()
65714      break
65715    case 'FI':
65716      showTrustlyButton()
65717      break
65718    case 'LT':
65719      showTrustlyButton()
65720      break
65721    case 'LV':
65722      showTrustlyButton()
65723      break
65724    default:
65725      break
65726  }
65727}
65728
65729function trackPaymentEvent(paymentMethod) {
65730  if (window.dataLayer && gon && gon.cart_items) {
65731    const list = gon.cart_items.map((item) => prepareTrackViewItemList(item))
65732    const couponValue = $('#coupon_code').val() || ''
65733
65734    if (window.dataLayer && list.length > 0) {
65735      window.dataLayer.push({
65736        event: 'add_payment_info',
65737        user_id: App.config.userId || '',
65738        ecommerce: {
65739          currency: 'USD',
65740          value: gon.cart.value,
65741          coupon: couponValue,
65742          payment_type: paymentMethod,
65743          items: list,
65744        }
65745      })
65746    }
65747  }
65748}
65749
65750// The four SDK tags in app/views/checkout/_gateway_setup.slim are parser-blocking and
65751// live in <head>, while this bundle is loaded at the end of <body>. The parser cannot
65752// reach our tag until those four have executed or permanently failed, so a missing
65753// `braintree` global here is a verdict, not a race. Do NOT add async/defer to those
65754// tags without replacing this check with a real readiness wait.
65755//
65756// Each of the four scripts attaches to window.braintree, so a partial failure yields a
65757// TypeError on the missing namespace rather than a ReferenceError. Check all four.
65758function braintreeSdkReady() {
65759  const sdk = window.braintree
65760
65761  return !!(sdk && sdk.dropin && sdk.client && sdk.localPayment && sdk.dataCollector)
65762}
65763window.braintreeSdkReady = braintreeSdkReady
65764
65765function reportBraintreeSdkUnavailable() {
65766  const sdk = window.braintree
65767
65768  console.error('Braintree SDK unavailable')
65769
65770  if (!window.Sentry) return
65771
65772  window.Sentry.captureMessage('Braintree SDK failed to load', {
65773    // A synthesized Error would carry a stack pointing at this reporter, whose frames
65774    // shift with every bundle digest and would refragment the issue on each deploy.
65775    level: 'error',
65776    fingerprint: ['braintree-sdk-unavailable'],
65777    tags: { section: 'checkout.braintree_sdk' },
65778    extra: {
65779      path: window.location.pathname,
65780      hasBraintreeGlobal: !!sdk,
65781      braintreeKeys: sdk ? Object.keys(sdk) : [],
65782      readyState: document.readyState,
65783      msSinceNavigation: Math.round(window.performance.now()),
65784    },
65785  })
65786}
65787window.reportBraintreeSdkUnavailable = reportBraintreeSdkUnavailable
65788
65789// Nothing here can run at bundle-eval time. This file ships in the Sprockets bundle,
65790// which is `async` at the end of <body>, while light_bundle (which defines window.Sentry
65791// via initSentry) is `defer` in <head> - so an async bundle that lands first sees no
65792// Sentry at all. App.events is worse: it is a plain mediator with no replay, and its
65793// 'app:notification:flash:error' subscriber only exists once App.Pages.Global boots on
65794// 'app:page:execute'. Both are ready by DOM ready, since deferred scripts run before
65795// DOMContentLoaded, so defer the whole failure handling to that boundary.
65796function onCheckoutBoot(callback) {
65797  if (window.App && App.events) {
65798    App.events.subscribe('app:page:execute', callback)
65799  } else if (document.readyState === 'loading') {
65800    document.addEventListener('DOMContentLoaded', callback)
65801  } else {
65802    setTimeout(callback, 0)
65803  }
65804}
65805window.onCheckoutBoot = onCheckoutBoot
65806
65807// autohide false: the checkout is blocked, so the toast must not disappear.
65808function handleBraintreeSdkUnavailable() {
65809  onCheckoutBoot(function () {
65810    reportBraintreeSdkUnavailable()
65811
65812    if (!window.App || !App.events) return
65813
65814    App.events.publish(
65815      'app:notification:flash:error',
65816      'Payment form could not be loaded. An ad blocker, a privacy extension or your ' +
65817      'network may be blocking it. Please allow js.braintreegateway.com and reload.',
65818      false
65819    )
65820  })
65821}
65822window.handleBraintreeSdkUnavailable = handleBraintreeSdkUnavailable
65823
65824if (isInCheckoutPage) {
65825  // Velocity-triggered guest checkout captcha: when the lockout is active the
65826  // server renders #checkout-guest-captcha. Ensure the MTCaptcha scripts are
65827  // appended so the widget actually renders.
65828  if (document.getElementById('checkout-guest-captcha') && typeof window.appendMTCaptcha === 'function') {
65829    window.appendMTCaptcha()
65830  }
65831
65832  container = document.getElementById('braintree-sdk-container')
65833  payButton = document.getElementById('pay-button')
65834  alipayPaymentMethodOption = document.getElementById('alipay-payment-method-option')
65835  otherPaymentMethodsOption = document.getElementById('other-payment-methods-option')
65836  otherPaymentMethodsForm = document.getElementById('other-payment-methods-form')
65837  closeOtherPaymentMethodsFormButton = document.getElementById('close-other
65837-payment-methods-form-button')
65838  auxilaryInfo = $('#payment')
65839
65840  bancontactButton = document.getElementById('bancontact-button')
65841  epsButton = document.getElementById('eps-button')
65842  giropayButton = document.getElementById('giropay-button')
65843  idealButton = document.getElementById('ideal-button')
65844  klarnaButton = document.getElementById('sofort-button')
65845  mybankButton = document.getElementById('mybank-button')
65846  p24Button = document.getElementById('p24-button')
65847  trustlyButton = document.getElementById('trustly-button')
65848  satispayButton = document.getElementById('satispay-button')
65849  alipayButton = document.getElementById('alipay-button')
65850
65851  dataContainer = container && container.dataset
65852  countryCode = dataContainer && dataContainer.countryCode
65853  threeDSecureEnabled = !!dataContainer && dataContainer.threeDSecureEnabled === "true"
65854  dropinReady = false
65855
65856  setTimeout(() => {
65857    if ($('.js-loader-container .cgt-spinner').is(":visible")) {
65858      if (window.Sentry) {
65859        // The spinner is lifted by the checkout page class, not by this SDK, so the
65860        // extras record whether the Drop-in itself was the laggard.
65861        window.Sentry.captureMessage('Payment tab is still loading after 5 seconds', {
65862          extra: {
65863            id: auxilaryInfo.data('auxilary'),
65864            dropinReady: dropinReady,
65865            readyState: document.readyState,
65866            msSinceNavigation: Math.round(window.performance.now()),
65867          }
65868        })
65869
65870        App.events.publish(
65871          'app:notification:flash:error',
65872          'Oops. Connection seems to be unstable today, please contact support.'
65873        )
65874      }
65875    } else if (dataContainer && !dropinReady && !braintreeSdkUnavailable && window.Sentry) {
65876      // The page now boots at DOM ready, so a hung braintree.dropin.create no longer
65877      // keeps the spinner up: the tab is visible but the card form and pay button are
65878      // missing. Report it separately; no flash, since 5s Drop-in latency is often
65879      // transient on slow networks.
65880      window.Sentry.captureMessage('Braintree Drop-in not ready after 5 seconds', {
65881        extra: {
65882          id: auxilaryInfo.data('auxilary'),
65883          readyState: document.readyState,
65884          msSinceNavigation: Math.round(window.performance.now()),
65885        }
65886      })
65887    }
65888  }, 5000)
65889
65890  if (dataContainer && braintreeSdkReady()) {
65891    try {
65892      braintree.dropin.create({
65893        authorization: dataContainer.client_token,
65894        selector: '#braintree-sdk-container',
65895        threeDSecure: threeDSecureEnabled,
65896        dataCollector: true,
65897        card: {
65898          cardholderName: {
65899            required: true
65900          }
65901        },
65902        paypal: {
65903          flow: 'checkout',
65904          commit: true,
65905          amount: dataContainer.amount,
65906          currency: dataContainer.currency,
65907        },
65908        venmo: {
65909          allowNewBrowserTab: false,
65910        },
65911        applePay: {
65912          displayName: 'CGTrader',
65913          paymentRequest: {
65914            total: {
65915              label: 'CGTrader',
65916              amount: dataContainer.amount,
65917            },
65918          }
65919        },
65920        googlePay: {
65921          merchantId: dataContainer.googleMerchantId,
65922          transactionInfo: {
65923            totalPriceStatus: 'FINAL',
65924            totalPrice: dataContainer.amount,
65925            currencyCode: dataContainer.currency,
65926          },
65927        }
65928      }).then(function (dropinInstance) {
65929        dropinReady = true
65930        $(payButton).removeClass('is-hidden')
65931
65932        shouldSeeOtherPaymentMethodsOption = availableCountriesForLocalPayments.includes(countryCode) && dataContainer.currency === 'EUR'
65933        shouldSeeAlipayPaymentMethodOption = countryCode === 'CN'
65934
65935        if (shouldSeeOtherPaymentMethodsOption) {
65936          $(otherPaymentMethodsOption).removeClass('is-hidden')
65937          enableLocalPayments()
65938        }
65939
65940        if (shouldSeeAlipayPaymentMethodOption) {
65941          $(alipayPaymentMethodOption).removeClass('is-hidden')
65942          $(alipayButton).removeClass('is-hidden')
65943        }
65944
65945        function readGuestCheckoutCaptchaToken() {
65946          if (!document.getElementById('checkout-guest-captcha')) return null
65947          if (!window.mtcaptcha || typeof window.mtcaptcha.getVerifiedToken !== 'function') return ''
65948
65949          return window.mtcaptcha.getVerifiedToken('checkout-guest-captcha') || ''
65950        }
65951
65952        function appendGuestCheckoutCaptchaToken(urlWithPayload) {
65953          const token = readGuestCheckoutCaptchaToken()
65954          if (token === null) return urlWithPayload
65955
65956          return urlWithPayload + '&MTCaptchaToken=' + encodeURIComponent(token)
65957        }
65958
65959        function sendNonceToServer() {
65960          let payloadDataForSentry
65961
65962          // Velocity-triggered guest checkout captcha: require a solved widget
65963          // before issuing the payment request when the gate is active.
65964          if (document.getElementById('checkout-guest-captcha')) {
65965            const token = readGuestCheckoutCaptchaToken()
65966            if (!token) {
65967              if (typeof window.flash === 'function') {
65968                window.flash('Please complete the captcha to continue.')
65969              } else {
65970                window.alert('Please complete the captcha to continue.')
65971              }
65972              payButton.disabled = false
65973              return
65974            }
65975          }
65976
65977          dropinInstance.requestPaymentMethod({
65978            threeDSecure: {
65979              collectDeviceData: true,
65980              amount: dataContainer.amount,
65981              email: dataContainer.email,
65982              additionalInformation: {
65983                ipAddress: dataContainer.ipAddress
65984              }
65985            }
65986          }).then(function (payload) {
65987            payButton.disabled = true
65988            controller_path = document.getElementById('controller_path').dataset.path
65989            payload_data = {
65990              nonce: payload.nonce,
65991              order_id: dataContainer.orderId,
65992              device_data: payload.deviceData,
65993            }
65994
65995            payloadDataForSentry = payload_data
65996
65997            const verifyUrl = controller_path + '&payload=' + encodeURIComponent(JSON.stringify(payload_data))
65998            window.location = appendGuestCheckoutCaptchaToken(verifyUrl)
65999          }).catch(function (err) {
66000            // Handle errors in requesting payment method
66001            window.Sentry.captureException(new Error('Braintree Drop-in payment method request failed'), {
66002              tags: {
66003                error_type: 'payment_sdk_failure',
66004                component: 'braintree_dropin',
66005                stage: 'payment_method_request'
66006              },
66007              extra: {
66008                error_message: err.message,
66009                stack_trace: err.stack,
66010                client_token_present: !!dataContainer.client_token,
66011                payload_data: payloadDataForSentry,
66012                timestamp: new Date().toISOString(),
66013                page_url: window.location.href
66014              },
66015              level: 'error'
66016            })
66017            payButton.disabled = false
66018          })
66019        }
66020
66021        // allows us to still request the payment method manually, such as
66022        // when filling out a credit card form
66023        $(payButton).on('click', (e) => {
66024          e.preventDefault()
66025          payButton.disabled = true
66026          if (window.customEventTracker) {
66027            window.customEventTracker({
66028              name: 'one_time_purchase_pay_click',
66029              eventData: {
66030                event_source: gon.is_buy_now ? 'buy_now' : 'cart',
66031                payment_method: 'credit_card',
66032              },
66033            })
66034          }
66035          sendNonceToServer()
66036        })
66037
66038        if (dropinInstance.isPaymentMethodRequestable()) {
66039          // This will be true if you generated the client token
66040          // with a customer ID and there is a saved payment method
66041          // available to tokenize with that customer.
66042          payButton.disabled = false
66043        }
66044
66045        dropinInstance.on('changeActiveView', function ({ newViewId }) {
66046          if (shouldSeeOtherPaymentMethodsOption) {
66047            if (['methods', 'options'].includes(newViewId)) {
66048              $(otherPaymentMethodsOption).removeClass('is-hidden')
66049              $(otherPaymentMethodsForm).addClass('is-hidden')
66050            } else {
66051              $(otherPaymentMethodsOption).addClass('is-hidden')
66052            }
66053          }
66054
66055          if (shouldSeeAlipayPaymentMethodOption) {
66056            if (['methods', 'options'].includes(newViewId)) {
66057              $(alipayPaymentMethodOption).removeClass('is-hidden')
66058              $(otherPaymentMethodsForm).addClass('is-hidden')
66059            } else {
66060              $(alipayPaymentMethodOption).addClass('is-hidden')
66061            }
66062          }
66063        });
66064
66065        dropinInstance.on('paymentMethodRequestable', function (event) {
66066          const { type, paymentMethodIsSelected } = event
66067
66068          // PayPalAccount
66069          //
66070          if (type === 'PayPalAccount') {
66071            trackPaymentEvent('PayPal')
66072          }
66073
66074          if (type === 'CreditCard') {
66075            trackPaymentEvent('Credit Card')
66076          }
66077
66078          if (paymentMethodIsSelected) {
66079            payButton.disabled = true;
66080
66081            if (type !== 'CreditCard') {
66082              sendNonceToServer();
66083            }
66084
66085            return;
66086          }
66087
66088          payButton.disabled = false
66089        });
66090
66091        dropinInstance.on('noPaymentMethodRequestable', function () {
66092          payButton.disabled = true
66093        });
66094
66095        $(alipayPaymentMethodOption).on('click', (e) => {
66096          e.preventDefault()
66097          $(container).addClass('is-hidden')
66098          $(alipayPaymentMethodOption).addClass('is-hidden')
66099          $(otherPaymentMethodsForm).removeClass('is-hidden')
66100        })
66101
66102        $(otherPaymentMethodsOption).on('click', (e) => {
66103          e.preventDefault()
66104          $(container).addClass('is-hidden')
66105          $(otherPaymentMethodsOption).addClass('is-hidden')
66106          $(otherPaymentMethodsForm).removeClass('is-hidden')
66107        })
66108
66109        $(closeOtherPaymentMethodsFormButton).on('click', (e) => {
66110          e.preventDefault()
66111          $(container).removeClass('is-hidden')
66112          $(otherPaymentMethodsOption).removeClass('is-hidden')
66113          $(otherPaymentMethodsForm).addClass('is-hidden')
66114        })
66115
66116        $('.gpay-button').on('click', (e) => {
66117          trackPaymentEvent('Google Pay')
66118          if (window.customEventTracker) {
66119            window.customEventTracker({
66120              name: 'one_time_purchase_pay_click',
66121              eventData: {
66122                event_source: gon.is_buy_now ? 'buy_now' : 'cart',
66123                payment_method: 'google_pay',
66124              },
66125            })
66126          }
66127        })
66128      }).catch(function (err) {
66129        // Handle any errors that might've occurred when creating Drop-in
66130        console.error(err)
66131        window.Sentry.captureException(err)
66132      });
66133    } catch (e) {
66134      window.Sentry.captureException(e)
66135    }
66136
66137    try {
66138      // Create a client.
66139      braintree.client.create({
66140        authorization: dataContainer.client_token,
66141      }).then(function (clientInstance) {
66142        // Create a local payment component.
66143        return braintree.localPayment.create({
66144          client: clientInstance,
66145          merchantAccountId
66146        });
66147      }).then(function (localPaymentInstance) {
66148        givenNameInput = document.getElementById('given-name-input')
66149        surnameInput = document.getElementById('surname-input')
66150
66151        function createLocalPaymentClickListener(type) {
66152          return function (event) {
66153            controller_path = document.getElementById('controller_path').dataset.path
66154            event.preventDefault();
66155
66156            givenName = $(givenNameInput).val()
66157            surname = $(surnameInput).val()
66158
66159            if (!givenName || !surname) {
66160              if (!givenName) {
66161                $(givenNameInput).parent().addClass('cgt-input--error')
66162              }
66163              if (!surname) {
66164                $(surnameInput).parent().addClass('cgt-input--error')
66165              }
66166              return false
66167            }
66168
66169            if (window.customEventTracker) {
66170              window.customEventTracker({
66171                name: 'one_time_purchase_pay_click',
66172                eventData: {
66173                  event_source: gon.is_buy_now ? 'buy_now' : 'cart',
66174                  payment_method: type,
66175                },
66176              })
66177            }
66178
66179            localPaymentInstance.startPayment({
66180              paymentType: type,
66181              amount: dataContainer.amount,
66182              fallback: { // see Fallback section for details on these params
66183                buttonText: 'Return to CGTrader',
66184                url: window.location.href,
66185              },
66186              currencyCode: dataContainer.currency,
66187              givenName,
66188              surname,
66189              email: dataContainer.email,
66190              onPaymentStart: function (data, start) {
66191                link_order_with_local_payment_path = document.getElementById('link_order_with_local_payment').dataset.path
66192                local_payment_id = data.paymentId
66193                order_id = dataContainer.orderId
66194
66195                payload = { order_id, local_payment_id }
66196
66197                fetch(link_order_with_local_payment_path, {
66198                  method: 'POST',
66199                  headers: {
66200                    'Content-Type': 'application/json',
66201                    'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
66202                  },
66203                  body: JSON.stringify(payload)
66204                })
66205                start();
66206              }
66207            }).then(function (payload) {
66208              // Send the nonce to your server to create a transaction
66209              controller_path = document.getElementById('controller_path').dataset.path
66210              payload_data = { nonce: payload.nonce, order_id: dataContainer.orderId }
66211
66212              const verifyUrl = controller_path + '&payload=' + encodeURIComponent(JSON.stringify(payload_data))
66213              window.location = appendGuestCheckoutCaptchaToken(verifyUrl)
66214            }).catch(function (err) {
66215              if (err.code === 'LOCAL_PAYMENT_POPUP_CLOSED') {
66216                console.error('Customer closed Local Payment popup.');
66217              } else {
66218                console.error('Error!', err);
66219              }
66220            });
66221          };
66222        }
66223
66224        $(givenNameInput).on('input', function () {
66225          $(givenNameInput).parent().removeClass('cgt-input--error')
66226        })
66227
66228        $(surnameInput).on('input', function () {
66229          $(surnameInput).parent().removeClass('cgt-input--error')
66230        })
66231
66232        $(bancontactButton).on('click', createLocalPaymentClickListener('bancontact'))
66233        $(epsButton).on('click', createLocalPaymentClickListener('eps'))
66234        $(giropayButton).on('click', createLocalPaymentClickListener('giropay'))
66235        $(idealButton).on('click', createLocalPaymentClickListener('ideal'))
66236        $(klarnaButton).on('click', createLocalPaymentClickListener('sofort'))
66237        $(mybankButton).on('click', createLocalPaymentClickListener('mybank'))
66238        $(p24Button).on('click', createLocalPaymentClickListener('p24'))
66239        $(trustlyButton).on('click', createLocalPaymentClickListener('trustly'))
66240        $(satispayButton).on('click', createLocalPaymentClickListener('satispay'))
66241        $(alipayButton).on('click', createLocalPaymentClickListener('alipay'))
66242      }).catch(function (err) {
66243        // Handle any errors that might've occurred when creating Local Payment
66244        console.error(err)
66245        window.Sentry.captureException(err)
66246      });
66247    } catch (e) {
66248      window.Sentry.captureException(e)
66249    }
66250  } else if (dataContainer && document.querySelector('script[src*="js.braintreegateway.com"]')) {
66251    // Reached only when the SDK genuinely failed to load. A missing tag instead means
66252    // the gateway partial never rendered one (Rails.env.test?, or a non-web_sdk
66253    // gateway), which is not a failure - stay silent there.
66254    braintreeSdkUnavailable = true
66255    handleBraintreeSdkUnavailable()
66256  }
66257}
66258;

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.