PageSourceSearch

https://globalkids.ua/view/js/main.js?v=ZRF1t

js globalkids.ua collected 2026-10-07 23:04:39 UTC 74,016 bytes, 2,024 lines download raw bytes

1// Sticky header
2function wfStickyHeader() {
3	const header = document.querySelector('.header');
4	let scrollPosition = window.scrollY;
5
6	var windowSizes = getPageSize();
7	var windowWidth = windowSizes[2];
8
9	if (windowWidth < 1000 && $('.mobile-menu').hasClass('active')) {
10		header.classList.remove('scrolled');
11		header.classList.remove('wf_scrolled_shadow');
12	} else {
13		if (scrollPosition > 100) {
14			header.classList.add('scrolled');
15		} else {
16			header.classList.remove('scrolled');
17		}
18		
19		if (
20			$('body').hasClass('template_search') || 
21			$('body').hasClass('template_404') || 
22			$('body').hasClass('template_contacts')
23		) {
24			if (scrollPosition > $('header').outerHeight()) {
25				header.classList.add('wf_scrolled_shadow');
26			} else {
27				header.classList.remove('wf_scrolled_shadow');
28			}
29		} else if (
30			$('body').hasClass('template_workshops') || 
31			$('body').hasClass('workshop_template') || 
32			$('body').hasClass('template_blog') || 
33			$('body').hasClass('article_template')
34		) {
35			if (scrollPosition > $('header').outerHeight() + $('.header__bottom').outerHeight() - 50) {
36				header.classList.add('wf_scrolled_shadow');
37			} else {
38				header.classList.remove('wf_scrolled_shadow');
39			}
40		} else if (
41			$('body').hasClass('template_schools') || 
42			$('body').hasClass('template_homepage') || 
43			$('body').hasClass('template_kindergartens') || 
44			$('body').hasClass('template_preparation') || 
45			$('body').hasClass('template_career')
46		) {
47			if ($('.main > section').length > 0) {
48				if (scrollPosition > $('.main > section:first').outerHeight() + (!$('body').hasClass('template_homepage') ? 100 : 0)) {
49					header.classList.add('wf_scrolled_shadow');
50				} else {
51					header.classList.remove('wf_scrolled_shadow');
52				}
53			}
54		}
55	}
56}
57
58// Workshops filter
59function wfWorkshopsFilter() {
60	$('.groups-all-list__list').addClass('wf_groups_list_loading');
61
62	setTimeout(function() {
63		var formData = new FormData();
64		formData.append('method', 'ajaxWorkshopsFilter');
65		formData.append('ajax_file', 'Workshop');
66
67		if ($('.groups-all-list__location').length > 0) {
68			formData.append('filter_location', $('[name="singleLocationSelect"]:checked').val());
69		}
70
71		if ($('.groups-all-list__direction').length > 0) {
72			formData.append('filter_type', $('[name="singleDirectionSelect"]:checked').val());
73		}
74
75		if ($('.groups-all-list__age').length > 0) {
76			formData.append('filter_age', $('[name="singleAgeSelect"]:checked').val());
77		}
78
79		$.ajax({
80			url: '/',
81			type: "POST",
82			dataType: "json",
83			cache: false,
84			contentType: false,
85			processData: false,
86			data: formData,
87			success: function(response) {
88				$('.groups-all-list__list').removeClass('wf_groups_list_loading');
89
90				if (response.success) {
91					$('.groups-all-list__list').html(response.success);
92				}
93			},
94			error: function(xhr, ajaxOptions, thrownError) {	
95				console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
96			}
97		});
98	}, 100);
99
100	return false;
101}
102
103$(document).ready(function () {
104	// Reinit mask symbol. Then I can print zero symbol by default
105	$.jMaskGlobals.translation["d"] = $.jMaskGlobals.translation["0"];
106    delete $.jMaskGlobals.translation["0"];
107	
108	/* GLOBAL */
109	// Phone mask
110	if ($('.wf_main_form__form-input[type="tel"]').length > 0) {
111		$('.wf_main_form__form-input[type="tel"]').mask("+38 (0dd) ddd-dd-dd", {
112            placeholder: "+38 (0XX) XXX-XX-XX"
113        });
114	}
115	if ($('.popup-register__form-input[type="tel"]').length > 0) {
116		$('.popup-register__form-input[type="tel"]').mask("+38 (0dd) ddd-dd-dd", {
117            placeholder: "+38 (0XX) XXX-XX-XX"
118        });
119	}
120
121	// Sticky header
122	wfStickyHeader();
123	
124    // Header. View/hide phone and search field
125	$('#header-phone').click(function(){
126		if ($('#data-phone').hasClass('visible__data-phone')) {
127			$('#data-phone').removeClass('visible__data-phone');
128		} else {
129			$('#data-phone').addClass('visible__data-phone');
130
131			fbq('track', 'Contact');
132		}
133
134		$('#data-search').addClass('visible__data-search');
135	});
136	$('#header-search').click(function(){
137		if ($('#data-search').hasClass('visible__data-search')) {
138			$('#data-search').removeClass('visible__data-search');
139		} else {
140			$('#data-search').addClass('visible__data-search');
141		}
142		
143		$('#data-phone').addClass('visible__data-phone');
144
145		var searchText = $('#data-search').val();
146		var searchTextLength = searchText.length;
147
148		$('#data-search').focus();
149		document.getElementById('data-search').setSelectionRange(searchTextLength, searchTextLength);
150	});
151
152	// Add body class for touch devices onload page
153	if (isTouchDevice()) {
154		$('body').addClass('touch_device');
155	}
156
157	// View section animation onscroll
158	var startReveal = true;
159
160	if ($('body').hasClass('article_template') || $('body').hasClass('workshop_template')) {
161		startReveal = false;
162
163		$('.main > section:not(.header__bottom)').css('visibility', 'visible');
164	}
165	
166	if (startReveal) {
167		window.sr = ScrollReveal({
168			reset: false
169		});
170
171		// Колись звідси виключались .routine, .middle-reviews і .middle-day: у них
172		// був sticky-scrolljacking, а ScrollReveal перезаписує інлайн style.cssText
173		// секції й затирає section.style.height, чим ламав pin-зону. Pin-зон більше
174		// немає в жодного блоку, тож секції звичайні й показуються, як решта.
175		// .about-counters має власну count-up анімацію (IntersectionObserver + RAF
176		// нижче) і виключена з anti-FOUC visibility:hidden — ScrollReveal не повинен
177		// керувати її видимістю, інакше лічильники не запустяться.
178		var commonCards = '.main > section:not(.header__bottom):not(.about-counters)';
179
180		sr.reveal(commonCards, {
181			origin: 'top',
182			duration: 1000,
183			distance: '20px',
184			opacity: 0,
185			viewFactor: 0.1
186		});
187	}
188
189	/* ABOUT PAGE — count-up анімація лічильників (.about-counters__num[data-count]).
190	   Секція .about-counters виключена з ScrollReveal і anti-FOUC (visibility:hidden),
191	   тому лічильники видимі одразу і цей IntersectionObserver запускає анімацію
192	   0 → data-count при появі у viewport. Один observer на всі лічильники. */
193	var aboutCounters = document.querySelectorAll('.about-counters__num[data-count]');
194	if (aboutCounters.length > 0) {
195		var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
196
197		function animateCounter(el) {
198			var target = parseInt(el.getAttribute('data-count'), 10) || 0;
199			if (target <= 0) {
200				el.textContent = String(target);
201				return;
202			}
203			if (reduceMotion) {
204				el.textContent = String(target);
205				return;
206			}
207			var duration = 1500;
208			var startTime = null;
209
210			function step(timestamp) {
211				if (startTime === null) {
212					startTime = timestamp;
213				}
214				var progress = Math.min((timestamp - startTime) / duration, 1);
215				// ease-out cubic
216				var eased = 1 - Math.pow(1 - progress, 3);
217				el.textContent = String(Math.floor(eased * target));
218				if (progress < 1) {
219					requestAnimationFrame(step);
220				} else {
221					el.textContent = String(target);
222				}
223			}
224
225			requestAnimationFrame(step);
226		}
227
228		if ('IntersectionObserver' in window) {
229			var counterObserver = new IntersectionObserver(function (entries, observer) {
230				entries.forEach(function (entry) {
231					if (entry.isIntersecting) {
232						animateCounter(entry.target);
233						observer.unobserve(entry.target);
234					}
235				});
236			}, { threshold: 0.4 });
237
238			aboutCounters.forEach(function (el) {
239				counterObserver.observe(el);
240			});
241		} else {
242			// Fallback: без IO одразу кінцеві значення.
243			aboutCounters.forEach(function (el) {
244				el.textContent = String(parseInt(el.getAttribute('data-count'), 10) || 0);
245			});
246		}
247	}
248
249	/* ABOUT PAGE. Team (view/template/modules/about_team.php)
250	   Фільтр за категоріями і «Більше». Скільки карток сховано до «Більше»,
251	   вирішує CSS; тут лише знімаємо обмеження класом is-expanded. Вікно
252	   «Про себе» відкриває Fancybox сам — за атрибутом data-fancybox на картці. */
253	$('.about-team__filter').click(function(){
254		var filter = $(this).attr('data-team-filter');
255		var team = $(this).closest('.about-team');
256
257		team.find('.about-team__filter').removeClass('is-active').attr('aria-pressed', 'false');
258		$(this).addClass('is-active').attr('aria-pressed', 'true');
259
260		team.find('.about-team__category').each(function(){
261			$(this).prop('hidden', filter !== 'all' && $(this).attr('data-team-category') !== filter);
262		});
263
264		return false;
265	});
266
267	$('.about-team__more').click(function(){
268		$(this).closest('.about-team__category').addClass('is-expanded');
269
270		return false;
271	});
272
273	/* MOBILE NAV */
274	// View/hide mobile nav
275	const mobileMenuItems = document.querySelectorAll('.mobile-menu__main .header__menu-item');
276	const mobileBtn = document.querySelector('.mobile-btn');
277	const mobileMenu = document.querySelector('.mobile-menu');
278	const closeBtn = document.querySelector('.mobile-menu__header-close');
279
280	mobileBtn.addEventListener('click', function () {
281		$.scrollLock(true);
282
283		mobileMenu.classList.add('active');
284		// При відкритті меню розгортаємо підменю, що містить активний підпункт
285		// (closeMobileMenu() скидає всі is-open, тому відновлюємо стан тут).
286		expandActiveMobileSubmenus();
287	});
288
289	// Розгорнути підменю 1-го рівня, якщо всередині є активний підпункт
290	// (.header__menu-subitem.active_nav_li) — щоб на мобільному меню поточна
291	// гілка була відкритою одразу при завантаженні/відкритті меню.
292	function expandActiveMobileSubmenus() {
293		document.querySelectorAll('.mobile-menu__main .header__menu-item').forEach(item => {
294			const sublist = item.querySelector(':scope > .block-inner > .header__menu-sublist, :scope > .header__menu-sublist');
295			if (!sublist) return;
296			if (!sublist.querySelector('.header__menu-subitem.active_nav_li')) return;
297			const toggle = item.querySelector(':scope > .block-inner > .header__menu-img');
298			item.classList.add('is-open');
299			if (toggle) toggle.setAttribute('aria-expanded', 'true');
300		});
301	}
302
303	// Toggle mobile sub-menu по клику на стрілку (тільки для мобільного меню)
304	mobileMenuItems.forEach(item => {
305		const sublist = item.querySelector(':scope > .block-inner > .header__menu-sublist, :scope > .header__menu-sublist');
306		if (!sublist) return;
307		const toggle = item.querySelector(':scope > .block-inner > .header__menu-img');
308		if (!toggle) return;
309		toggle.setAttribute('role', 'button');
310		toggle.setAttribute('aria-expanded', 'false');
311		toggle.setAttribute('tabindex', '0');
312		const onToggle = function (e) {
313			e.preventDefault();
314			e.stopPropagation();
315			const opened = item.classList.toggle('is-open');
316			toggle.setAttribute('aria-expanded', opened ? 'true' : 'false');
317		};
318		toggle.addEventListener('click', onToggle);
319		toggle.addEventListener('keydown', function (e) {
320			if (e.key === 'Enter' || e.key === ' ') onToggle(e);
321		});
322	});
323
324	// Одразу розгорнути активну гілку при завантаженні сторінки
325	expandActiveMobileSubmenus();
326
327	// Закрити мобільне меню та скинути всі відкриті підменю
328	function closeMobileMenu() {
329		$.scrollLock(false);
330
331		mobileMenu.classList.remove('active');
332		document.querySelectorAll('.mobile-menu__main .header__menu-item.is-open').forEach(i => {
333			i.classList.remove('is-open');
334			const t = i.querySelector(':scope > .block-inner > .header__menu-img');
335			if (t) t.setAttribute('aria-expanded', 'false');
336		});
337	}
338	closeBtn.addEventListener('click', closeMobileMenu);
339
340	// Якір на поточну сторінку (підпункти «Про нас») не перезавантажує сторінку,
341	// тож меню лишилось би відкритим поверх секції, а скрол — заблокованим.
342	// Закриваємо до переходу: обробник спрацьовує раніше за стрибок до якоря.
343	mobileMenu.querySelectorAll('a[href*="#"]').forEach(function (link) {
344		link.addEventListener('click', function () {
345			if (link.hash && link.pathname.replace(/\/$/, '') === location.pathname.replace(/\/$/, '')) {
346				closeMobileMenu();
347			}
348		});
349	});
350
351	/* FORMS */
352	// Main form
353	$('.wf_main_form__form').submit(function(){
354		var form = $(this);
355		var trackingData = getTrackingParams();
356		var name = $.trim(form.find('.wf_field_name').val());
357		var lastname = $.trim(form.find('.wf_field_lastname').val());
358		var phone = $.trim(form.find('.wf_field_phone').val());
359		var email = $.trim(form.find('.wf_field_email').val());
360		var comment = $.trim(form.find('.wf_main_form__form-input--textarea').val());
361
362		if (phone.length < 19) {
363			form.find('.wf_field_phone').addClass('wf_field_error');
364
365			errorModal(phoneRequiredField);
366		} else {
367			viewMainPreloader();
368
369			var formData = new FormData();
370			formData.append('method', 'ajaxSendOrder');
371			formData.append('ajax_file', 'Conversions');
372			formData.append('name', name);
373			formData.append('lastname', lastname);
374			formData.append('phone', phone);
375			formData.append('email', email);
376			formData.append('comment', comment);
377            formData.append('full_url', $(location).attr('href'));
378			formData.append('monitor_width', screen.width);
379			formData.append('monitor_height', screen.height);
380            formData.append('conversion_id', 1);
381
382			if (trackingData) {
383				for (var key in trackingData) {
384					if (trackingData.hasOwnProperty(key)) {
385						formData.append(key, trackingData[key]); 
386					}
387				}
388        	}
389
390			$.ajax({
391				url: '/',
392		        type: "POST",
393		        dataType: "json",
394		        cache: false,
395		        contentType: false,
396		        processData: false,
397		        data: formData,
398				success: function(response) {
399					hiddenMainPreloader();
400
401					if (response.error) {
402						errorModal(response.error);
403					} else if (response.success) {
404						// Clear data
405						form.find('.wf_field_name').val('');
406						form.find('.wf_field_lastname').val('');
407						form.find('.wf_field_phone').val('');
408						form.find('.wf_field_email').val('');
409						form.find('.wf_main_form__form-input--textarea').val('');
410
411						// Successfully notification
412						successfullyModal(response.success, response.success_text);
413
414						fbq('track', 'Lead');
415					}
416				},
417				error: function(xhr, ajaxOptions, thrownError) {	
418					console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
419
420					hiddenMainPreloader();
421				}
422			});
423		}
424
425		return false;
426	});
427
428	// Newsletter
429	$('.news-list__form').submit(function(){
430		var form = $(this);
431
432		viewMainPreloader();
433
434		var email = $.trim(form.find('#news-list-email').val());
435
436		var formData = new FormData();
437		formData.append('method', 'ajaxSendOrder');
438		formData.append('ajax_file', 'Conversions');
439		formData.append('email', email);
440		formData.append('full_url', $(location).attr('href'));
441		formData.append('monitor_width', screen.width);
442		formData.append('monitor_height', screen.height);
443		formData.append('conversion_id', 3);
444
445		$.ajax({
446			url: '/',
447			type: "POST",
448			dataType: "json",
449			cache: false,
450			contentType: false,
451			processData: false,
452			data: formData,
453			success: function(response) {
454				hiddenMainPreloader();
455
456				if (response.error) {
457					errorModal(response.error);
458				} else if (response.success) {
459					// Clear data
460					form.find('#news-list-email').val('');
461
462					// Successfully notification
463					successfullyModal(response.success, response.success_text);
464
465					fbq('track', 'Lead');
466				}
467			},
468			error: function(xhr, ajaxOptions, thrownError) {	
469				console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
470
471				hiddenMainPreloader();
472			}
473		});
474
475		return false;
476	});
477
478	// Open own popup. Main big popup
479	$('.wf_btn_main_popup').click(function(){
480		$.scrollLock(true);
481
482		if ($('.wf-popup-big').length > 0) {
483			$('.wf-popup-big').toggleClass('is-visible');
484		} else {
485			$('.popup-simple-form').toggleClass('is-visible');
486		}
487
488		return false;
489	});
490
491	// Main big popup. View onload page
492	if ($('.wf-popup-big').length > 0) {
493		if ($('.wf-popup-big').attr('data-view-onload-page') == 'yes') {
494			var popupTimeoutSeconds = $('.wf-popup-big').attr('data-onload-page-timeout-second');
495			var popupUniqueKey = $('.wf-popup-big').attr('data-unique-key');
496			var popupUniqueKeyCookieName = 'wf_popup_' + popupUniqueKey;
497
498			var popupUniqueKeyCookieValue = WFCookie.get(popupUniqueKeyCookieName);
499
500			if (popupUniqueKeyCookieValue === null) {
501				if (popupTimeoutSeconds == '') {
502					popupTimeoutSeconds = 0;
503				} else {
504					popupTimeoutSeconds = parseInt(popupTimeoutSeconds, 10);
505				}
506				
507				if (popupTimeoutSeconds > 0) {
508					setTimeout(function() {
509						$.scrollLock(true);
510
511						$('.wf-popup-big').toggleClass('is-visible');
512
513						// "Save" cookie
514						var date = new Date(Date.now() + 24 * 365 * 10 * 60 * 60 * 1000); // 10 years
515						var options = { expires: date };
516						WFCookie.set(popupUniqueKeyCookieName, '1', options);
517					}, popupTimeoutSeconds * 1000);
518				} else {
519					$.scrollLock(true);
520
521					$('.wf-popup-big').toggleClass('is-visible');
522
523					// "Save" cookie
524					var date = new Date(Date.now() + 24 * 365 * 10 * 60 * 60 * 1000); // 10 years
525					var options = { expires: date };
526					WFCookie.set(popupUniqueKeyCookieName, '1', options);
527				}
528			}
529		}
530	}
531
532	// Open own popup. Additional request popup. Open
533	$('.wf_btn_simple_popup').click(function(){
534		if ($(this).hasClass('popup-btn')) {
535			if ($(this).closest('.popup').length > 0) {
536				$(this).closest('.popup').removeClass('is-visible');
537			}
538		}
539
540		$.scrollLock(true);
541
542		$('.popup-simple-form').toggleClass('is-visible');
543
544		return false;
545	});
546
547	// Плаваюча кнопка «Залишити заявку»: кнопка — лише текст, фігура й смайлик
548	// під нею — окремі SVG. Клік по них теж відкриває попап.
549	$('.fixed-btn svg').click(function(){
550		$(this).siblings('.wf_btn_simple_popup').trigger('click');
551
552		return false;
553	});
554
555	// Open own popup. Additional request popup. Submit
556	$('.popup-simple-form .popup-btn-submit').click(function(){
557		$('.popup-simple-form').removeClass('is-visible');
558
559		var form = $(this).closest('.popup');
560		var trackingData = getTrackingParams();
561		var name = $.trim(form.find('.wf_field_name').val());
562		var lastname = $.trim(form.find('.wf_field_lastname').val());
563		var phone = $.trim(form.find('.wf_field_phone').val());
564		var email = $.trim(form.find('.wf_field_email').val());
565		var comment = $.trim(form.find('.popup-register__form-input--textarea').val());
566
567		if (phone.length < 19) {
568			form.find('.wf_field_phone').addClass('wf_field_error');
569
570			errorModal(phoneRequiredField);
571		} else {
572			viewMainPreloader();
573
574			var formData = new FormData();
575			formData.append('method', 'ajaxSendOrder');
576			formData.append('ajax_file', 'Conversions');
577			formData.append('name', name);
578			formData.append('lastname', lastname);
579			formData.append('phone', phone);
580			formData.append('email', email);
581			formData.append('comment', comment);
582            formData.append('full_url', $(location).attr('href'));
583			formData.append('monitor_width', screen.width);
584			formData.append('monitor_height', screen.height);
585            formData.append('conversion_id', 2);
586
587			if (trackingData) {
588				for (var key in trackingData) {
589					if (trackingData.hasOwnProperty(key)) {
590						formData.append(key, trackingData[key]); 
591					}
592				}
593        	}
594
595			$.ajax({
596				url: '/',
597		        type: "POST",
598		        dataType: "json",
599		        cache: false,
600		        contentType: false,
601		        processData: false,
602		        data: formData,
603				success: function(response) {
604					hiddenMainPreloader();
605
606					if (response.error) {
607						errorModal(response.error);
608					} else if (response.success) {
609						$.scrollLock(false);
610						
611						// Clear data
612						form.find('.wf_field_name').val('');
613						form.find('.wf_field_lastname').val('');
614						form.find('.wf_field_phone').val('');
615						form.find('.wf_field_email').val('');
616						form.find('.popup-register__form-input--textarea').val('');
617
618						// Successfully notification
619						successfullyModal(response.success, response.success_text);
620
621						fbq('track', 'Lead');
622					}
623				},
624				error: function(xhr, ajaxOptions, thrownError) {	
625					console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
626
627					hiddenMainPreloader();
628				}
629			});
630		}
631
632		return false;
633	});
634
635	// Open popup - Brukhovychi open door. Additional request popup. Open
636	$('.wf_btn_brukhovychi_open_door_popup').click(function(){
637		if ($(this).hasClass('popup-btn')) {
638			if ($(this).closest('.popup').length > 0) {
639				$(this).closest('.popup').removeClass('is-visible');
640			}
641		}
642
643		$.scrollLock(true);
644
645		$('.popup-brukhovychi-open-door').toggleClass('is-visible');
646
647		return false;
648	});
649
650	// Open own popup. Additional request popup. Submit
651	$('.popup-brukhovychi-open-door .popup-btn-submit').click(function(){
652		$('.popup-brukhovychi-open-door').removeClass('is-visible');
653
654		var form = $(this).closest('.popup');
655
656		var name = $.trim(form.find('.wf_field_name').val());
657		var lastname = $.trim(form.find('.wf_field_lastname').val());
658		var phone = $.trim(form.find('.wf_field_phone').val());
659		var email = $.trim(form.find('.wf_field_email').val());
660		var comment = $.trim(form.find('.popup-register__form-input--textarea').val());
661
662		if (phone.length < 19) {
663			form.find('.wf_field_phone').addClass('wf_field_error');
664
665			errorModal(phoneRequiredField);
666		} else {
667			viewMainPreloader();
668
669			var formData = new FormData();
670			formData.append('method', 'ajaxSendOrder');
671			formData.append('ajax_file', 'Conversions');
672			formData.append('name', name);
673			formData.append('lastname', lastname);
674			formData.append('phone', phone);
675			formData.append('email', email);
676			formData.append('comment', comment);
677            formData.append('full_url', $(location).attr('href'));
678			formData.append('monitor_width', screen.width);
679			formData.append('monitor_height', screen.height);
680            formData.append('conversion_id', 5);
681
682			$.ajax({
683				url: '/',
684		        type: "POST",
685		        dataType: "json",
686		        cache: false,
687		        contentType: false,
688		        processData: false,
689		        data: formData,
690				success: function(response) {
691					hiddenMainPreloader();
692
693					if (response.error) {
694						errorModal(response.error);
695					} else if (response.success) {
696						$.scrollLock(false);
697						
698						// Clear data
699						form.find('.wf_field_name').val('');
700						form.find('.wf_field_lastname').val('');
701						form.find('.wf_field_phone').val('');
702						form.find('.wf_field_email').val('');
703						form.find('.popup-register__form-input--textarea').val('');
704
705						// Successfully notification
706						successfullyModal(response.success, response.success_text);
707
708						fbq('track', 'Lead');
709					}
710				},
711				error: function(xhr, ajaxOptions, thrownError) {	
712					console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
713
714					hiddenMainPreloader();
715				}
716			});
717		}
718
719		return false;
720	});
721
722	// Career popup. Open popup
723	$('.career-looking-list > div').click(function(){
724		if ($('.popup-career').length > 0) {
725			$.scrollLock(true);
726
727			$('.popup-career').toggleClass('is-visible');
728
729			// Vacancy title for conversion
730			var vacancyTitle = $(this).find('.career-looking-list-item-text__title').text();
731
732			if ($(this).find('.wf_career_looking').length > 0) {
733				vacancyTitle += '. ' + $(this).find('.wf_career_looking').text();
734			}
735
736			$('.popup-career').attr('data-career-title', wfFixQuotes(vacancyTitle));
737		}
738
739		return false;
740	});
741
742	$('.open_career_popup_vacancy_page').click(function(){
743		if ($('.popup-career').length > 0) {
744			$.scrollLock(true);
745
746			$('.popup-career').toggleClass('is-visible');
747
748			// Vacancy title for conversion
749			var vacancyTitle = $('h1.title').text();
750
751			$('.popup-career').attr('data-career-title', wfFixQuotes(vacancyTitle));
752		}
753
754		return false;
755	});
756
757	// Career popup. Submit
758	$('.popup-career .popup-btn-submit').click(function(){
759		$('.popup-career').removeClass('is-visible');
760
761		var form = $(this).closest('.popup');
762
763		var name = $.trim(form.find('.wf_field_name').val());
764		var lastname = $.trim(form.find('.wf_field_lastname').val());
765		var phone = $.trim(form.find('.wf_field_phone').val());
766
767		if (phone.length < 19) {
768			form.find('.wf_field_phone').addClass('wf_field_error');
769
770			errorModal(phoneRequiredField);
771		} else {
772			viewMainPreloader();
773
774			var formData = new FormData();
775			formData.append('method', 'ajaxSendOrder');
776			formData.append('ajax_file', 'Conversions');
777			formData.append('name', name);
778			formData.append('lastname', lastname);
779			formData.append('phone', phone);
780			formData.append('comment', form.attr('data-career-title'));
781            formData.append('full_url', $(location).attr('href'));
782			formData.append('monitor_width', screen.width);
783			formData.append('monitor_height', screen.height);
784            formData.append('conversion_id', 4);
785
786			$.ajax({
787				url: '/',
788		        type: "POST",
789		        dataType: "json",
790		        cache: false,
791		        contentType: false,
792		        processData: false,
793		        data: formData,
794				success: function(response) {
795					hiddenMainPreloader();
796
797					if (response.error) {
798						errorModal(response.error);
799					} else if (response.success) {
800						$.scrollLock(false);
801						
802						// Clear data
803						form.find('.wf_field_name').val('');
804						form.find('.wf_field_lastname').val('');
805						form.find('.wf_field_phone').val('');
806
807						// Successfully notification
808						successfullyModal(response.success, response.success_text);
809
810						fbq('track', 'Lead');
811					}
812				},
813				error: function(xhr, ajaxOptions, thrownError) {	
814					console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
815
816					hiddenMainPreloader();
817				}
818			});
819		}
820
821		return false;
822	});
823
824	/* HOME PAGE */
825	// Main video. Desctop. Open video by button
826	$('.home-top__link--play').fancybox({
827		// src: '<video autoplay playsinline controls id="main_video_fancy_mobile"><source src="/video/home.mp4" type="video/mp4">Your browser does not support HTML5 video.</video>',
828		src: '<video autoplay playsinline controls id="main_video_fancy_mobile"><source src="/video/global_1veresnya2.mp4" type="video/mp4">Your browser does not support HTML5 video.</video>',
829		defaultType: 'html'
830	});
831
832	// Main video. Desctop. Open video by video (right block)
833	$('.wf_home_top_inner_right').click(function(){
834		$('.home-top__link--play-mobile').trigger('click');
835
836		return false;
837	});
838
839	// Main video. Mobile. Open video by video
840	$('.home-top__video').click(function(){
841		$('.home-top__link--play-mobile').trigger('click');
842
843		return false;
844	});
845
846	// Main video. Mobile. Open video by button
847	$('.home-top__link--play-mobile').fancybox({
848		// src: '<video autoplay playsinline controls id="main_video_fancy_mobile"><source src="/video/home.mp4" type="video/mp4">Your browser does not support HTML5 video.</video>',
849		src: '<video autoplay playsinline controls id="main_video_fancy_mobile"><source src="/video/global_1veresnya2.mp4" type="video/mp4">Your browser does not support HTML5 video.</video>',
850		defaultType: 'html'
851	});
852
853	// Locations. Open video by play btn
854	$('.home-location__videos-play').click(function(){
855		if ($(this).closest('li').find('a.home-location__videos-link').length > 0) {
856			$(this).closest('li').find('a.home-location__videos-link').trigger('click');
857		}
858
859		return false;
860	});
861
862	// Locations. Open video
863	$('a.home-location__videos-link').fancybox({
864		defaultType: 'iframe'
865	});
866
867	// FAQs
868	$('.home-faq__accordeon-btn').click(function(){
869		if ($(this).hasClass('active')) {
870			$(this).removeClass('active');
871			$(this).closest('li').find('.home-faq__accordeon-panel').css('display', 'none');
872			$(this).closest('li').removeClass('wf_faq_active');
873		} else {
874			$(this).addClass('active');
875			$(this).closest('li').find('.home-faq__accordeon-panel').css('display', 'block');
876			$(this).closest('li').addClass('wf_faq_active');
877		}
878
879		return false;
880	});
881
882	/* WORKSHOPS */
883	// Filter selects. Location
884	$('.groups-all-list__location-title').click(function(e){
885		if ($('.groups-all-list__location').attr('data-state') == 'active') {
886			$('.groups-all-list__location').attr('data-state', '');
887		} else {
888			$('.groups-all-list__location').attr('data-state', 'active');
889		}
890	});
891
892	$('.groups-all-list__location-label').click(function(e){
893		$('.groups-all-list__location-title').text($(this).text());
894		$('.groups-all-list__location').attr('data-state', '');
895
896		wfWorkshopsFilter();
897	});
898
899	// Filter selects. Type
900	$('.groups-all-list__direction-title').click(function(e){
901		if ($('.groups-all-list__direction').attr('data-state') == 'active') {
902			$('.groups-all-list__direction').attr('data-state', '');
903		} else {
904			$('.groups-all-list__direction').attr('data-state', 'active');
905		}
906	});
907
908	$('.groups-all-list__direction-label').click(function(e){
909		$('.groups-all-list__direction-title').text($(this).text());
910		$('.groups-all-list__direction').attr('data-state', '');
911
912		wfWorkshopsFilter();
913	});
914
915	// Filter selects. Age
916	$('.groups-all-list__age-title').click(function(e){
917		if ($('.groups-all-list__age').attr('data-state') == 'active') {
918			$('.groups-all-list__age').attr('data-state', '');
919		} else {
920			$('.groups-all-list__age').attr('data-state', 'active');
921		}
922	});
923
924	$('.groups-all-list__age-label').click(function(e){
925		$('.groups-all-list__age-title').text($(this).text());
926		$('.groups-all-list__age').attr('data-state', '');
927
928		wfWorkshopsFilter();
929	});
930
931	/* WORKSHOP */
932	// Add main image in right place
933	if ($('.wf_workshop_main_image').length > 0 && $('.workshop_template .group-page-header__bottom + .section_froala').length > 0) {
934		$('.workshop_template .group-page-header__bottom + .section_froala .container').append('<div class="group-
934page__content-image">' + $('.wf_workshop_main_image').html() + '</div>');
935
936		$('.wf_workshop_main_image').remove();
937	} else if ($('.wf_workshop_main_image').length > 0 && $('.workshop_template .group-page-header__bottom + .wf_workshop_main_image_mobile + .section_froala').length > 0) {
938		$('.workshop_template .group-page-header__bottom + .wf_workshop_main_image_mobile + .section_froala .container').append('<div class="group-page__content-image">' + $('.wf_workshop_main_image').html() + '</div>');
939
940		$('.wf_workshop_main_image').remove();
941	}
942
943	/* BLOG. MAIN PAGE */
944	// Load more articles
945	$('.news-list #loadMoreBtn').click(function(){
946		$('.news-list #loadMoreBtn').css('display', 'none');
947
948		var formData = new FormData();
949		formData.append('method', 'ajaxUploadArticleMore');
950		formData.append('ajax_file', 'Blog');
951		formData.append('start', $('a.news-list__item').length);
952		formData.append('limit', 6);
953
954		if ($('.news__filters-btn.active').length > 0) {
955			formData.append('category_id', $('.news__filters-btn.active').attr('data-category-id'));
956		}
957
958		$.ajax({
959			url: '/',
960			type: "POST",
961			dataType: "json",
962			cache: false,
963			contentType: false,
964			processData: false,
965			data: formData,
966			success: function(response) {
967				if (response.success) {
968					$('.news-list__inner').append(response.success);
969
970					if ($('a.news-list__item').length < parseInt($('.news-list #loadMoreBtn').attr('data-articles-total'), 10)) {
971						$('.news-list #loadMoreBtn').css('display', 'block');
972					} else {
973						$('.news-list #loadMoreBtn').remove();
974					}
975				}
976			},
977			error: function(xhr, ajaxOptions, thrownError) {	
978				console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
979			}
980		});
981	});
982
983	/* BLOG. ARTICLE */
984	// Additional format html article-text structure
985	if ($('body').hasClass('article_template')) {
986		if ($('.main .header__bottom + .section_froala').length > 0) {
987			// $('.main .header__bottom + .section_froala').addClass('new-page-info');
988			$('.main .section_froala').addClass('new-page-info');
989			$('.main .header__bottom + .section_froala').prepend('<svg xmlns="http://www.w3.org/2000/svg" width="380" height="569" viewBox="0 0 380 569" fill="none"><path d="M452.036 -1.97591e-05L447.598 4.65068L402.135 57.6463L380.485 -1.66315e-05L324.955 -1.42042e-05L308.394 71.2738L219.307 -9.58621e-06L208.699 -9.12251e-06L196.142 2.4876L240.198 115.833L239.441 120.808L-2.16239e-05 74.3021L2.70613 84.3604L216.709 199.112L1.08245 307.591L2.81436 323.922L251.348 281.85L130.978 514.274L134.658 519.141L315.321 325.22L359.053 562.294L369.877 569L408.413 320.57L466 374.647L466 98.2041L465.351 98.8531L462.969 102.098L465.351 98.8531L466 97.8797L466 -2.03695e-05L452.036 -1.97591e-05Z" fill="#D5ABFB"/></svg>');
990		}
991	}
992
993	// Add main image in right place
994	if ($('.wf_article_main_image').length > 0 && $('.main .header__bottom + .section_froala .froala_block').length > 0) {
995		$('.main .header__bottom + .section_froala .froala_block').prepend('<div class="new-page-info__image">' + $('.wf_article_main_image').html() + '</div>');
996
997		$('.wf_article_main_image').remove();
998	}
999
1000	/* PREPARATION PAGE */
1001	// Tabs
1002	$('.study__tablink').click(function(){
1003		if ($(this).hasClass('active')) {
1004			return false;
1005		}
1006
1007		var tabId = $(this).attr('data-tab');
1008
1009		$('.study__tablink').removeClass('active');
1010		$(this).addClass('active');
1011
1012		$('.study__tabcontent').css('display', 'none');
1013		$('#tab' + tabId).css('display', 'block');
1014		
1015		return false;
1016	});
1017
1018	// Scroll to link
1019	$('.offers__link--null').click(function(){
1020		if ($('#nullClass').length > 0) {
1021			$('html, body').animate({
1022				scrollTop: $('#nullClass').offset().top - $('header').outerHeight() - 30
1023			}, 500);
1024		}
1025		
1026		return false;
1027	});
1028	$('.offers__link--step').click(function(){
1029		if ($('#stepToSchool').length > 0) {
1030			$('html, body').animate({
1031				scrollTop: $('#stepToSchool').offset().top - $('header').outerHeight() - 30
1032			}, 1000);
1033		}
1034
1035		return false;
1036	});
1037
1038	// Посилання «#form» з будь-якого місця (кнопка конструктора, меню, текст
1039	// у редакторі) плавно скролять до блоку форми (form_left_side / form_right_side).
1040	// Посилання «/сторінка#form» з іншої сторінки браузер відпрацьовує сам,
1041	// шапку там враховує scroll-margin-top у wf_style.css.
1042	$(document).on('click', 'a[href*="#form"]', function(){
1043		var form = $('.wf_main_form').first();
1044
1045		if (this.hash !== '#form' || form.length === 0 ||
1046			this.pathname.replace(/\/$/, '') !== location.pathname.replace(/\/$/, '')) {
1047			return;
1048		}
1049
1050		$('html, body').animate({
1051			scrollTop: form.offset().top - $('header').outerHeight() - 30
1052		}, 500);
1053
1054		return false;
1055	});
1056	
1057	/* SLIDERS */
1058	// Gallery slider
1059	if ($('.wf_swiper_main').length > 0) {
1060		$.each($('.wf_swiper_main'), function(index, item) {
1061			var swiperId = parseInt($(item).attr('data-gallery-id'), 10);
1062
1063			var swiper = new Swiper('.wf_swiper_thumbnails_' + swiperId, {
1064				/* loop має збігатися з main-swiper (loop:false), інакше Swiper
1065				   переупорядковує слайди thumbs (index 0 їде в кінець DOM) і
1066				   активний квадратик розсинхронізується з main. */
1067				loop: false,
1068				spaceBetween: 10,
1069				slidesPerView: 1,
1070				freeMode: true,
1071				watchSlidesProgress: true,
1072				centeredSlides: true,
1073				centeredSlidesBounds: true,
1074				breakpoints: {
1075				  	320: {
1076						slidesPerView: 3
1077				  	},
1078				  	480: {
1079						slidesPerView: 3
1080				  	},
1081				  	600: {
1082						slidesPerView: 4
1083				  	},
1084				  	768: {
1085						slidesPerView: 5
1086				  	},
1087				  	1000: {
1088						slidesPerView: 7
1089				  	},
1090				  	1200: {
1091						slidesPerView: 10
1092				  	}
1093				}
1094			});
1095
1096			var swiper2 = new Swiper('.wf_swiper_main_' + swiperId, {
1097				loop: false,
1098				spaceBetween: 10,
1099				navigation: {
1100				  	nextEl: '.wf_swiper_button_next_' + swiperId,
1101				  	prevEl: '.wf_swiper_button_prev_' + swiperId,
1102				},
1103				thumbs: {
1104				  	swiper: swiper,
1105				},
1106				autoHeight: true,
1107				breakpoints: {
1108				  	1000: {
1109						autoHeight: false
1110				  	}
1111				}
1112			});
1113		});
1114	}
1115
1116	// Reviews slider
1117	if ($('.wf_reviews_slider_mobile').length > 0) {
1118		var swiperReviews = new Swiper('.wf_reviews_slider_mobile', {
1119			loop: false,
1120			spaceBetween: 30,
1121			autoHeight: true,
1122			pagination: {
1123				el: '.wf_reviews_slider_pagination_mobile',
1124				type: 'bullets'
1125			},
1126			slidesPerView: 'auto'
1127		});
1128	}
1129
1130
1131
1132
1133	// Workshops slider
1134	if ($('.additionally__slider').length > 0) {
1135		let $slider = $('.additionally__slider');
1136  		let isLeftArrowVisible = false;
1137
1138  		$slider.slick({
1139    		dots: false,
1140    		speed: 300,
1141    		slidesToShow: 3,
1142    		slidesToScroll: 1,
1143    		variableWidth: false,
1144    		prevArrow: '.additionally__slider-inner .left-arrow',
1145    		nextArrow: '.additionally__slider-inner .right-arrow',
1146    		draggable: false,
1147    		swipe: (isTouchDevice() ? true : false),
1148    		responsive: [
1149      			{
1150        			breakpoint: 901,
1151        			settings: {
1152          				slidesToShow: 2
1153        			}
1154      			},
1155      			{
1156        			breakpoint: 601,
1157        			settings: {
1158          				slidesToShow: 3
1159        			}
1160      			},
1161      			{
1162        			breakpoint: 481,
1163        			settings: {
1164          				slidesToShow: 2
1165        			}
1166      			}
1167			]
1168  		});
1169
1170		// Hide left arrow onload page
1171		if (!isTouchDevice()) {
1172			$('.additionally__slider-inner .left-arrow').hide();
1173		}
1174
1175		// Hide right arrow if slider end
1176		$slider.on('afterChange', function (event, slick, currentSlide, nextSlide) {
1177		  	if (currentSlide === slick.slideCount - 1) {
1178				$('.additionally__slider-inner .right-arrow').hide();
1179		  	} else {
1180				$('.additionally__slider-inner .right-arrow').show();
1181		  	}
1182		});
1183	  
1184		// Slider arrows
1185		$('.additionally__slider-inner .left-arrow, .additionally__slider-inner .right-arrow').on('click', function () {
1186		  	const currentSlide = $slider.slick('slickCurrentSlide');
1187	  
1188		  	if (currentSlide === 0) {
1189				isLeftArrowVisible = !isLeftArrowVisible;
1190
1191				$('.additionally__slider-inner .left-arrow').toggle(isLeftArrowVisible);
1192		  	} else {
1193				isLeftArrowVisible = true;
1194
1195				$('.additionally__slider-inner .left-arrow').show();
1196		  	}
1197		});
1198	}
1199
1200	// ── Горизонтальні стрічки з перехопленням скролу ─────────────────────────
1201	// Спільна механіка для розкладів дня: «Нульовий клас», «Школа», садочок
1202	// (.routine) і Middle school (.middle-day).
1203	//
1204	// Секція лишається блоком звичайної висоти. Скрипт слухає скрол сторінки:
1205	// щойно середина самої стрічки сходиться з серединою екрана, сторінка замирає
1206	// і колесо везе стрічку вбік. Доїхала остання картка — скрол сторінки знову
1207	// звичайний. Назад так само: повернувся до блоку — стрічка відмотується.
1208	// Перша й остання картки стоять по центру екрана.
1209	//
1210	// Курсор може бути будь-де — слухач висить на window, а не на блоці.
1211	//
1212	// На тач-екранах механіка інша — див. «Тач» нижче: сторінку там не заморожуємо
1213	// ніколи, бо вертикальний свайп і є єдиним способом гортати.
1214	//
1215	// opts: container, wrapper, slides, gap (px між картками),
1216	//       onProgress(progress) — необов'язковий, 0..1.
1217	function wfScrollConveyor(opts) {
1218		var container = opts.container;
1219		var wrapper = opts.wrapper;
1220		var slides = opts.slides;
1221		var gap = opts.gap;
1222		var onProgress = opts.onProgress;
1223
1224		// Тач чи миша. `hover: none` відсіює ноутбуки з тачскріном: там є і
1225		// колесо, і палець, і два режими одночасно билися б за позицію стрічки.
1226		var touchMode = window.matchMedia('(hover: none) and (pointer: coarse)').matches;
1227
1228		// Фіксований крок стрічки: однаковий margin у всіх, остання без нього.
1229		// !important — перебиває правила теми на кшталт
1230		// @media(max-width:768px) .swiper-slide-active { margin-right: 110px }:
1231		// зі зміною активного пункту крок стрибав і стрічку смикало.
1232		slides.forEach(function (slide, idx) {
1233			slide.style.setProperty('margin-right', idx < slides.length - 1 ? gap + 'px' : '0px', 'important');
1234			slide.style.flexShrink = '0';
1235			slide.style.width = 'auto'; // override swiper-bundle.min.css width:100%
1236		});
1237		wrapper.style.display = 'flex';
1238
1239		// Перша й остання картки центруються у viewport.
1240		var setCentering = function () {
1241			var centerOffset = (container.clientWidth / 2) - (slides[0].clientWidth / 2);
1242			wrapper.style.paddingLeft = centerOffset + 'px';
1243			wrapper.style.paddingRight = centerOffset + 'px';
1244			wrapper.style.boxSizing = 'border-box';
1245		};
1246		setCentering();
1247
1248		var maxTranslate = 0;   // скільки пікселів має проїхати стрічка
1249		var target = 0;         // куди їдемо (оновлює колесо/палець)
1250		var current = 0;        // де зараз (доганяє target у rAF)
1251		var raf = null;
1252
1253		// Шлях = відстань між центрами першої й останньої картки: перша стоїть
1254		// по центру екрана на старті, остання має стати по центру в кінці.
1255		//
1256		// Не через scrollWidth: Chrome не враховує в ньому кінцевий padding
1257		// контейнера, і остання картка зупинялась біля правого краю. І не через
1258		// getBoundingClientRect: ScrollReveal показує секції через scale(0.9),
1259		// тож у момент виміру rect дає розміри на 10% менші — стрічка не
1260		// доїжджала на цілу картку. offsetLeft/offsetWidth — це верстка без
1261		// трансформацій, тому надійні тут тільки вони.
1262		var measure = function () {
1263			var first = slides[0];
1264			var last = slides[slides.length - 1];
1265			maxTranslate = Math.max(0,
1266				(last.offsetLeft + last.offsetWidth / 2) - (first.offsetLeft + first.offsetWidth / 2));
1267		};
1268
1269		var render = function () {
1270			// Плавне наздоганяння: клацання колеса дає стрибок у ~100px, без
1271			// згладжування стрічку смикає.
1272			current += (target - current) * 0.2;
1273			if (Math.abs(target - current) < 0.5) current = target;
1274			wrapper.style.transform = 'translate3d(' + (-current) + 'px, 0, 0)';
1275			if (onProgress) onProgress(maxTranslate > 0 ? current / maxTranslate : 0);
1276			raf = current !== target ? window.requestAnimationFrame(render) : null;
1277		};
1278		var kick = function () {
1279			if (raf === null) raf = window.requestAnimationFrame(render);
1280		};
1281
1282		// Момент захоплення: середина самої стрічки дійшла до середини екрана.
1283		// Міряємо саме стрічку, а не секцію: секція — це ще заголовок, сітка
1284		// кроків і текст, тож її середина припадає бозна-куди. У Middle стрічка
1285		// стоїть угорі секції, у розкладі «Нульового класу» — внизу, і на
1286		// невисоких екранах різниця виходила в цілий екран: скрол перехоплювало,
1287		// коли стрічки на екрані ще (або вже) не було.
1288		//
1289		// Якщо стрічка вища за екран, ловимо її не серединою, а верхнім краєм —
1290		// інакше момент захоплення поїхав би нижче видимої частини.
1291		//
1292		// Умова залежить від напрямку — при скролі вниз середина стрічки
1293		// підходить до середини екрана згори (diff спадає до нуля), при скролі
1294		// вгору — навпаки. Без цієї різниці на зворотному ході стрічка починала
1295		// відмотуватись ще до появи блоку на екрані. Допуск у 0.6 екрана
1296		// страхує від того, що середину проскочили за одне клацання колеса;
1297		// далі за нього блок уже не ловимо.
1298		var atTrigger = function (delta) {
1299			var rect = container.getBoundingClientRect();
1300			var anchor = Math.min(rect.height, window.innerHeight) / 2;
1301			var diff = (rect.top + anchor) - (window.innerHeight / 2);
1302			var slack = window.innerHeight * 0.6;
1303			return delta > 0
1304				? (diff <= 0 && diff >= -slack)
1305				: (diff >= 0 && diff <= slack);
1306		};
1307
1308		// Заморожування сторінки. Самого preventDefault() мало: під інерцію
1309		// трекпада браузер шле події з cancelable === false — скасувати їх
1310		// неможливо, і сторінка їде одночасно зі стрічкою. Повертати позицію
1311		// назад теж не варіант: сторінка встигає зсунутись і смикається. Тому
1312		// на час стрічки скрол прибирається зовсім — позиція при цьому
1313		// зберігається.
1314		var frozen = false;
1315		var savedOverflow = '';
1316		var savedPadding = '';
1317		var freeze = function () {
1318			if (frozen) return;
1319			var de = document.documentElement;
1320			// Ширина смуги прокрутки: без компенсації сторінка стрибне вбік у
1321			// браузерах, де смуга займає місце (Windows, Linux).
1322			var scrollbar = window.innerWidth - de.clientWidth;
1323			savedOverflow = de.style.overflow;
1324			savedPadding = de.style.paddingRight;
1325			de.style.overflow = 'hidden';
1326			if (scrollbar > 0) de.style.paddingRight = scrollbar + 'px';
1327			frozen = true;
1328		};
1329		var thaw = function () {
1330			if (!frozen) return;
1331			var de = document.documentElement;
1332			de.style.overflow = savedOverflow;
1333			de.style.paddingRight = savedPadding;
1334			frozen = false;
1335		};
1336
1337		// Клавіатура має лишити шлях назовні: поки сторінка заморожена, ані
1338		// стрілки, ані End її не рухають.
1339		window.addEventListener('keydown', function () { thaw(); });
1340
1341		// Повертає true, якщо скрол «з'їдений» стрічкою (тоді подію треба
1342		// скасувати). false — стрічка на краю і сторінка скролиться далі.
1343		var consume = function (delta) {
1344			// Перший вимір міг статись до того, як догрузились шрифти й картки
1345			// набрали остаточну ширину — тоді maxTranslate вийшов нульовим і
1346			// стрічка б мовчки не працювала. Переміряємо при потребі.
1347			if (maxTranslate <= 0) measure();
1348			if (maxTranslate <= 0) { thaw(); return false; }
1349			// Поки сторінка не заморожена — чекаємо, доки стрічка стане
1350			// серединою на середину екрана. Заморожена — вона нікуди не
1351			// подінеться.
1352			if (!frozen && !atTrigger(delta)) return false;
1353			if (delta > 0 && target >= maxTranslate) { thaw(); return false; }
1354			if (delta < 0 && target <= 0) { thaw(); return false; }
1355			freeze();
1356			target = Math.min(maxTranslate, Math.max(0, target + delta));
1357			kick();
1358			return true;
1359		};
1360
1361		// deltaMode 1 — браузер міряє крок у рядках, а не пікселях (Firefox).
1362		var wheelDelta = function (e) {
1363			return e.deltaMode === 1 ? e.deltaY * 16 : e.deltaY;
1364		};
1365
1366		if (!touchMode) {
1367			window.addEventListener('wheel', function (e) {
1368				if (consume(wheelDelta(e)) && e.cancelable) e.preventDefault();
1369			}, { passive: false });
1370		}
1371
1372		// ── Тач: стрічка їде за скролом сторінки ─────────────────────────────
1373		// Замість перехоплення — прив'язка до позиції скролу. Стрічка стає
1374		// sticky всередині обгортки-доріжки й тримається по центру екрана, поки
1375		// сторінка проходить доріжку; пройдений відсоток доріжки і є позицією
1376		// стрічки. Сторінка не завмирає ні на мить — свайп завжди гортає її,
1377		// стрічка просто їде разом.
1378		//
1379		// Липне не сама стрічка, а вона разом із заголовком блоку: інакше на час
1380		// проходу заголовок їде вгору, і на екрані лишається стрічка серед
1381		// порожнечі — незрозуміло, що це взагалі за перелік.
1382		//
1383		// Обидві обгортки створює скрипт, а не розмітка: на десктопі їх бути не
1384		// має, та й висота доріжки рахується з довжини стрічки, яку знає лише JS.
1385		//
1386		// Sticky тут тримається на тому, що жоден предок не є scroll-контейнером:
1387		// .wrapper має overflow: clip, .middle-page — overflow-x: clip (саме
1388		// clip, не hidden). Зміните на hidden — стрічка перестане липнути й
1389		// проскочить доріжку порожньою дірою.
1390		var track = null;    // доріжка: задає, скільки сторінки проходить стрічка
1391		var pin = null;      // те, що липне: заголовок + стрічка
1392		var trackTravel = 0; // висота запасу в доріжці, px
1393		if (touchMode) {
1394			// Стрічка лежить на різній глибині: у Middle — прямо в секції, у
1395			// «Нульовому класі» — всередині .routine__inner. Піднімаємось до
1396			// рівня секції, бо заголовок — сусід саме на ньому.
1397			var block = container;
1398			while (block.parentElement && block.parentElement.tagName !== 'SECTION') {
1399				block = block.parentElement;
1400			}
1401			var heading = block.previousElementSibling;
1402
1403			track = document.createElement('div');
1404			track.className = 'wf-conveyor-track';
1405			pin = document.createElement('div');
1406			pin.className = 'wf-conveyor-pin';
1407			pin.style.position = 'sticky';
1408
1409			block.parentNode.insertBefore(track, heading || block);
1410			track.appendChild(pin);
1411			if (heading) pin.appendChild(heading);
1412			pin.appendChild(block);
1413		}
1414
1415		var layoutTrack = function () {
1416			if (!track) return;
1417			var height = pin.offsetHeight;
1418			// Запас = горизонтальний шлях стрічки: палець веде її в тому ж темпі,
1419			// що й сторінку. Але не більше двох екранів — на довгому розкладі
1420			// точна пропорція з'їла б пів сторінки скролу.
1421			trackTravel = Math.min(maxTranslate, window.innerHeight * 2);
1422			track.style.height = (height + trackTravel) + 'px';
1423			// Блок стоїть по центру екрана; вищий за екран — чіпляється згори.
1424			pin.style.top = Math.max(0, (window.innerHeight - height) / 2) + 'px';
1425		};
1426
1427		var trackScroll = function () {
1428			if (!track || trackTravel <= 0 || maxTranslate <= 0) return;
1429			var passed = (parseFloat(pin.style.top) || 0) - track.getBoundingClientRect().top;
1430			var progress = Math.min(1, Math.max(0, passed / trackTravel));
1431			target = progress * maxTranslate;
1432			kick();
1433		};
1434
1435		if (touchMode) {
1436			window.addEventListener('scroll', trackScroll, { passive: true });
1437		}
1438
1439		// Горизонтальне перетягування стрічки пальцем — для ноутбуків із
1440		// тачскріном. На телефоні його немає: там позицію стрічки задає скрол,
1441		// і протягнутий убік палець відкотило б назад першим же рухом сторінки.
1442		if (!touchMode) {
1443			var dragX = null;
1444			var dragFrom = 0;
1445			container.addEventListener('touchstart', function (e) {
1446				dragX = e.touches[0].clientX;
1447				dragFrom = target;
1448			}, { passive: true });
1449			container.addEventListener('touchmove', function (e) {
1450				if (dragX === null || maxTranslate <= 0) return;
1451				target = Math.min(maxTranslate, Math.max(0, dragFrom + (dragX - e.touches[0].clientX)));
1452				kick();
1453			}, { passive: true });
1454			container.addEventListener('touchend', function () { dragX = null; }, { passive: true });
1455		}
1456
1457		var recompute = function () {
1458			setCentering();
1459			measure();
1460			layoutTrack();
1461			target = Math.min(target, maxTranslate);
1462			current = target;
1463			render();
1464			trackScroll();
1465		};
1466		window.addEventListener('resize', recompute);
1467		window.addEventListener('orientationchange', recompute);
1468		// load — коли догрузились шрифти й картинки: ширини карток остаточні.
1469		window.addEventListener('load', recompute);
1470		// Одного 'load' мало: висота доріжки рахується з розмірів карток, а ті
1471		// добирають останні пікселі вже після нього — на підміні шрифту. Без
1472		// перевиміру доріжка лишалась короткою і стрічка їхала вдвічі швидше за
1473		// палець. ResizeObserver ловить будь-яку таку зміну; рекурсії немає —
1474		// recompute міняє висоту доріжки й горизонтальні padding'и, а не розмір
1475		// самого контейнера.
1476		if (document.fonts && document.fonts.ready) document.fonts.ready.then(recompute);
1477		if (window.ResizeObserver) new ResizeObserver(recompute).observe(pin || container);
1478		recompute();
1479	}
1480	// Shedule slider — стрічка розкладу дня («Нульовий клас», «Школа», садочок).
1481	// Механіка спільна з розкладом Middle school — див. wfScrollConveyor вище.
1482	//
1483	// Раніше тут був sticky-scrolljacking: секція розтягувалась до
1484	// (висота екрана + 700px × кількість пунктів) і пінилась на 100vh. Блок
1485	// займав увесь екран, а сторінка через нього тягнулась на 8600px.
1486	if ($('.wf_shedule_swiper_container').length > 0) {
1487		document.querySelectorAll('.routine').forEach(function (section) {
1488			var container = section.querySelector('.wf_shedule_swiper_container');
1489			var wrapper = section.querySelector('.wf_shedule_swiper_wrapper');
1490			var slides = Array.prototype.slice.call(section.querySelectorAll('.swiper-slide'));
1491			if (!container || !wrapper || slides.length === 0) return;
1492
1493			// Репліка-хмаринка показується лише в активного пункту.
1494			slides.forEach(function (slide, index) {
1495				var anime = slide.querySelector('[data-anime]');
1496				if (anime) anime.style.display = index === 0 ? 'block' : 'none';
1497				if (index === 0) slide.classList.add('swiper-slide-active');
1498			});
1499
1500			wfScrollConveyor({
1501				container: container,
1502				wrapper: wrapper,
1503				slides: slides,
1504				gap: 130,
1505				onProgress: function (progress) {
1506					var activeIndex = Math.floor(progress * slides.length);
1507					if (activeIndex >= slides.length) activeIndex = slides.length - 1;
1508					if (activeIndex < 0) activeIndex = 0;
1509
1510					slides.forEach(function (slide, index) {
1511						var anime = slide.querySelector('[data-anime]');
1512						if (index === activeIndex) {
1513							if (!slide.classList.contains('swiper-slide-active')) {
1514								slide.classList.add('swiper-slide-active');
1515								if (anime) $(anime).stop(true, true).fadeIn(300);
1516							}
1517						} else if (slide.classList.contains('swiper-slide-active')) {
1518							slide.classList.remove('swiper-slide-active');
1519							if (anime) $(anime).stop(true, true).fadeOut(300);
1520						}
1521					});
1522				}
1523			});
1524		});
1525	}
1526
1527	// Middle reviews — стрічка відгуків («Що про GlobalKids Middle говорять наші
1528	// батьки»). Звичайний Swiper: гортається мишею, пальцем і смужкою прокрутки
1529	// під картками.
1530	//
1531	// Раніше блок мав sticky-scrolljacking: секція розтягувалась на
1532	// «висота екрана + 400px × кількість відгуків» і пінилась на 100vh, а картки
1533	// їхали самі по скролу сторінки. Автоматичний скрол прибрано — відгуки гортає
1534	// відвідувач.
1535	if ($('.wf_middle_reviews_slider').length > 0) {
1536		var middleReviewsSwiper = new Swiper('.wf_middle_reviews_slider', {
1537			loop: false,
1538			slidesPerView: 'auto',
1539			spaceBetween: 24,
1540			grabCursor: true,
1541			freeMode: true,
1542			scrollbar: {
1543				el: '.wf_middle_reviews_scrollbar',
1544				draggable: true
1545			},
1546			navigation: {
1547				nextEl: '.wf_middle_reviews_next',
1548				prevEl: '.wf_middle_reviews_prev'
1549			}
1550		});
1551	}
1552
1553	// Відео-відгуки Middle: така сама стрічка обкладинок, клік відкриває ролик
1554	// у fancybox. defaultType: 'iframe' — як у відео локацій на головній
1555	// (a.home-location__videos-link), fancybox сам розбирає посилання youtu.be.
1556	if ($('.wf_middle_videos_slider').length > 0) {
1557		var middleVideosSwiper = new Swiper('.wf_middle_videos_slider', {
1558			loop: false,
1559			slidesPerView: 'auto',
1560			spaceBetween: 16,
1561			grabCursor: true,
1562			freeMode: true,
1563			scrollbar: {
1564				el: '.wf_middle_videos_scrollbar',
1565				draggable: true
1566			}
1567		});
1568
1569		$('a.wf_middle_review_video').fancybox({
1570			defaultType: 'iframe'
1571		});
1572	}
1573
1574	// Middle day schedule — стрічка розкладу дня на сторінці Middle school.
1575	// Механіка спільна з розкладом «Нульового класу» — див. wfScrollConveyor вище.
1576	if ($('.wf_middle_day_slider').length > 0) {
1577		document.querySelectorAll('.middle-day').forEach(function (section) {
1578			var container = section.querySelector('.wf_middle_day_slider');
1579			var wrapper = section.querySelector('.swiper-wrapper');
1580			var slides = Array.prototype.slice.call(section.querySelectorAll('.swiper-slide'));
1581			var progressBar = section.querySelector('.middle-day__progress-bar');
1582			if (!container || !wrapper || slides.length === 0) return;
1583
1584			wfScrollConveyor({
1585				container: container,
1586				wrapper: wrapper,
1587				slides: slides,
1588				gap: 24,
1589				onProgress: function (progress) {
1590					if (progressBar) {
1591						progressBar.style.transform = 'scaleX(' + Math.max(0.02, progress) + ')';
1592					}
1593				}
1594			});
1595		});
1596	}
1597
1598	// Kindergarten team
1599	if ($('.kindergartenSwiper').length > 0) {
1600		var kindergartenSwiper = new Swiper('.kindergartenSwiper', {
1601			loop: true,
1602			slidesPerView: 'auto',
1603			spaceBetween: 0,
1604			slidesPerGroup: 1,
1605    		speed: 300,
1606			// autoplay: {
1607			// 	delay: 10000,
1608			// },
1609			autoplay: false,
1610			navigation: {
1611				nextEl: '#kindergarten-team-btn-next',
1612				prevEl: '#kindergarten-team-btn-prev',
1613			}
1614			/*breakpoints: {
1615			  	// 320: {
1616				// 	slidesPerView: 'auto'
1617			  	// },
1618			  	768: {
1619					slidesPerView: 3
1620			  	},
1621			  	// 1024: {
1622				// 	slidesPerView: 3
1623			  	// }
1624			}*/
1625		});
1626	}
1627
1628	/* SEARCH */
1629	function wfSearch(searchValue) {
1630		searchValue = $.trim(searchValue);
1631
1632		var searchUrl = $('#data-search').attr('data-search-url');
1633
1634		if (searchValue != '') {
1635			searchUrl += '?search=' + fixedEncodeURIComponent(searchValue);
1636		}
1637
1638		window.location.href = searchUrl;
1639	}
1640
1641	// Header search
1642	$('.wf_header_search_form').submit(function(){
1643		wfSearch($('#data-search').val());
1644
1645		return false;
1646	});
1647
1648	// Search page
1649	$('.search__form').submit(function(){
1650		wfSearch($('.search__input').val());
1651
1652		return false;
1653	});
1654
1655	$('.wf_search_input_btn').click(function(){
1656		wfSearch($('.search__input').val());
1657	});
1658
1659	// Mobile nav search
1660	$('.wf_header_search_form_mobile').submit(function(){
1661		wfSearch($('.mobile-menu__header-search').val());
1662
1663		return false;
1664	});
1665
1666	$('.mobile-menu__header-search-icon').click(function(){
1667		wfSearch($('.mobile-menu__header-search').val());
1668	});
1669
1670	// Not fount page search
1671	$('.error__form').submit(function(){
1672		wfSearch($('.error__input').val());
1673
1674		return false;
1675	});
1676
1677	$('.wf_error_input_btn').click(function(){
1678		wfSearch($('.error__input').val());
1679	});
1680
1681	/* GLOBAL EVENTS */
1682	// Page scroll event
1683	document.addEventListener('scroll', function () {
1684		// Sticky header
1685		wfStickyHeader();
1686	});
1687
1688	// Page mousemove event
1689	document.addEventListener('mousemove', function (e) {
1690		// Animate K
1691		if (
1692			$('#groups-about__img--k').length > 0 || 
1693			$('#kindergarten-about__img--k').length > 0 || 
1694			$('#map__k').length > 0
1695		) {
1696			if ($('#groups-about__img--k').length > 0) {
1697				var card = document.getElementById('groups-about__img--k');
1698			} else if ($('#kindergarten-about__img--k').length > 0) {
1699				var card = document.getElementById('kindergarten-about__img--k');
1700			} else if ($('#map__k').length > 0) {
1701				var card = document.getElementById('map__k');
1702			}
1703
1704			var x = e.clientX;
1705			var y = e.clientY;
1706
1707			var width = document.documentElement.clientWidth;
1708			var height = document.documentElement.clientHeight;
1709
1710			var rotX = (x / width * 50) - 25;
1711			var rotY = (y / height * -50) + 25;
1712			var transX = (x / width * 50);
1713			var transY = (y / height * 50);
1714
1715			card.style.transform = 'rotateX(' + rotX + 'deg) rotateY(' + rotY + 'deg) translateX(' + transX + 'px) translateY(' + transY + 'px)';
1716		}
1717	});
1718
1719	// Page click event
1720	$(document).click(function(e) {
1721		// Hide workshops filters. Location
1722		var filterWorkshopsLocation = $('.groups-all-list__location');
1723
1724		if (!filterWorkshopsLocation.is(e.target) && filterWorkshopsLocation.has(e.target).length === 0) {
1725			$('.groups-all-list__location').attr('data-state', '');
1726		}
1727
1728		// Hide workshops filters. Type
1729		var filterWorkshopsType = $('.groups-all-list__direction');
1730
1731		if (!filterWorkshopsType.is(e.target) && filterWorkshopsType.has(e.target).length === 0) {
1732			$('.groups-all-list__direction').attr('data-state', '');
1733		}
1734
1735		// Hide workshops filters. Age
1736		var filterWorkshopsAge = $('.groups-all-list__age');
1737
1738		if (!filterWorkshopsAge.is(e.target) && filterWorkshopsAge.has(e.target).length === 0) {
1739			$('.groups-all-list__age').attr('data-state', '');
1740		}
1741	});
1742
1743	// Dynamic form popup handler
1744	$(document).on('click', '.wf_btn_open_form', function(e){
1745		e.preventDefault();
1746		
1747		var button = $(this);
1748		var formId = button.data('form-id');
1749		
1750		if (!formId) {
1751			console.error('Form ID not found');
1752			return false;
1753		}
1754		
1755		// Make AJAX request to get form data
1756		var formData = new FormData();
1757		formData.append('method', 'ajaxGetFormForPopup');
1758		formData.append('ajax_file', 'Forms');
1759		formData.append('form_id', formId);
1760		
1761		$.ajax({
1762			url: '/',
1763			type: 'POST',
1764			dataType: 'json',
1765			cache: false,
1766			contentType: false,
1767			processData: false,
1768			data: formData,
1769			success: function(response) {
1770				if (response.success && response.form) {
1771					var form = response.form;
1772					var popup = $('.popup-dynamic-form');
1773					
1774					// Set form ID and title for tracking
1775					popup.attr('data-form-id', form.id);
1776					popup.attr('data-form-title', form.title);
1777					
1778					// Set title and subtitle
1779					popup.find('.popup-title').html(form.title);
1780					popup.find('.popup-text').html(form.subtitle);
1781					
1782					// Set button text
1783					popup.find('.popup-form-submit').html(form.btn_text);
1784					
1785					// Add color class to popup based on form color
1786					popup.removeClass('form-purple form-green form-yellow');
1787					popup.addClass('form-' + form.color);
1788					
1789					// Show the popup
1790					$.scrollLock(true);
1791					popup.addClass('is-visible');
1792				} else {
1793					console.error('Error loading form:', response.error || 'Unknown error');
1794				}
1795			},
1796			error: function() {
1797				console.error('Failed to load form data');
1798			}
1799		});
1800		
1801		return false;
1802	});
1803
1804	// Dynamic form popup submit handler
1805	$('.popup-dynamic-form .popup-form-submit').click(function(){
1806		var popup = $('.popup-dynamic-form');
1807		var formId = popup.attr('data-form-id');
1808		var formTitle = popup.attr('data-form-title');
1809		
1810		var name = $.trim(popup.find('.wf_field_name').val());
1811		var lastname = $.trim(popup.find('.wf_field_lastname').val());
1812		var phone = $.trim(popup.find('.wf_field_phone').val());
1813		var email = $.trim(popup.find('.wf_field_email').val());
1814		var comment = $.trim(popup.find('.popup-register__form-input--textarea').val());
1815		
1816		// Add form title to comment if comment exists, otherwise just use form title
1817		var fullComment = comment ? comment + ' (Форма: ' + formTitle + ')' : 'Форма: ' + formTitle;
1818
1819		if (phone.length < 19) {
1820			popup.find('.wf_field_phone').addClass('wf_field_error');
1821			errorModal(phoneRequiredField);
1822		} else {
1823			// Hide popup before showing preloader
1824			popup.removeClass('is-visible');
1825			
1826			viewMainPreloader();
1827
1828			var formData = new FormData();
1829			formData.append('method', 'ajaxSendOrder');
1830			formData.append('ajax_file', 'Conversions');
1831			formData.append('name', name);
1832			formData.append('lastname', lastname);
1833			formData.append('phone', phone);
1834			formData.append('email', email);
1835			formData.append('comment', fullComment);
1836			formData.append('full_url', $(location).attr('href'));
1837			formData.append('monitor_width', screen.width);
1838			formData.append('monitor_height', screen.height);
1839			formData.append('conversion_id', 2); // Use popup conversion ID
1840
1841			$.ajax({
1842				url: '/',
1843				type: "POST",
1844				dataType: "json",
1845				cache: false,
1846				contentType: false,
1847				processData: false,
1848				data: formData,
1849				success: function(response) {
1850					hiddenMainPreloader();
1851
1852					if (response.error) {
1853						errorModal(response.error);
1854					} else if (response.success) {
1855						$.scrollLock(false);
1856						
1857						// Clear form fields
1858						popup.find('.wf_field_name').val('');
1859						popup.find('.wf_field_lastname').val('');
1860						popup.find('.wf_field_phone').val('');
1861						popup.find('.wf_field_email').val('');
1862						popup.find('.popup-register__form-input--textarea').val('');
1863
1864						// Successfully notification
1865						successfullyModal(response.success, response.success_text);
1866
1867						fbq('track', 'Lead');
1868					}
1869				},
1870				error: function(xhr, ajaxOptions, thrownError) {	
1871					console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
1872					hiddenMainPreloader();
1873				}
1874			});
1875		}
1876		
1877		return false;
1878	});
1879});
1880
1881/**
1882 * Function to set a cookie with name, value, and expiration days.
1883 * @param {string} name - The name of the cookie.
1884 * @param {string} value - The value to store in the cookie.
1885 * @param {number} days - The number of days until the cookie expires.
1886 */
1887function setCookie(name, value, days) {
1888    let expires = "";
1889    if (days) {
1890        const date = new Date();
1891        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 
1892        expires = "; expires=" + date.toUTCString();
1893    }
1894    // Set cookie with path and SameSite=Lax for security
1895    document.cookie = name + "=" + (value || "") + expires + "; path=/; SameSite=Lax";
1896}
1897
1898/**
1899 * Function to collect UTM parameters, GCLID, and referrer, and store them in a cookie.
1900 */
1901function collectAndStoreParams() {
1902    const paramsToCollect = [
1903        'utm_source',
1904        'utm_medium',
1905        'utm_campaign',
1906        'utm_content',
1907        'utm_term',
1908        'gclid' // Google Click Identifier
1909    ];
1910
1911    const urlParams = new URLSearchParams(window.location.search);
1912    const trackingData = {};
1913
1914    paramsToCollect.forEach(param => {
1915        if (urlParams.has(param)) {
1916            trackingData[param] = urlParams.get(param);
1917        }
1918    });
1919
1920    const referrer = document.referrer;
1921    if (referrer) {
1922        trackingData['referrer'] = referrer;
1923    }
1924
1925    if (Object.keys(trackingData).length > 0) {
1926        const dataString = JSON.stringify(trackingData);
1927
1928        // Store data in 'tracking_params' cookie for 30 days
1929        setCookie('tracking_params', dataString, 30);
1930
1931        // Optional: Console logging for debugging
1932        // console.log('Tracking parameters stored:', trackingData);
1933    }
1934}
1935
1936// Execute the collection function immediately upon script load
1937collectAndStoreParams();
1938
1939/**
1940 * Function to get and decode the tracking_params cookie data.
1941 * @returns {object|null} - An object containing tracking parameters, or null if not found.
1942 */
1943function getTrackingParams() {
1944    const trackingCookie = document.cookie
1945        .split('; ')
1946        .find(row => row.startsWith('tracking_params='));
1947
1948    if (trackingCookie) {
1949        // Extract the encoded JSON string
1950        const encodedJsonString = trackingCookie.split('=')[1];
1951        
1952        // 1. Decode the URL-encoded string
1953        const jsonString = decodeURIComponent(encodedJsonString);
1954
1955        // 2. Parse the JSON string into a JavaScript object
1956        try {
1957            return JSON.parse(jsonString);
1958        } catch (e) {
1959            console.error('Error parsing tracking_params JSON:', e);
1960            return null;
1961        }
1962    }
1963    return null;
1964}
1965/**
1966 * Віддає висоту шапки в CSS через --wf-header-height.
1967 *
1968 * Потрібно героєві на головній: він має займати рівно один екран, а шапка
1969 * стоїть у потоці (position: sticky), тож його висота — це 100vh мінус шапка.
1970 * Хардкодити не можна: меню редагується в адмінці, і на двох рядках шапка стає
1971 * вищою. Запасне значення 112px лежить у самому CSS (view/css/home.css).
1972 */
1973function wfHeaderHeightVar() {
1974	var header = document.querySelector('.header');
1975
1976	if (!header) {
1977		return;
1978	}
1979
1980	document.documentElement.style.setProperty(
1981		'--wf-header-height',
1982		Math.round(header.getBoundingClientRect().height) + 'px'
1983	);
1984}
1985
1986document.addEventListener('DOMContentLoaded', wfHeaderHeightVar);
1987window.addEventListener('load', wfHeaderHeightVar);
1988window.addEventListener('resize', wfHeaderHeightVar);
1989
1990// Geometria підміняє системний шрифт уже після load і робить меню вищим —
1991// без цього перерахунку низ блоку не сходиться з краєм екрана на пікселі.
1992if (document.fonts && document.fonts.ready) {
1993	document.fonts.ready.then(wfHeaderHeightVar);
1994}
1995
1996/**
1997 * На головній ховає фіксовану кнопку «Заповнити заявку», поки на екрані
1998 * видно кнопку hero: на телефоні вона її перекриває. Кнопка hero пішла за
1999 * верхній край — фіксована з'являється, повернулись нагору — знову ховається.
2000 * Поріг у пікселях тут не годиться: кнопка hero стоїть низько, і після
2001 * невеликого скролу вона ще внизу екрана, саме під фіксованою.
2002 *
2003 * Клас-очікування ставить сам скрипт, а не шаблон, — якщо JS не відпрацює,
2004 * кнопка просто лишиться видимою. Що саме ховати і на яких ширинах, вирішує
2005 * CSS (view/css/home.css), тут лише прапорець «кнопку hero ще видно».
2006 */
2007function wfHomeFixedBtnWait() {
2008	var body = document.body;
2009	// Без кнопки в hero (текст у конструкторі порожній) орієнтуємось на весь блок.
2010	var target = document.querySelector('.home-top__button') || document.querySelector('.home-top');
2011
2012	if (!body.classList.contains('template_homepage') || !target || !document.querySelector('.fixed-btn')) {
2013		return;
2014	}
2015
2016	var update = function () {
2017		body.classList.toggle('wf-fixed-btn-wait', target.getBoundingClientRect().bottom > 0);
2018	};
2019
2020	update();
2021	window.addEventListener('scroll', update, { passive: true });
2022}
2023
2024document.addEventListener('DOMContentLoaded', wfHomeFixedBtnWait);

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.