PageSourceSearch

https://bfla.eu/assets/components/base/script.js?v=3

js bfla.eu collected 2026-10-02 03:26:21 UTC 47,162 bytes, 1,703 lines download raw bytes

1'use strict';
2
3// Global components list
4let components = {};
5
6components.pageReveal = {
7	selector: 'html',
8	init: function( nodes ) {
9		window.addEventListener( 'components:ready', function () {
10			window.dispatchEvent( new CustomEvent( 'resize' ) );
11			document.documentElement.classList.add( 'components-ready' );
12
13			setTimeout( function() {
14				document.documentElement.classList.add( 'page-loaded' );
15			}, 300 );
16		}, { once: true } );
17	}
18};
19
20components.currentDevice = {
21	selector: 'html',
22	script: '/assets/components/current-device/current-device.min.js'
23};
24
25components.fontFivoSansModern = {
26	selector: 'html',
27	styles: '/assets/components/fivosansmodern/fivosansmodern.css'
28};
29
30components.fontLato = {
31	selector: 'html',
32	styles: 'https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap'
33};
34
35components.spartan = {
36	selector: 'html',
37	styles: 'https://fonts.googleapis.com/css2?family=Spartan:wght@400;500;600;700;900&display=swap'
38};
39
40components.fontAwesome = {
41	selector: '[class*="fa-"]',
42	styles: '/assets/components/font-awesome/font-awesome.css'
43};
44
45components.mdi = {
46	selector: '[class*="mdi-"]',
47	styles: '/assets/components/mdi/mdi.css'
48};
49
50components.linearicons = {
51	selector: '[class*="linearicons-"]',
52	styles: '/assets/components/linearicons/linearicons.css'
53};
54
55components.alert = {
56	selector: '.alert',
57	styles: '/assets/components/alert/alert.css'
58};
59
60components.blogPost = {
61	selector: '.blog-post',
62	styles: '/assets/components/blog-post/blog-post.css'
63};
64
65components.blogSearch = {
66	selector: '.blog-search',
67	styles: '/assets/components/blog-search/blog-search.css'
68};
69
70components.blurb = {
71	selector: '.blurb',
72	styles: '/assets/components/blurb/blurb.css'
73};
74
75components.box = {
76	selector: '.box',
77	styles: '/assets/components/box/box.css'
78};
79
80components.breadcrumb = {
81	selector: '.breadcrumb',
82	styles: '/assets/components/breadcrumb/breadcrumb.css'
83};
84
85components.button = {
86	selector: '.btn',
87	styles: '/assets/components/button/button.css'
88};
89
90components.checkboxColor = {
91	selector: '.checkbox-color',
92	styles: '/assets/components/checkbox-color/checkbox-color.css'
93};
94
95components.checkboxTag = {
96	selector: '.checkbox-tag',
97	styles: '/assets/components/checkbox-tag/checkbox-tag.css'
98};
99
100components.close = {
101	selector: '.close',
102	styles: '/assets/components/close/close.css'
103};
104
105components.comment = {
106	selector: '.comment',
107	styles: '/assets/components/comment/comment.css'
108};
109
110components.divider = {
111	selector: '.divider',
112	styles: '/assets/components/divider/divider.css'
113};
114
115components.footer = {
116	selector: 'footer',
117	styles: '/assets/components/footer/footer.css'
118};
119
120components.gallery = {
121	selector: '.gallery',
122	styles: [
123		'/assets/components/gallery/gallery.css',
124		'/assets/components/linearicons/linearicons.css'
125	]
126};
127
128components.grid = {
129	selector: '.container, .container-fluid, .row, [class*="col-"]',
130	styles: '/assets/components/grid/grid.css'
131};
132
133components.icon = {
134	selector: '.icon',
135	styles: '/assets/components/icon/icon.css'
136};
137
138components.image = {
139	selector: '.image',
140	styles: '/assets/components/image/image.css'
141};
142
143components.input = {
144	selector: '.form-group, .input-group, .form-check, .custom-control, .form-control',
145	styles: '/assets/components/input/input.css'
146};
147
148components.intro = {
149	selector: '.intro',
150	styles: '/assets/components/intro/intro.css'
151};
152
153components.link = {
154	selector: '.link',
155	styles: '/assets/components/link/link.css'
156};
157
158components.list = {
159	selector: '.list',
160	styles: '/assets/components/list/list.css'
161};
162
163components.logo = {
164	selector: '.logo',
165	styles: '/assets/components/logo/logo.css'
166};
167
168components.nav = {
169	selector: '.nav',
170	styles: '/assets/components/nav/nav.css'
171};
172
173components.pagination = {
174	selector: '.pag',
175	styles: '/assets/components/pag/pag.css'
176};
177
178components.person = {
179	selector: '.person',
180	styles: '/assets/components/person/person.css'
181};
182
183components.post = {
184	selector: '.post',
185	styles: '/assets/components/post/post.css'
186};
187
188components.preloader = {
189	selector: '.preloader',
190	styles: '/assets/components/preloader/preloader.css'
191};
192
193components.product = {
194	selector: '.product',
195	styles: '/assets/components/product/product.css'
196};
197
198components.quote = {
199	selector: '.quote',
200	styles: '/assets/components/quote/quote.css'
201};
202
203components.rdForm = {
204	selector: '.rd-form',
205	styles: '/assets/components/rd-form/rd-form.css'
206};
207
208components.section = {
209	selector: 'section',
210	styles: '/assets/components/section/section.css'
211};
212
213components.snackbar = {
214	selector: '.snackbar',
215	styles: '/assets/components/snackbar/snackbar.css'
216};
217
218components.social = {
219	selector: '.social',
220	styles: '/assets/components/social/social.css'
221};
222
223components.tab = {
224	selector: '.tab',
225	styles: '/assets/components/tab/tab.css'
226};
227
228components.tag = {
229	selector: '.tag',
230	styles: '/assets/components/tag/tag.css'
231};
232
233components.templateInfo = {
234	selector: '.template-info',
235	styles: '/assets/components/template-info/template-info.css'
236};
237
238components.textBlock = {
239	selector: '.text-block',
240	styles: '/assets/components/text-block/text-block.css'
241};
242
243components.textDivider = {
244	selector: '.text-divider',
245	styles: '/assets/components/text-divider/text-divider.css'
246};
247
248components.thumbnail = {
249	selector: '.thumbnail',
250	styles: '/assets/components/thumbnail/thumbnail.css'
251};
252
253components.widget = {
254	selector: '.widget',
255	styles: '/assets/components/widget/widget.css'
256};
257
258// Script components
259components.accordion = {
260	selector: '.accordion',
261	styles: [
262		'/assets/components/accordion/accordion.css',
263		'/assets/components/mdi/mdi.css'
264	],
265	script: [
266		'/assets/components/jquery/jquery.min.js',
267		'/assets/components/current-device/current-device.min.js',
268		'/assets/components/multiswitch/multiswitch.min.js'
269	],
270	init: function ( nodes ) {
271		nodes.forEach( function ( node ) {
272			let
273				items = node.querySelectorAll( '.accordion-item' ),
274				click = device.ios() ? 'touchstart' : 'click';
275
276			items.forEach( function ( item ) {
277				let
278					head = item.querySelector( '.accordion-head' ),
279					body = item.querySelector( '.accordion-body' );
280
281				// MultiSwitch({
282				// 	node: head,
283				// 	targets: [ item, body ],
284				// 	isolate: node.querySelectorAll( '.accordion-head' ),
285				// 	state: item.classList.contains( 'active' ),
286				// 	event: click,
287				// });
288
289				// if ( !body.multiSwitchTarget.groups.active.state ) body.style.display = 'none';
290
291				// body.addEventListener( 'switch:active', function () {
292				// 	let $this = $( this );
293
294				// 	if ( this.multiSwitchTarget.groups.active.state ) {
295				// 		$this.stop().slideDown( 300 );
296				// 	} else {
297				// 		$this.stop().slideUp( 300 );
298				// 	}
299				// });
300
301				item.addEventListener( click, function () {
302					let $this = $( this );
303					
304					if (false === $this.hasClass('active')) {
305						$this.addClass('active')
306						$this.find('.accordion-body').stop().slideDown( 300 )
307						$this.find('.accordion-head').addClass('active')
308						$this.find('.accordion-body').addClass('active')
309					} else {
310						$this.removeClass('active')
311						$this.find('.accordion-body').stop().slideUp( 300 )
312						$this.find('.accordion-head').removeClass('active')
313						$this.find('.accordion-body').removeClass('active')
314					}
315				});
316			});
317		});
318	}
319};
320
321components.animate = {
322	selector: '[data-animate]',
323	styles: '/assets/components/animate/animate.css',
324	script: '/assets/components/current-device/current-device.min.js',
325	init: function ( nodes ) {
326		if ( window.xMode || device.macos()|| device.ios() ) {
327			nodes.forEach( function ( node ) {
328				let params = parseJSON( node.getAttribute( 'data-animate' ) );
329				node.classList.add( 'animated', params.class );
330			});
331		} else {
332			let observer = new IntersectionObserver( function ( entries ) {
333				let observer = this;
334
335				entries.forEach( function ( entry ) {
336					let
337						node = entry.target,
338						params = parseJSON( node.getAttribute( 'data-animate' ) );
339
340					if ( params.delay ) node.style.animationDelay = params.delay;
341					if ( params.duration ) node.style.animationDuration = params.duration;
342
343					if ( entry.isIntersecting ) {
344						node.classList.add( 'animated', params.class );
345						observer.unobserve( node );
346					}
347				});
348			}, {
349				threshold: .5
350			});
351
352			nodes.forEach( function ( node ) {
353				observer.observe( node );
354			});
355		}
356	}
357};
358
359components.twitterTimeline = {
360	selector: '.twitter-timeline',
361	init: function ( nodes ) {
362		var scriptSource = document.createElement("script");
363		scriptSource.src = "https://platform.twitter.com/widgets.js";
364
365		let observer = new IntersectionObserver( function ( entries ) {
366			let observer = this;
367
368			entries.forEach( function ( entry ) {
369				if ( entry.isIntersecting ) {
370					document.querySelector("head").appendChild( scriptSource );
371					observer.unobserve( entry.target );
372				}
373			});
374		}, {
375			threshold: .5
376		});
377
378		nodes.forEach( function ( node ) {
379			observer.observe( node );
380		});
381
382	}
383};
384
385components.campaignMonitor = {
386	selector: '.campaign-mailform',
387	styles: '/assets/components/rd-mailform/rd-mailform.css',
388	script: '/assets/components/jquery/jquery.min.js',
389	init: function ( nodes ) {
390		/**
391		 * @desc Check if all elements pass validation
392		 * @param {object} elements - object of items for validation
393		 * @param {object} captcha - captcha object for validation
394		 * @return {boolean}
395		 */
396		function isValidated(elements, captcha) {
397			let results, errors = 0;
398
399			if (elements.length) {
400				for (let j = 0; j < elements.length; j++) {
401
402					let $input = $(elements[j]);
403					if ((results = $input.regula('validate')).length) {
404						for (let k = 0; k < results.length; k++) {
405							errors++;
406							$input.siblings(".form-validation").text(results[k].message).parent().addClass("has-error");
407						}
408					} else {
409						$input.siblings(".form-validation").text("").parent().removeClass("has-error")
410					}
411				}
412
413				if (captcha) {
414					if (captcha.length) {
415						return validateReCaptcha(captcha) && errors === 0
416					}
417				}
418
419				return errors === 0;
420			}
421			return true;
422		}
423
424		let $nodes = $(nodes);
425
426		for ( let i = 0; i < $nodes.length; i++ ) {
427			let $campaignItem = $($nodes[i]);
428
429			$campaignItem.on('submit', $.proxy(function (e) {
430				e.preventDefault();
431
432				let data = {},
433					url = this.attr('action'),
434					dataArray = this.serializeArray(),
435					$output = $("#" + $nodes.attr("data-form-output")),
436					$this = $(this);
437
438				for ( let i = 0; i < dataArray.length; i++) {
439					data[dataArray[i].name] = dataArray[i].value;
440				}
441
442				$.ajax({
443					data: data,
444					url: url,
445					dataType: 'jsonp',
446					error: function (resp, text) {
447						$output.html('Server error: ' + text);
448
449						setTimeout(function () {
450							$output.removeClass("active");
451						}, 4000);
452					},
453					success: function (resp) {
454						$output.html(resp.Message).addClass('active');
455
456						setTimeout(function () {
457							$output.removeClass("active");
458						}, 6000);
459					},
460					beforeSend: function (data) {
461						// Stop request if inputs are invalid
462						if ( window.xMode || !isValidated( $this.find( '[data-constraints]' ) ) )
463							return false;
464
465						$output.html('Submitting...').addClass('active');
466					}
467				});
468
469				// Clear inputs after submit
470				let inputs = $this[0].getElementsByTagName('input');
471				for (let i = 0; i < inputs.length; i++) {
472					inputs[i].value = '';
473					let label = document.querySelector( '[for="'+ inputs[i].getAttribute( 'id' ) +'"]' );
474					if( label ) label.classList.remove( 'focus', 'not-empty' );
475				}
476
477				return false;
478			}, $campaignItem));
479		}
480	}
481};
482
483components.counter = {
484	selector: '[data-counter]',
485	styles: '/assets/components/counter/counter.css',
486	script: [
487		'/assets/components/util/util.min.js',
488		'/assets/components/counter/counter.min.js',
489	],
490	init: function ( nodes ) {
491		let observer = new IntersectionObserver( function ( entries ) {
492			let observer = this;
493
494			entries.forEach( function ( entry ) {
495				let node = entry.target;
496
497				if ( entry.isIntersecting ) {
498					node.counter.run();
499					observer.unobserve( node );
500				}
501			});
502		}, {
503			rootMargin: '0px',
504			threshold: 1.0
505		});
506
507		nodes.forEach( function ( node ) {
508			let counter = aCounter( Object.assign( {
509				node: node,
510				duration: 1000
511			}, parseJSON( node.getAttribute( 'data-counter' ) ) ) );
512
513			if ( window.xMode ) {
514				counter.run();
515			} else {
516				observer.observe( node );
517			}
518		})
519	}
520};
521
522components.countdown = {
523	selector: '[ data-countdown ]',
524	styles: '/assets/components/countdown/countdown.css',
525	script: [
526		'/assets/components/util/util.min.js',
527		'/assets/components/progress-circle/progress-circle.min.js',
528		'/assets/components/countdown/countdown.min.js'
529	],
530	init: function ( nodes ) {
531		nodes.forEach( function ( node ) {
532			aCountdown( Object.assign( {
533				node:  node,
534				tick:  100
535			}, parseJSON( node.getAttribute( 'data-countdown' ) ) ) );
536		} )
537	}
538};
539
540components.lightgallery = {
541	selector: '[data-lightgallery]',
542	styles: '/assets/components/lightgallery/lightgallery.css',
543	script: [
544		'/assets/components/jquery/jquery.min.js',
545		'/assets/components/lightgallery/lightgallery.min.js',
546		'/assets/components/util/util.min.js'
547	],
548	init: function ( nodes ) {
549		if ( !window.xMode ) {
550			nodes.forEach( function ( node ) {
551				node = $( node );
552				let
553					defaults = {
554						thumbnail: true,
555						selector: '.lightgallery-item',
556						youtubePlayerParams: {
557							modestbranding: 1,
558							showinfo: 0,
559							rel: 0,
560							controls: 0
561						},
562						vimeoPlayerParams: {
563							byline : 0,
564							portrait : 0,
565							color : 'A90707'
566						}
567					},
568					options = parseJSON( node.attr( 'data-lightgallery' ) );
569
570				node.lightGallery( Util.merge( [ defaults, options ] ) );
571			});
572		}
573	}
574};
575
576components.mailchimp = {
577	selector: '.mailchimp-mailform',
578	styles: '/assets/components/rd-mailform/rd-mailform.css',
579	script: '/assets/components/jquery/jquery.min.js',
580	init: function ( nodes ) {
581		let $nodes = $( nodes );
582
583		for ( let i = 0; i < $nodes.length; i++ ) {
584			let
585				$mailchimpItem = $($nodes[i]),
586				$email = $mailchimpItem.find('input[type="email"]');
587
588			// Required by MailChimp
589			$mailchimpItem.attr('novalidate', 'true');
590			$email.attr('name', 'EMAIL');
591
592			$mailchimpItem.on('submit', $.proxy( function ( $email, event ) {
593				event.preventDefault();
594
595				let
596					$this = this,
597					data = {},
598					url = $this.attr('action').replace('/post?', '/post-json?').concat('&c=?'),
599					dataArray = $this.serializeArray(),
600					$output = $("#" + $this.attr("data-form-output"));
601
602				for ( let i = 0; i < dataArray.length; i++ ) {
603					data[dataArray[i].name] = dataArray[i].value;
604				}
605
606				$.ajax({
607					data: data,
608					url: url,
609					dataType: 'jsonp',
610					error: function (resp, text) {
611						$output.html('Server error: ' + text);
612
613						setTimeout(function () {
614							$output.removeClass("active");
615						}, 4000);
616					},
617					success: function (resp) {
618						$output.html(resp.msg).addClass('active');
619						$email[0].value = '';
620						var $label = $('[for="'+ $email.attr( 'id' ) +'"]');
621						if ( $label.length ) $label.removeClass( 'focus not-empty' );
622
623						setTimeout(function () {
624							$output.removeClass("active");
625						}, 6000);
626					},
627					beforeSend: function (data) {
628						var isValidated = (function () {
629							var results, errors = 0;
630							var elements = $this.find('[data-constraints]');
631							var captcha = null;
632							if (elements.length) {
633								for (var j = 0; j < elements.length; j++) {
634
635									var $input = $(elements[j]);
636									if ((results = $input.regula('validate')).length) {
637										for (var k = 0; k < results.length; k++) {
638											errors++;
639											$input.siblings(".form-validation").text(results[k].message).parent().addClass("has-error");
640										}
641									} else {
642										$input.siblings(".form-validation").text("").parent().removeClass("has-error")
643									}
644								}
645
646								if (captcha) {
647									if (captcha.length) {
648										return validateReCaptcha(captcha) && errors === 0
649									}
650								}
651
652								return errors === 0;
653							}
654							return true;
655						})();
656
657						// Stop request if builder or inputs are invalid
658						if ( window.xMode || !isValidated ) return false;
659
660						$output.html('Submitting...').addClass('active');
661					}
662				});
663
664				return false;
665			}, $mailchimpItem, $email ));
666		}
667	}
668};
669
670components.modal = {
671	selector: '[data-modal]',
672	styles: '/assets/components/modal/modal.css',
673	script: [
674		'/assets/components/jquery/jquery.min.js',
675		'/assets/components/bootstrap/js/popper.js',
676		'/assets/components/bootstrap/js/bootstrap.min.js'
677	],
678	init: function ( nodes ) {
679		nodes.forEach( function ( node ) {
680			let params = parseJSON( node.getAttribute( 'data-modal' ) );
681			node.addEventListener( 'click', function () {
682				$( params.target ).modal();
683			} );
684		});
685	}
686};
687
688components.multiswitch = {
689	selector: '[data-multi-switch]',
690	script: [
691		'/assets/components/current-device/current-device.min.js',
692		'/assets/components/multiswitch/multiswitch.min.js'
693	],
694	init: function ( nodes ) {
695		//let click = device.ios() ? 'touchstart' : 'click';
696		let click = 'click';
697
698		nodes.forEach( function ( node ) {
699			if ( node.tagName === 'A' ) {
700				node.addEventListener( click, function ( event ) {
701					event.preventDefault();
702				});
703			}
704
705			MultiSwitch( Object.assign( {
706				node: node,
707				event: click,
708			}, parseJSON( node.getAttribute( 'data-multi-switch' ) ) ) );
709		});
710	}
711};
712
713components.nav = {
714	selector: '.nav',
715	styles: '/assets/components/nav/nav.css',
716	script: [
717		'/assets/components/jquery/jquery.min.js',
718		'/assets/components/bootstrap/js/popper.js',
719		'/assets/components/bootstrap/js/bootstrap.min.js'
720	],
721	init: function ( nodes ) {
722		nodes.forEach( function ( node ) {
723			$( node ).on( 'click', function ( event ) {
724				event.preventDefault();
725				$( this ).tab( 'show' );
726			});
727
728			$( node ).find( 'a[data-toggle="tab"]' ).on( 'shown.bs.tab', function () {
729				window.dispatchEvent( new Event( 'resize' ) );
730			});
731		});
732	}
733};
734
735components.owlCarousel = {
736	selector: '.owl-carousel',
737	styles: [
738		'/assets/components/owl-carousel/owl.carousel.css',
739		'/assets/components/mdi/mdi.css'
740	],
741	script: [
742		'/assets/components/jquery/jquery.min.js',
743		'/assets/components/owl-carousel/owl.carousel.min.js',
744		'/assets/components/util/util.min.js'
745	],
746	init: function ( nodes ) {
747		nodes.forEach( function ( node ) {
748			let
749				params = parseJSON( node.getAttribute( 'data-owl' ) ),
750				defaults = {
751					items: 1,
752					margin: 30,
753					loop: false,
754					mouseDrag: true,
755					stagePadding: 0,
756					nav: false,
757					navText: [],
758					dots: false,
759					autoplay: false,
760					autoplayHoverPause: true
761				},
762				xMode = {
763					autoplay: false,
764					loop: false,
765					mouseDrag: false
766				},
767				generated = {
768					autoplay: node.getAttribute( 'data-autoplay' ) !== 'false',
769					loop: node.getAttribute( 'data-loop' ) !== 'false',
770					mouseDrag: node.getAttribute( 'data-mouse-drag' ) !== 'false',
771					responsive: {}
772				},
773				aliaces = [ '-', '-sm-', '-md-', '-lg-', '-xl-', '-xxl-' ],
774				values =  [ 0, 576, 768, 992, 1200, 1600 ],
775				responsive = generated.responsive;
776
777			for ( let j = 0; j < values.length; j++ ) {
778				responsive[ values[ j ] ] = {};
779
780				for ( let k = j; k >= -1; k-- ) {
781					if ( !responsive[ values[ j ] ][ 'items' ] && node.getAttribute( 'data' + aliaces[ k ] + 'items' ) ) {
782						responsive[ values[ j ] ][ 'items' ] = k < 0 ? 1 : parseInt( node.getAttribute( 'data' + aliaces[ k ] + 'items' ), 10 );
783					}
784					if ( !responsive[ values[ j ] ][ 'stagePadding' ] && responsive[ values[ j ] ][ 'stagePadding' ] !== 0 && node.getAttribute( 'data' + aliaces[ k ] + 'stage-padding' ) ) {
785						responsive[ values[ j ] ][ 'stagePadding' ] = k < 0 ? 0 : parseInt( node.getAttribute( 'data' + aliaces[ k ] + 'stage-padding' ), 10 );
786					}
787					if ( !responsive[ values[ j ] ][ 'margin' ] && responsive[ values[ j ] ][ 'margin' ] !== 0 && node.getAttribute( 'data' + aliaces[ k ] + 'margin' ) ) {
788						responsive[ values[ j ] ][ 'margin' ] = k < 0 ? 30 : parseInt( node.getAttribute( 'data' + aliaces[ k ] + 'margin' ), 10 );
789					}
790				}
791			}
792
793			node.owl = $( node );
794			$( node ).owlCarousel( Util.merge( window.xMode ? [ defaults, params, generated, xMode ] : [ defaults, params, generated ] ) );
795		});
796	}
797};
798
799components.progressLinear = {
800	selector: '.progress-linear',
801	styles: '/assets/components/progress-linear/progress-linear.css',
802	script: [
803		'/assets/components/util/util.min.js',
804		'/assets/components/counter/counter.min.js'
805	],
806	init: function ( nodes ) {
807		let observer = new IntersectionObserver( function ( entries ) {
808			let observer = this;
809
810			entries.forEach( function ( entry ) {
811				let node = entry.target;
812
813				if ( entry.isIntersecting ) {
814					node.counter.run();
815					observer.unobserve( node );
816				}
817			});
818		}, {
819			rootMargin: '0px',
820			threshold: 1.0
821		});
822
823		nodes.forEach( function ( node ) {
824			let
825				bar = node.querySelector( '.progress-linear-bar' ),
826				counter = node.counter = aCounter({
827					node: node.querySelector( '.progress-linear-counter' ),
828					duration: 3000,
829					onStart: function ( value ) {
830						bar.style.width = this.params.to +'%';
831					}
832				});
833
834			if ( window.xMode ) {
835				counter.run();
836			} else {
837				observer.observe( node );
838			}
839		});
840	}
841};
842
843components.regula = {
844	selector: '[data-constraints]',
845	styles: '/assets/components/regula/regula.css',
846	script: [
847		'/assets/components/jquery/jquery.min.js',
848		'/assets/components/regula/regula.min.js'
849	],
850	init: function ( nodes ) {
851		let elements = $( nodes );
852
853		// Custom validator - phone number
854		regula.custom({
855			name: 'PhoneNumber',
856			defaultMessage: 'Неверный формат',
857			validator: function() {
858				return /^((\+?375|(8[\s\-]?0))[\s\-]?)(\d[\s\-]?){9}$/i.test( this.value );
859			}
860		});
861
862		regula.custom({
863			name: 'FIO',
864			defaultMessage: 'Неверный формат',
865			validator: function() {
866				return this.value.split(" ").length - 1 >= 2;
867			}
868		});
869
870		regula.custom({
871			name: 'PhoneLength',
872			defaultMessage: 'Введенное значение должно быть более 9 цифр',
873			validator: function() {
874				return this.value.length >= 9;
875			}
876		});
877
878		regula.custom({
879			name: 'CustomEmail',
880			defaultMessage: 'Неверный формат',
881			validator: function() {
882				if ( this.value === '' ) return true;
883				else return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/i.test( this.value );
884			}
885		});
886
887		for (let i = 0; i < elements.length; i++) {
888			let o = $(elements[i]), v;
889			o.addClass("form-control-has-validation").after("<span class='form-validation'></span>");
890			v = o.parent().find(".form-validation");
891			if (v.is(":last-child")) o.addClass("form-control-last-child");
892		}
893
894		elements.on('input change propertychange blur', function (e) {
895			let $this = $(this), results;
896
897			if (e.type !== "blur") if (!$this.parent().hasClass("has-error")) return;
898			if ($this.parents('.rd-mailform').hasClass('success')) return;
899
900			if (( results = $this.regula('validate') ).length) {
901				for (let i = 0; i < results.length; i++) {
902					$this.siblings(".form-validation").text(results[i].message).parent().addClass("has-error");
903				}
904			} else {
905				$this.siblings(".form-validation").text("").parent().removeClass("has-error")
906			}
907		}).regula('bind');
908
909		let regularConstraintsMessages = [
910			{
911				type: regula.Constraint.Required,
912				newMessage: "Поле обязательно для заполнения."
913			},
914			{
915				type: regula.Constraint.Email,
916				newMessage: "Адрес электронной почты недействителен."
917			},
918			{
919				type: regula.Constraint.Numeric,
920				newMessage: "Можно вводить только цифры"
921			},
922			{
923				type: regula.Constraint.Selected,
924				newMessage: "Please choose an option."
925			}
926		];
927
928
929		for (let i = 0; i < regularConstraintsMessages.length; i++) {
930			let regularConstraint = regularConstraintsMessages[i];
931
932			regula.override({
933				constraintType: regularConstraint.type,
934				defaultMessage: regularConstraint.newMessage
935			});
936		}
937	}
938};
939
940components.rdCalendar = {
941	selector: '.rd-calendar',
942	styles: [
943		'/assets/components/rd-calendar/rd-calendar.css',
944		'/assets/components/prompt/prompt.css',
945		'/assets/components/mdi/mdi.css',
946	],
947	script: [
948		'/assets/components/jquery/jquery.min.js',
949		'/assets/components/rd-calendar/rd-calendar-fixed.min.js',
950		'/assets/components/prompt/prompt.js'
951	],
952	init: function ( nodes ) {
953		function initEvents ( calendar ) {
954			let events = calendar.querySelectorAll( '.rdc-table_has-events' );
955
956			events.forEach( function ( node ) {
957				node.addEventListener( 'click', function ( event ) {
958					event.preventDefault();
959					event.stopPropagation();
960
961					const calendarEventsContainer = document.querySelector('.calendar-events__container')
962					calendarEventsContainer.innerHTML = node.querySelector( '.rdc-table_events' ).innerHTML
963					calendarEventsContainer.classList.add('active')
964
965					const productImage = document.querySelector('.product-img')
966					if (productImage) {
967						productImage.classList.add('blur')
968					}
969				});
970			});
971		}
972
973		nodes.forEach( function ( node ) {
974			$( node ).rdCalendar({
975				days: $( node ).attr("data-days") ? $( node ).attr("data-days").split(/\s?,\s?/i) : [ 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ],
976				months: $( node ).attr("data-months") ? $( node ).attr("data-months").split(/\s?,\s?/i) : [ 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ]
977			});
978			initEvents( node );
979			$('.rdc-next, .rdc-prev').on( "click", initEvents.bind( null, node ) );
980		});
981	}
982};
983
984components.rdMailform = {
985	selector: '.rd-mailform',
986	styles: [
987		'/assets/components/rd-mailform/rd-mailform.css',
988		'/assets/components/font-awesome/font-awesome.css'
989	],
990	script: [
991		'/assets/components/jquery/jquery.min.js',
992		'/assets/components/rd-mailform/rd-mailform.min.js',
993	],
994	init: function ( nodes ) {
995		let i, j, k,
996			$captchas = $( nodes ).find( '.recaptcha' ),
997			msg = {
998				'MF000': 'Successfully sent!',
999				'MF001': 'Recipients are not set!',
1000				'MF002': 'Form will not work locally!',
1001				'MF003': 'Please, define email field in your form!',
1002				'MF004': 'Please, define type of your form!',
1003				'MF254': 'Something went wrong with PHPMailer!',
1004				'MF255': 'Aw, snap! Something went wrong.'
1005			};
1006
1007		if ( $captchas.length ) {
1008			$.getScript("//www.google.com/recaptcha/api.js?onload=onloadCaptchaCallback&render=explicit&hl=en");
1009		}
1010
1011		/**
1012		 * @desc Check if all elements pass validation
1013		 * @param {object} elements - object of items for validation
1014		 * @param {object} captcha - captcha object for validation
1015		 * @return {boolean}
1016		 */
1017		function isValidated(elements, captcha) {
1018			let results, errors = 0;
1019
1020			if (elements.length) {
1021				for (let j = 0; j < elements.length; j++) {
1022
1023					let $input = $(elements[j]);
1024					if ((results = $input.regula('validate')).length) {
1025						for (k = 0; k < results.length; k++) {
1026							errors++;
1027							$input.siblings(".form-validation").text(results[k].message).parent().addClass("has-error");
1028						}
1029					} else {
1030						$input.siblings(".form-validation").text("").parent().removeClass("has-error")
1031					}
1032				}
1033
1034				if (captcha) {
1035					if (captcha.length) {
1036						return validateReCaptcha(captcha) && errors === 0
1037					}
1038				}
1039
1040				return errors === 0;
1041			}
1042			return true;
1043		}
1044
1045		/**
1046		 * @desc Validate google reCaptcha
1047		 * @param {object} captcha - captcha object for validation
1048		 * @return {boolean}
1049		 */
1050		function validateReCaptcha(captcha) {
1051			let captchaToken = captcha.find('.g-recaptcha-response').val();
1052
1053			if (captchaToken.length === 0) {
1054				captcha
1055					.siblings('.form-validation')
1056					.html('Please, prove that you are not robot.')
1057					.addClass('active');
1058				captcha
1059					.closest('.form-wrap')
1060					.addClass('has-error');
1061
1062				captcha.on('propertychange', function () {
1063					let $this = $(this),
1064						captchaToken = $this.find('.g-recaptcha-response').val();
1065
1066					if (captchaToken.length > 0) {
1067						$this
1068							.closest('.form-wrap')
1069							.removeClass('has-error');
1070						$this
1071							.siblings('.form-validation')
1072							.removeClass('active')
1073							.html('');
1074						$this.off('propertychange');
1075					}
1076				});
1077
1078				return false;
1079			}
1080
1081			return true;
1082		}
1083
1084		/**
1085		 * @desc Initialize Google reCaptcha
1086		 */
1087		window.onloadCaptchaCallback = function () {
1088			for (let i = 0; i < $captchas.length; i++) {
1089				let
1090					$captcha = $($captchas[i]),
1091					resizeHandler = (function() {
1092						let
1093							frame = this.querySelector( 'iframe' ),
1094							inner = this.firstElementChild,
1095							inner2 = inner.firstElementChild,
1096							containerRect = null,
1097							frameRect = null,
1098							scale = null;
1099
1100						inner2.style.transform = '';
1101						inner.style.height = 'auto';
1102						inner.style.width = 'auto';
1103
1104						containerRect = this.getBoundingClientRect();
1105						frameRect = frame.getBoundingClientRect();
1106						scale = containerRect.width/frameRect.width;
1107
1108						if ( scale < 1 ) {
1109							inner2.style.transform = 'scale('+ scale +')';
1110							inner2.style.transformOrigin = 'top left';
1111							inner.style.height = ( frameRect.height * scale ) + 'px';
1112							inner.style.width = ( frameRect.width * scale ) + 'px';
1113						}
1114					}).bind( $captchas[i] );
1115
1116				grecaptcha.render(
1117					$captcha.attr('id'),
1118					{
1119						sitekey: $captcha.attr('data-sitekey'),
1120						size: $captcha.attr('data-size') ? $captcha.attr('data-size') : 'normal',
1121						theme: $captcha.attr('data-theme') ? $captcha.attr('data-theme') : 'light',
1122						callback: function () {
1123							$('.recaptcha').trigger('propertychange');
1124						}
1125					}
1126				);
1127
1128				$captcha.after("<span class='form-validation'></span>");
1129
1130				if ( $captchas[i].hasAttribute( 'data-auto-size' ) ) {
1131					resizeHandler();
1132					window.addEventListener( 'resize', resizeHandler );
1133				}
1134			}
1135		};
1136
1137		for ( i = 0; i < nodes.length; i++ ) {
1138			let
1139				$form = $(nodes[i]),
1140				formHasCaptcha = false;
1141
1142			$form.attr('novalidate', 'novalidate').ajaxForm({
1143				data: {
1144					"form-type": $form.attr("data-form-type") || "contact",
1145					"counter": i
1146				},
1147				beforeSubmit: function (arr, $form, options) {
1148					if ( window.xMode ) return;
1149
1150					let
1151						form = $(nodes[this.extraData.counter]),
1152						inputs = form.find("[data-constraints]"),
1153						output = $("#" + form.attr("data-form-output")),
1154						captcha = form.find('.recaptcha'),
1155						captchaFlag = true;
1156
1157					output.removeClass("active error success");
1158
1159					if (isValidated(inputs, captcha)) {
1160
1161						// veify reCaptcha
1162						if (captcha.length) {
1163							let captchaToken = captcha.find('.g-recaptcha-response').val(),
1164								captchaMsg = {
1165									'CPT001': 'Please, setup you "site key" and "secret key" of reCaptcha',
1166									'CPT002': 'Something wrong with google reCaptcha'
1167								};
1168
1169							formHasCaptcha = true;
1170
1171							$.ajax({
1172								method: "POST",
1173								url: "components/rd-mailform/reCaptcha.php",
1174								data: {'g-recaptcha-response': captchaToken},
1175								async: false
1176							})
1177								.done(function (responceCode) {
1178									if (responceCode !== 'CPT000') {
1179										if (output.hasClass("snackbar")) {
1180											output.html('<div class="snackbar-inner"><div class="snackbar-title"><span class="icon snackbar-icon fa-check"></span>'+ captchaMsg[responceCode] +'</div></div>');
1181
1182											setTimeout(function () {
1183												output.removeClass("active");
1184											}, 3500);
1185
1186											captchaFlag = false;
1187										} else {
1188											output.html(captchaMsg[responceCode]);
1189										}
1190
1191										output.addClass("active");
1192									}
1193								});
1194						}
1195
1196						if (!captchaFlag) {
1197							return false;
1198						}
1199
1200						form.addClass('form-in-process');
1201
1202						if (output.hasClass("snackbar")) {
1203							output.html('<div class="snackbar-inner"><div class="snackbar-title"><span class="icon snackbar-icon fa-circle-o-notch fa-spin"></span>Sending</div></div>');
1204							output.addClass("active");
1205						}
1206					} else {
1207						return false;
1208					}
1209				},
1210				error: function (result) {
1211					if ( window.xMode ) return;
1212
1213					let
1214						output = $("#" + $(nodes[this.extraData.counter]).attr("data-form-output")),
1215						form = $(nodes[this.extraData.counter]);
1216
1217					output.text(msg[result]);
1218					form.removeClass('form-in-process');
1219
1220					if (formHasCaptcha) {
1221						grecaptcha.reset();
1222					}
1223				},
1224				success: function (result) {
1225					if ( window.xMode ) return;
1226
1227					let
1228						form = $(nodes[this.extraData.counter]),
1229						output = $("#" + form.attr("data-form-output")),
1230						select = form.find('select');
1231
1232					form
1233						.addClass('success')
1234						.removeClass('form-in-process');
1235
1236					if (formHasCaptcha) {
1237						grecaptcha.reset();
1238					}
1239
1240					result = result.length === 5 ? result : 'MF255';
1241					output.text(msg[result]);
1242
1243					if (result === "MF000") {
1244						if (output.hasClass("snackbar")) {
1245							output.html('<div class="snackbar-inner"><div class="snackbar-title"><span class="icon snackbar-icon fa-check"></span>'+ msg[result] +'</div></div>');
1246						} else {
1247							output.addClass("active success");
1248						}
1249					} else {
1250						if (output.hasClass("snackbar")) {
1251							output.html('<div class="snackbar-inner"><div class="snackbar-title"><span class="icon snackbar-icon fa-exclamation-triangle"></span>'+ msg[result] +'</div></div>');
1252						} else {
1253							output.addClass("active error");
1254						}
1255					}
1256
1257					form.clearForm();
1258
1259					if (select.length) {
1260						select.select2("val", "");
1261					}
1262
1263					form.find('input, textarea').trigger('blur');
1264
1265					setTimeout(function () {
1266						output.removeClass("active error success");
1267						form.removeClass('success');
1268					}, 3500);
1269				}
1270			});
1271		}
1272	}
1273};
1274
1275components.rdNavbar = {
1276	selector: '.rd-navbar',
1277	styles: [
1278		'/assets/components/rd-navbar/rd-navbar.css'
1279	],
1280	script: [
1281		'/assets/components/jquery/jquery.min.js',
1282		'/assets/components/util/util.min.js',
1283		'/assets/components/current-device/current-device.min.js',
1284		'/assets/components/rd-navbar/rd-navbar-fixed.min.js'
1285	],
1286	dependencies: 'currentDevice',
1287	init: function ( nodes ) {
1288		nodes.forEach( function ( node ) {
1289			let
1290				backButtons = node.querySelectorAll( '.navbar-navigation-back-btn' ),
1291				params = parseJSON( node.getAttribute( 'data-rd-navbar' ) ),
1292				defaults = {
1293					stickUpClone: false,
1294					anchorNav: false,
1295					autoHeight: false,
1296					stickUpOffset: '1px',
1297					responsive: {
1298						0: {
1299							layout: 'rd-navbar-fixed',
1300							deviceLayout: 'rd-navbar-fixed',
1301							focusOnHover: 'ontouchstart' in window,
1302							stickUp: false
1303						},
1304						992: {
1305							layout: 'rd-navbar-fixed',
1306							deviceLayout: 'rd-navbar-fixed',
1307							focusOnHover: 'ontouchstart' in window,
1308							stickUp: false
1309						},
1310						1200: {
1311							layout: 'rd-navbar-fullwidth',
1312							deviceLayout: 'rd-navbar-fullwidth',
1313							stickUp: true,
1314							stickUpOffset: '1px',
1315							autoHeight: true
1316						}
1317					},
1318					callbacks: {
1319						onStuck: function () {
1320							document.documentElement.classList.add( 'rd-navbar-stuck' );
1321						},
1322						onUnstuck: function () {
1323							document.documentElement.classList.remove( 'rd-navbar-stuck' );
1324						},
1325						onDropdownToggle: function () {
1326							if ( this.classList.contains( 'opened' ) ) {
1327								this.parentElement.classList.add( 'overlaid' );
1328							} else {
1329								this.parentElement.classList.remove( 'overlaid' );
1330							}
1331						},
1332						onDropdownClose: function () {
1333							this.parentElement.classList.remove( 'overlaid' );
1334						}
1335					}
1336				},
1337				xMode = {
1338					stickUpClone: false,
1339					anchorNav: false,
1340					responsive: {
1341						0: {
1342							stickUp: false,
1343							stickUpClone: false
1344						},
1345						992: {
1346							stickUp: false,
1347							stickUpClone: false
1348						},
1349						1200: {
1350							stickUp: false,
1351							stickUpClone: false
1352						}
1353					},
1354					callbacks: {
1355						onDropdownOver: function () { return false; }
1356					}
1357				},
1358				navbar = node.RDNavbar = new RDNavbar( node, Util.merge( window.xMode ? [ defaults, params, xMode ] : [ defaults, params ] ) );
1359
1360			if ( backButtons.length ) {
1361				backButtons.forEach( function ( btn ) {
1362					btn.addEventListener( 'click', function () {
1363						let
1364							submenu = this.closest( '.rd-navbar-submenu' ),
1365							parentmenu = submenu.parentElement;
1366
1367						navbar.dropdownToggle.call( submenu, navbar );
1368					});
1369				});
1370			}
1371		})
1372	}
1373};
1374
1375components.rdRange = {
1376	selector: '.rd-range',
1377	styles: '/assets/components/rd-range/rd-range.css',
1378	script: [
1379		'/assets/components/jquery/jquery.min.js',
1380		'/assets/components/rd-range/rd-range.min.js'
1381	],
1382	init: function ( nodes ) {
1383		nodes.forEach( function ( node ) {
1384			$( node ).RDRange({});
1385		});
1386	}
1387};
1388
1389components.rdSearch = {
1390	selector: '[data-rd-search]',
1391	styles: '/assets/components/rd-search/rd-search.css',
1392	script: '/assets/components/rd-search/rd-search.js',
1393	init: function ( nodes ) {
1394		nodes.forEach( function ( node ) {
1395			new RDSearch( Object.assign( {
1396				form: node,
1397				handler: 'components/rd-search/rd-search.php',
1398				output: '.rd-search-results',
1399			}, parseJSON( node.getAttribute( 'data-rd-search' ) ) ) );
1400		});
1401	}
1402};
1403
1404components.select2 = {
1405	selector: '.select2',
1406	styles: '/assets/components/select2/select2.css',
1407	script: [
1408		'/assets/components/jquery/jquery.min.js',
1409		'/assets/components/select2/select2.min.js'
1410	],
1411	init: function ( nodes ) {
1412		nodes.forEach( function ( node ) {
1413			let
1414				params = parseJSON( node.getAttribute( 'data-select2-options' ) ),
1415				defaults = {
1416					dropdownParent: $( node ).parent(),
1417					minimumResultsForSearch: Infinity
1418				};
1419
1420			$( node ).select2( $.extend( defaults, params ) );
1421			$( node ).on('select2:select', function (e) {
1422				var data = e.params.data;
1423				window.location.href = data.id
1424			})
1425		});
1426	}
1427};
1428
1429components.spinner = {
1430	selector: '[data-spinner]',
1431	styles: '/assets/components/spinner/spinner.css',
1432	script: [
1433		'/assets/components/jquery/jquery.min.js',
1434		'/assets/components/jquery/jquery-ui.min.js'
1435	],
1436	init: function ( nodes ) {
1437		nodes.forEach( function ( node ) {
1438			let
1439				params = parseJSON( node.getAttribute( 'data-spinner' ) ),
1440				defaults = {
1441					min: 0,
1442					step: 1
1443				};
1444
1445			$( node ).spinner( $.extend( defaults, params ) );
1446		});
1447	}
1448};
1449
1450components.swiper = {
1451	selector: '[data-swiper]',
1452	styles: '/assets/components/swiper/swiper.css',
1453	script: [
1454		'/assets/components/jquery/jquery.min.js',
1455		'/assets/components/swiper/swiper.min.js',
1456		'/assets/components/util/util.min.js'
1457	],
1458	init: function ( nodes ) {
1459		nodes.forEach( function ( node ) {
1460			let
1461				slides = node.querySelectorAll( '.swiper-slide[data-slide-bg]' ),
1462				params = parseJSON( node.getAttribute( 'data-swiper' ) ),
1463				nextItem = node.querySelector( '.swiper-next-item' ),
1464				pagination = {};
1465
1466			if ( params.paginationImage === true ) {
1467				pagination = {
1468					type: 'bullets',
1469					renderBullet: function ( index, className ) {
1470						let
1471							indexActive = this.params.loop === true ? index + 1 : index,
1472							backgroundImage = window.getComputedStyle( this.slides[ indexActive ] ).getPropertyValue("background-image");
1473						return "<span style='background-image:" + backgroundImage + "' class='" + className + "'></span>";
1474					}
1475				}
1476			}
1477
1478			let
1479				defaults = {
1480					speed: 1000,
1481					loop: true,
1482					simulateTouch: false,
1483					pagination: Object.assign( {
1484						type: 'bullets',
1485						el: '.swiper-pagination',
1486						clickable: true,
1487						formatFractionCurrent: function ( number ) {
1488							return number < 10 ? '0' + number : number;
1489						},
1490						formatFractionTotal: function ( number ) {
1491							return number < 10 ? '0' + number : number;
1492						},
1493						renderFraction: function (currentClass, totalClass) {
1494							return '<span class="' + currentClass + '"></span>' +
1495								'/' +
1496								'<span class="' + totalClass + '"></span>';
1497						}
1498					}, pagination ),
1499					navigation: {
1500						nextEl: '.swiper-button-next',
1501						prevEl: '.swiper-button-prev'
1502					},
1503					autoplay: {
1504						delay: 5000
1505					},
1506					on: {
1507						init: function () {
1508							if ( nextItem ) {
1509								let
1510									activeElement = this.params.loop === true ? this.realIndex + 2 : this.realIndex,
1511									backgroundImage = window.getComputedStyle( this.slides[ activeElement ] ).getPropertyValue("background-image");
1512								nextItem.style.backgroundImage = backgroundImage;
1513								nextItem.querySelector( '.swiper-next-item-tag' ).textContent = this.slides[ activeElement ].getAttribute( 'data-swiper-tag' );
1514							}
1515						},
1516						slideChange: function () {
1517							if ( nextItem ) {
1518								let
1519									activeElement = this.params.loop === true ? this.realIndex + 2 : this.realIndex,
1520									backgroundImage = window.getComputedStyle( this.slides[ activeElement ] ).getPropertyValue( "background-image" );
1521								nextItem.style.backgroundImage = backgroundImage;
1522								nextItem.querySelector( '.swiper-next-item-tag' ).textContent = this.slides[ activeElement ].getAttribute( 'data-swiper-tag' );
1523							}
1524						}
1525					}
1526				},
1527				xMode = {
1528					autoplay: false,
1529					loop: false,
1530					simulateTouch: false
1531				};
1532
1533			// Set background image for slides with `data-slide-bg` attribute for Novi Builder
1534			slides.forEach( function ( slide ) {
1535				slide.style.backgroundImage = 'url(' + slide.getAttribute( "data-slide-bg" ) + ')';
1536			});
1537
1538			new Swiper( node, Util.merge( window.xMode ? [ defaults, params, xMode ] : [ defaults, params ] ) );
1539		});
1540	}
1541};
1542
1543components.slick = {
1544	selector: '.slick-slider',
1545	styles: '/assets/components/slick/slick.css',
1546	script: [
1547		'/assets/components/jquery/jquery.min.js',
1548		'/assets/components/slick/slick.min.js'
1549	],
1550	init: function ( nodes ) {
1551		nodes.forEach( function ( node ) {
1552			let
1553				breakpoint = { sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1600 }, // slick slider uses desktop first principle
1554				responsive = [];
1555
1556			// Making responsive parameters
1557			for ( let key in breakpoint ) {
1558				if ( node.hasAttribute( 'data-slick-'+ key ) ) {
1559					responsive.push({
1560						breakpoint: breakpoint[ key ],
1561						settings: parseJSON( node.getAttribute( 'data-slick-'+ key ) )
1562					});
1563				}
1564			}
1565
1566			$( node ).slick({ responsive: responsive });
1567		});
1568	}
1569};
1570
1571components.table = {
1572	selector: '.table',
1573	styles: '/assets/components/table/table.css'
1574};
1575
1576components.termList = {
1577	selector: '.term-list',
1578	styles: '/assets/components/term-list/term-list.css'
1579};
1580
1581components.tooltip = {
1582	selector: '[data-tooltip]',
1583	styles: '/assets/components/tooltip/tooltip.css',
1584	script: [
1585		'/assets/components/jquery/jquery.min.js',
1586		'/assets/components/bootstrap/js/popper.js',
1587		'/assets/components/bootstrap/js/bootstrap.min.js'
1588	],
1589	init: function ( nodes ) {
1590		nodes.forEach( function ( node ) {
1591			let options = parseJSON( node.getAttribute( 'data-tooltip' ) );
1592			$( node ).tooltip( options )
1593		})
1594	}
1595};
1596
1597components.toTop = {
1598	selector: 'html',
1599	styles: '/assets/components/to-top/to-top.css',
1600	script: '/assets/components/jquery/jquery.min.js',
1601	init: function () {
1602		if ( !window.xMode ) {
1603			let node = document.createElement( 'div' );
1604			node.className = 'to-top mdi-chevron-up';
1605			document.body.appendChild( node );
1606
1607			node.addEventListener( 'mousedown', function () {
1608				this.classList.add( 'active' );
1609
1610				$( 'html, body' ).stop().animate( { scrollTop:0 }, 500, 'swing', (function () {
1611					this.classList.remove( 'active' );
1612				}).bind( this ));
1613			});
1614
1615			document.addEventListener( 'scroll', function () {
1616				if ( window.scrollY > window.innerHeight ) node.classList.add( 'show' );
1617				else node.classList.remove( 'show' );
1618			});
1619		}
1620	}
1621};
1622
1623
1624/**
1625 * Wrapper to eliminate json errors
1626 * @param {string} str - JSON string
1627 * @returns {object} - parsed or empty object
1628 */
1629function parseJSON ( str ) {
1630	try {
1631		if ( str )  return JSON.parse( str );
1632		else return {};
1633	} catch ( error ) {
1634		console.warn( error );
1635		return {};
1636	}
1637}
1638
1639/**
1640 * Get tag of passed data
1641 * @param {*} data
1642 * @return {string}
1643 */
1644function objectTag ( data ) {
1645	return Object.prototype.toString.call( data ).slice( 8, -1 );
1646}
1647
1648/**
1649 * Merging of two objects
1650 * @param {Object} source
1651 * @param {Object} merged
1652 * @return {Object}
1653 */
1654function merge( source, merged ) {
1655	for ( let key in merged ) {
1656		let tag = objectTag( merged[ key ] );
1657
1658		if ( tag === 'Object' ) {
1659			if ( typeof( source[ key ] ) !== 'object' ) source[ key ] = {};
1660			source[ key ] = merge( source[ key ], merged[ key ] );
1661		} else if ( tag !== 'Null' ) {
1662			source[ key ] = merged[ key ];
1663		}
1664	}
1665
1666	return source;
1667}
1668
1669/**
1670 * Strict merging of two objects. Merged only parameters from the original object and with the same data type. Merge only simple data types, arrays and objects.
1671 * @param source
1672 * @param merged
1673 * @return {object}
1674 */
1675function strictMerge( source, merged ) {
1676	for ( let key in source ) {
1677		let
1678			sTag = objectTag( source[ key ] ),
1679			mTag = objectTag( merged[ key ] );
1680
1681		if ( [ 'Object', 'Array', 'Number', 'String', 'Boolean', 'Null', 'Undefined' ].indexOf( sTag ) > -1 ) {
1682			if ( sTag === 'Object' && sTag === mTag ) {
1683				source[ key ] = strictMerge( source[ key ], merged[ key ] );
1684			} else if ( mTag !== 'Undefined' && ( sTag === 'Undefined' || sTag === 'Null' || sTag === mTag ) ) {
1685				source[ key ] = merged[ key ];
1686			}
1687		}
1688	}
1689
1690	return source;
1691}
1692
1693// Main
1694window.addEventListener( 'load', function () {
1695	new ZemezCore({
1696		debug: true,
1697		components: components,
1698		observeDOM: window.xMode,
1699		IEHandler: function ( version ) {
1700			document.documentElement.classList.add( 'ie-'+ version );
1701		}
1702	});
1703});

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.