PageSourceSearch

https://grace-gorizont.ru/vendor/js/main.js

js grace-gorizont.ru collected 2026-10-04 09:36:47 UTC 32,672 bytes, 955 lines download raw bytes

1$(document).ready(function(){
2    
3    // менюшка
4    var $navbarCollapse = $('.nav-menu');
5    // закрываем меню при клике на любую ссылку внутри 
6    $navbarCollapse.find('a').on('click', function () {
7        $('.nav-menu').collapse('hide');
8    });
9    // закрываем меню при клике вне его
10    $(document).on('click', function (e) {
11        if ($navbarCollapse.hasClass('show') &&
12            !$(e.target).closest('.nav-menu').length &&
13            !$(e.target).closest('.nav-menu-toggler').length) {
14            $navbarCollapse.collapse('hide');
15        }
16    });
17
18
19
20    // меняем прозрачность строки букинга при скролле
21    $(function () {
22        var $window = $(window);
23        var $hero = $('.hero');
24        var $booking = $('.booking.sticky-top'); // или просто $('.booking')
25    
26        if (!$hero.length || !$booking.length) return;
27    
28        function getHeroBottom() {
29            return $hero.offset().top + $hero.outerHeight();
30        }
31    
32        var heroBottom = getHeroBottom();
33        var ticking = false;
34    
35        var lastCheck = null;
36        var stableCount = 0;
37        var STABLE_FRAMES = 6; // сколько кадров подряд нужно
38    
39        function onScrollTick() {
40            var scrollTop = $window.scrollTop();
41            var stickyBottom = scrollTop + $booking.outerHeight();
42    
43            // true = прозрачный
44            var currentCheck = stickyBottom < heroBottom;
45    
46            if (currentCheck === lastCheck) {
47                stableCount++;
48            } else {
49                stableCount = 0;
50                lastCheck = currentCheck;
51            }
52    
53            if (stableCount >= STABLE_FRAMES) {
54                if (currentCheck) {
55                    if (!$booking.hasClass('trnsp')) $booking.addClass('trnsp');
56                } else {
57                    if ($booking.hasClass('trnsp')) $booking.removeClass('trnsp');
58                }
59            }
60    
61            ticking = false;
62        }
63    
64        $window.on('scroll', function () {
65            if (!ticking) {
66                ticking = true;
67                requestAnimationFrame(onScrollTick);
68            }
69        });
70    
71        $window.on('resize', function () {
72            heroBottom = getHeroBottom();
73            onScrollTick();
74        });
75    
76        onScrollTick();
77    });
78
79
80    
81    // скрываем строку букинга при скролле
82    $(function() {
83        var lastScrollTop = 0; // сохраняем прошлую позицию скролла
84    
85        $(window).on('scroll', function() {
86            var st = $(this).scrollTop();
87    
88            if (st > lastScrollTop) {
89                // Скролл вниз
90                $('.booking').addClass('hidden');
91            } else {
92                // Скролл вверх
93                $('.booking').removeClass('hidden');
94            }
95    
96            lastScrollTop = st;
97        });
98    });
99    
100    
101    
102    // минимизируем строку букинга на мобилках
103    $(function () {
104        var breakpoint = 992;
105        var $root = $(':root');
106        var $booking = $('.booking');
107        var $container = $('.booking .container');
108        var btnSelector = '#booking-toggle-btn';
109    
110        var $form = $();
111        var baseHeight = null;
112        var wasMobile = false;
113    
114        function isMobile() {
115            return $(window).width() <= breakpoint;
116        }
117    
118        function getCssHeight() {
119            return parseInt(
120                getComputedStyle(document.documentElement)
121                    .getPropertyValue('--booking-form-height')
122            );
123        }
124    
125        function setHeight(value) {
126            $root.css('--booking-form-height', value + 'px');
127        }
128    
129        function addButton() {
130            if (!$(btnSelector).length) {
131                $container.append(
132                    '<a class="btn-book" id="booking-toggle-btn">Выбрать дату заезда</a>'
133                );
134            }
135        }
136    
137        function removeButton() {
138            $(btnSelector).remove();
139        }
140    
141        function hideForm() {
142            if ($form.length) $form.hide();
143            if ($form.length) $form.css('margin-bottom', '0');
144        }
145    
146        function showForm() {
147            if ($form.length) $form.show();
148            if ($form.length) $form.css('margin-bottom', '1rem');
149        }
150    
151        function enterMobile() {
152            // читаем актуальное CSS-значение именно в момент входа
153            baseHeight = getCssHeight();
154    
155            setHeight(75);
156            hideForm();
157            addButton();
158        }
159    
160        function leaveMobile() {
161            // полностью возвращаем управление CSS
162            $root.css('--booking-form-height', '');
163            showForm();
164            $form.css('margin-bottom', '0');
165            removeButton();
166    
167            baseHeight = null;
168        }
169    
170        // клик по кнопке
171        $(document).on('click', btnSelector, function () {
172            var currentHeight = getCssHeight();
173    
174            if (currentHeight === 75) {
175                setHeight(baseHeight + 60);
176                showForm();
177                $(this).text('Свернуть');
178            } else {
179                setHeight(75);
180                hideForm();
181                $(this).text('Выбрать дату заезда');
182            }
183        });
184    
185        function check() {
186            var mobileAllowed = isMobile() && !$booking.hasClass('trnsp');
187        
188            if (mobileAllowed && !wasMobile) {
189                enterMobile();
190            }
191        
192            if ((!mobileAllowed && wasMobile)) {
193                leaveMobile();
194            }
195        
196            wasMobile = mobileAllowed;
197        }
198    
199        // --- MutationObserver ---
200        var classObserver = new MutationObserver(function () {
201            check();
202        });
203        
204        classObserver.observe($booking[0], {
205            attributes: true,
206            attributeFilter: ['class']
207        });
208        
209        var observer = new MutationObserver(function () {
210            var $found = $('.booking .container form');
211            if ($found.length) {
212                $form = $found;
213                check();
214                observer.disconnect();
215            }
216        });
217    
218        observer.observe($container[0], {
219            childList: true,
220            subtree: true
221        });
222    
223        // fallback
224        var $initialForm = $('.booking .container form');
225        if ($initialForm.length) {
226            $form = $initialForm;
227            check();
228        }
229    
230        $(window).on('resize', check);
231    });
232
233
234    
235    // слайдер hero
236    var heroSwiper = new Swiper('.hero-slider-list', {
237        autoplay: { delay: 4000 },
238        speed: 3000,
239        loop: true,
240        slidesPerView: 1,
241        effect: 'fade',
242        fadeEffect: {
243            crossFade: true
244        },
245        allowTouchMove: false,
246        pagination: {
247            el: '.hero-swiper-dots',
248            clickable: true,
249        },
250    });
251
252    
253    // эффект счетчика для рейтинга
254    $(function () {
255    
256        var $section = $('.rating');
257        if (!$section.length) return;
258    
259        var animated = false;
260    
261        function isSectionInViewport() {
262            var sectionTop = $section.offset().top;
263            var sectionBottom = sectionTop + $section.outerHeight();
264    
265            var viewportTop = $(window).scrollTop();
266            var viewportBottom = viewportTop + $(window).height();
267    
268            // срабатывает, когда секция попала хотя бы наполовину
269            return sectionTop < viewportBottom - ($section.outerHeight() / 2);
270        }
271    
272        function animateCounter($el) {
273    
274            var finalValue = $el.text().trim().replace(',', '.');
275            finalValue = parseFloat(finalValue);
276    
277            if (isNaN(finalValue)) return;
278    
279            var duration = 2000;
280            var startTime = null;
281    
282            function step(timestamp) {
283                if (!startTime) startTime = timestamp;
284    
285                var progress = Math.min((timestamp - startTime) / duration, 1);
286                var current = finalValue * progress;
287    
288                if (finalValue % 1 !== 0) {
289                    $el.text(current.toFixed(1).replace('.', ','));
290                } else {
291                    $el.text(Math.round(current));
292                }
293    
294                if (progress < 1) {
295                    requestAnimationFrame(step);
296                } else {
297                    $el.text(finalValue.toString().replace('.', ','));
298                }
299            }
300    
301            requestAnimationFrame(step);
302        }
303    
304        function startCounters() {
305            if (animated) return;
306            animated = true;
307    
308            $section.find('.item-rating > span').each(function () {
309                animateCounter($(this));
310            });
311        }
312    
313        function checkSection() {
314            if (isSectionInViewport()) {
315                startCounters();
316                $(window).off('scroll', checkSection);
317            }
318        }
319    
320        // если секция уже видна при загрузке
321        checkSection();
322    
323        // запуск при скролле
324        $(window).on('scroll', checkSection);
325    
326    });
327    
328    
329    // слайдер акций
330    var specialOffersSwiper = new Swiper('.special-offers-list', {
331        direction: 'horizontal',
332        autoplay: { delay: 5000 },
333        speed: 300,
334        loop: true,
335        slidesPerView: 1,
336        spaceBetween: 24,
337        allowTouchMove: true,
338        pagination: {
339            el: '.special-offers .section-swiper-dots',
340            clickable: true,
341        },
342        breakpoints: {
343            992: {
344                slidesPerView: 2,
345            }
346        }
347    });
348    
349    
350    
351    // показать все для акций
352    $('.special-offers[data-init]').each(function() {
353        var $container = $(this);
354        var mode = $container.data('init');
355        var $swiperEl = $container.find('.swiper.special-offers-list');
356        var $wrapper = $swiperEl.find('.swiper-wrapper');
357    
358        function destroySwiper() {
359            if ($swiperEl[0].swiper) {
360                $swiperEl[0].swiper.destroy(true, true);
361            }
362            $container.find('.section-swiper-dots, .btn-wrap').hide();
363    
364            var $slides = $wrapper.find('.swiper-slide');
365            $slides.each(function() {
366                $(this).wrap('<div class="col-lg-6 col-12"></div>');
367            });
368    
369            $wrapper.replaceWith(function() {
370                return $('<div class="row g-4"></div>').append($(this).children());
371            });
372    
373            $swiperEl.removeClass('swiper special-offers-list swiper-initialized');
374        }
375    
376        // Если указано data-init="grid" — сразу показываем все карточки
377        if (mode === 'grid') {
378            destroySwiper();
379        }
380    
381        // При клике на "Показать все" — переключаем на сетку
382        $container.find('#special-offers-show-more').on('click', function(e) {
383            e.preventDefault();
384            destroySwiper();
385        });
386    });
387
388
389    
390    // добавляем кнопку "показать еще" для всех
391    $(function() {
392        $('.show-more').each(function() {
393            const $container = $(this);
394            const $items = $container.find('.item').parent();
395            const total = $items.length;
396
397            // Чтение атрибутов или установка значений по умолчанию
398            const initialCount = parseInt($container.data('initialcount')) || 3;
399            const loadCount = parseInt($container.data('loadcount')) || 3;
400
401            if (total <= initialCount) return;
402
403            // Скрываем все, кроме первых initialCount
404            $items.hide().slice(0, initialCount).show();
405
406            // Добавляем кнопку
407            const $btnWrap = $('<div class="btn-wrap"></div>');
408            const $btn = $('<a href="#" class="btn-show-more">Показать ещё</a>');
409            $btnWrap.append($btn);
410            $container.after($btnWrap);
411
412            let visible = initialCount;
413
414            // Обработка клика
415            $btn.on('click', function(e) {
416                e.preventDefault();
417
418                if (!$btn.hasClass('opened')) {
419                    // Показываем следующую партию карточек
420                    visible += loadCount;
421                    $items.slice(0, visible).fadeIn(300);
422
423                    // Если показаны все — меняем текст
424                    if (visible >= total) {
425                        $btn.addClass('opened').text('Скрыть');
426                    }
427                } else {
428                    // Скрываем обратно все, кроме первых initialCount
429                    $items.slice(initialCount).fadeOut(300);
430                    visible = initialCount;
431                    $btn.removeClass('opened').text('Показать ещё');
432                }
433            });
434        });
435    });
436    
437    
438    
439    // слайдер карточек номеров
440    $('.rooms .rooms-slider-list').each(function (index, element) {
441        // уникальные классы для каждого экземпляра
442        var swiperClass = 'swiper-instance-' + index;
443        $(element).addClass(swiperClass);
444
445        new Swiper('.' + swiperClass, {
446            direction: 'horizontal',
447            autoplay: false,
448            speed: 300,
449            loop: true,
450            slidesPerView: 1,
451            spaceBetween: 10,
452            allowTouchMove: true,
453            pagination: {
454                el: $(element).find('.rooms-item-swiper-dots')[0],
455                clickable: true,
456            },
457            navigation: {
458                nextEl: $(element).find('.rooms-item-swiper-next')[0],
459                prevEl: $(element).find('.rooms-item-swiper-prev')[0],
460            }
461        });
462    });
463    
464    
465    
466    // "показать еще" и фильтры для номеров
467    $(function() {
468        const $container = $('.show-more-rooms');
469        const $allItems = $container.find('.item').parent(); // col-lg-4 col-md-6 col-12
470    
471        // === ДИНАМИЧЕСКИЕ СПИСКИ ФИЛЬТРОВ ===
472        const $categorySelect = $('#category');
473        const $sortSelect = $('#sort');
474        const $amenitiesDropdown = $('#amenitiesDropdown');
475        const $categoryDropdown = $('#categoryDropdown');
476        const $sortDropdown = $('#sortDropdown');
477    
478        const $amenitiesMenu = $amenitiesDropdown.find('.dropdown-menu');
479        const $amenitiesButton = $amenitiesDropdown.find('button');
480        const $categoryMenu = $categoryDropdown.find('.dropdown-menu');
481        const $categoryButton = $categoryDropdown.find('button');
482        const $sortMenu = $sortDropdown.find('.dropdown-menu');
483        const $sortButton = $sortDropdown.find('button');
484    
485        // Собираем значения
486        const amenitiesSet = new Set();
487        const categorySet = new Set();
488    
489        $allItems.each(function() {
490            const $item = $(this).find('.item');
491            const itemAmenities = ($item.data('amenities') || '')
492                .split(',')
493                .map(a => a.trim())
494                .filter(a => a);
495            itemAmenities.forEach(a => amenitiesSet.add(a));
496    
497            const itemCategory = $item.data('category');
498            if (itemCategory) categorySet.add(itemCategory);
499        });
500    
501        // === УДОБСТВА (dropdown с чекбоксами) ===
502        if ($amenitiesDropdown.length) {
503            $amenitiesMenu.empty();
504            Array.from(amenitiesSet).sort().forEach(a => {
505                const id = 'amenity-' + a.replace(/\s+/g, '-').toLowerCase();
506                $amenitiesMenu.append(`
507                    <li>
508                        <label class="dropdown-item d-flex align-items-center gap-2">
509                            <input type="checkbox" class="form-check-input m-0" value="${a}" id="${id}">
510                            <span>${a}</span>
511                        </label>
512                    </li>
513                `);
514            });
515        
516            // Обновляем текст кнопки при выборе (без applyFilters)
517            $amenitiesMenu.on('change', 'input[type="checkbox"]', function() {
518                const selected = $amenitiesMenu.find('input:checked').map(function() {
519                    return $(this).val();
520                }).get();
521        
522                if (selected.length === 0) {
523                    $amenitiesButton.text('Удобства');
524                } else if (selected.length === 1) {
525                    $amenitiesButton.text(selected[0]);
526                } else {
527                    $amenitiesButton.text('Выбрано: ' + selected.length);
528                }
529        
530                // applyFilters() больше не вызываем здесь
531            });
532        }
533    
534        // === КАТЕГОРИИ (dropdown одиночный) ===
535        if ($categoryDropdown.length) {
536            $categoryMenu.empty();
537            $categorySelect.empty(); // очищаем select
538            $categoryMenu.append('<li data-value=""><span class="dropdown-item">Все категории</span></li>');
539            $categorySelect.append('<option value="">Все категории</option>');
540            
541            Array.from(categorySet).sort().forEach(c => {
542                $categoryMenu.append('<li data-value="'+c+'"><span class="dropdown-item">'+c+'</span></li>');
543                $categorySelect.append('<option value="'+c+'">'+c+'</option>');
544            });
545        
546            $categoryMenu.on('click', 'li', function(e) {
547                e.preventDefault();
548                e.stopPropagation();
549        
550                const value = $(this).data('value');
551                const text = $(this).text();
552        
553                $categorySelect.val(value);
554                $categoryButton.text(text);
555                $categoryMenu.find('li').removeClass('active');
556                $(this).addClass('active');
557        
558                applyFilters();
559            });
560        }
561    
562        // === СОРТИРОВКА (dropdown одиночный) ===
563        if ($('#sortDropdown').length) {
564            const $sortDropdown = $('#sortDropdown');
565            const $sortButton = $sortDropdown.find('.btn-dropdown');
566            const $sortMenu = $sortDropdown.find('.dropdown-menu');
567        
568            $sortMenu.on('click', 'li', function(e) {
569                e.preventDefault();
570                e.stopPropagation();
571        
572                const value = $(this).data('value');
573                const text = $(this).text();
574        
575                // Меняем кнопку и значение
576                $sortButton.text(text);
577                $sortSelect.val(value);
578        
579                $sortMenu.find('li').removeClass('active');
580                $(this).addClass('active');
581        
582                applyFilters();
583            });
584        }
585    
586        // === 1. Кнопка "Показать ещё" ===
587        const initialCount = parseInt($container.data('initialcount')) || 3;
588        const loadCount = parseInt($container.data('loadcount')) || 3;
589        let visible = initialCount;
590    
591        $allItems.hide().slice(0, initialCount).fadeIn(300);
592    
593        const $btnWrap = $('<div class="btn-wrap"></div>');
594        const $btn = $('<a href="#" class="btn-show-more">Показать ещё</a>');
595        $btnWrap.append($btn);
596        $container.after($btnWrap);
597    
598        $btn.on('click', function(e) {
599            e.preventDefault();
600            if (!$btn.hasClass('opened')) {
601                visible += loadCount;
602                $allItems.slice(0, visible).fadeIn(300);
603                if (visible >= $allItems.length) {
604                    $btn.addClass('opened').text('Скрыть');
605                }
606            } else {
607                $allItems.slice(initialCount).fadeOut(300);
608                visible = initialCount;
609                $btn.removeClass('opened').text('Показать ещё');
610            }
611        });
612    
613        // === 2. ФИЛЬТРЫ ===
614        const $form = $('#roomFilters');
615        const $reset = $('#resetFilters');
616    
617        function applyFilters() {
618            const guests = parseInt($('#guests').val()) || null;
619            const category = $('#category').val();
620            const sort = $('#sort').val();
621        
622            // выбранные удобства
623            const amenities = $amenitiesMenu.find('input:checked').map(function() {
624                return $(this).val();
625            }).get();
626        
627            // === ФИЛЬТРАЦИЯ ===
628            let filtered = $allItems.filter(function() {
629                const $item = $(this).find('.item');
630                const itemGuests = parseInt($item.data('guests'));
631                const itemAmenities = ($item.data('amenities') || '').split(',').map(a => a.trim());
632                const itemCategory = $item.data('category');
633        
634                let match = true;
635                if (guests && itemGuests < guests) match = false;
636                if (category && itemCategory !== category) match = false;
637                if (amenities.length && !amenities.every(a => itemAmenities.includes(a))) match = false;
638        
639                return match;
640            });
641        
642            // === СОРТИРОВКА (меняем порядок элементов ВНУТРИ контейнера) ===
643            if (sort) {
644                filtered = $(filtered).sort(function(a, b) {
645                    const priceA = parseFloat($(a).find('.item').data('price')) || 0;
646                    const priceB = parseFloat($(b).find('.item').data('price')) || 0;
647                    if (sort === 'price-asc') return priceA - priceB;
648                    if (sort === 'price-desc') return priceB - priceA;
649                    return 0;
650                });
651        
652                // обновляем порядок элементов внутри контейнера
653                $container.append(filtered);
654            }
655        
656            // === ПОКАЗ/СКРЫТИЕ ===
657            $allItems.hide();        // скрываем все
658            filtered.show();         // показываем только прошедшие фильтр
659        
660            // === КНОПКА "ПОКАЗАТЬ ЕЩЁ" ===
661            if (filtered.length <= initialCount) {
662                $btn.hide();
663                filtered.slice(initialCount).hide(); // на всякий случай
664            } else {
665                $btn.show().removeClass('opened').text('Показать ещё');
666                visible = initialCount;
667                filtered.slice(initialCount).hide();
668                filtered.slice(0, initialCount).fadeIn(200);
669            }
670        
671            // console.log('Filtered:', filtered.length, 'items');
672        }
673
674    
675        // Отслеживание изменений в фильтрах
676        $form.on('change input', 'input, select', applyFilters);
677    
678        // Кнопка сброса
679        $reset.on('click', function() {
680            $form[0].reset();
681            $amenitiesMenu.find('input').prop('checked', false);
682            $amenitiesButton.text('Удобства');
683            $categoryButton.text('Категория');
684            $sortButton.text('Сортировка');
685            $categoryMenu.find('li, .active').removeClass('active');
686            $sortMenu.find('li, .active').removeClass('active');
687            applyFilters();
688        });
689    
690        // Первичная инициализация
691        applyFilters();
692    });
693
694
695    
696    // слайдер галереи
697    var gallerySwiper = new Swiper('.gallery-slider-list', {
698        direction: 'horizontal',
699        slidesPerView: 1,
700        initialSlide: 1,
701        centeredSlides: true,
702        loop: true,
703        grabCursor: true,
704        effect: 'coverflow',
705        coverflowEffect: {
706            scale: 0.9,
707            rotate: 20,
708            stretch: '5%',
709            depth: 20,
710            slideShadows: true
711        },
712        breakpoints: {
713            768: {
714                slidesPerView: 3,
715            }
716        }
717    });
718
719
720
721    // слайдер страницы номера
722    var roomPageSwiper = new Swiper('.room-page-slider-list', {
723        direction: 'horizontal',
724        autoplay: { delay: 5000 },
725        speed: 300,
726        loop: true,
727        slidesPerView: 1,
728        spaceBetween: 10,
729        allowTouchMove: true,
730        pagination: {
731            el: '.room-page-swiper-dots',
732            clickable: true,
733        }
734    });
735
736
737
738    // слайдер видео-отзывов
739    var videoReviewSwiper = new Swiper('.video-slider-list', {
740        direction: 'horizontal',
741        autoplay: false,
742        speed: 300,
743        loop: false,
744        slidesPerView: 1,
745        spaceBetween: 40,
746        allowTouchMove: true,
747        pagination: {
748            el: '.video-reviews .section-swiper-dots',
749            clickable: true,
750        },
751        breakpoints: {
752            992: {
753                slidesPerView: 3,
754            }
755        }
756    });
757    
758    
759    // запускаем видео-отзывы при клике 
760    $(function() {
761      $('.video-review').on('click', function() {
762        const video = this;
763        const $item = $(video).closest('.item');
764    
765        if (video.paused) {
766          // Останавливаем все другие видео
767          $('.video-review').each(function() {
768            if (this !== video) {
769              this.pause();
770              this.currentTime = 0;
771              $(this).closest('.item').find('.play-icon').show();
772            }
773          });
774    
775          // Запускаем текущее
776          video.play();
777          $item.find('.play-icon').hide();
778        } else {
779          // При повторном клике — стоп и перемотка
780          video.pause();
781          video.currentTime = 0;
782          $item.find('.play-icon').show();
783        }
784      });
785    
786      // Дополнительно: если видео закончилось само, показываем иконку
787      $('.video-review').on('ended', function() {
788        const $item = $(this).closest('.item');
789        $item.find('.play-icon').show();
790        this.currentTime = 0;
791      });
792    });
793
794    
795    
796    // инициализируем fancybox
797    Fancybox.bind("[data-fancybox]", {
798        caption: (fancybox, slide) => {
799            const caption = slide.caption || "";
800            return `${slide.index + 1} / ${
801                fancybox.carousel?.slides.length
802            } <br /> ${caption}`;
803        },
804    });
805
806
807
808    // делим колонку на 2
809    function splitColumns() {
810        $('.row.split').each(function () {
811            var $row = $(this);
812            var $wrap = $row.find('.menu-list-wrap');
813            var $items = $wrap.find('.item');
814
815            // Сначала возвращаем всё в одну колонку (на случай ресайза)
816            if ($row.find('.col-lg-6').length > 1) {
817                $wrap.html($row.find('.item')); // вернуть все элементы в первый блок
818                $row.find('.col-lg-6:not(:first)').remove(); // удалить вторую колонку
819            }
820
821            // Разделяем только если экран больше 992px и элементов больше 2
822            if ($(window).width() >= 992 && $items.length > 2) {
823                var total = $items.length;
824                var half = Math.ceil(total / 2); // первая колонка на 1 больше при нечётном
825
826                var $firstColItems = $items.slice(0, half);
827                var $secondColItems = $items.slice(half);
828
829                // Создаём вторую колонку
830                var $secondCol = $('<div class="col-lg-6 col-12"><div class="menu-list-wrap"></div></div>');
831                $secondCol.find('.menu-list-wrap').append($secondColItems);
832
833                // Оставляем первую колонку как есть
834                $wrap.html($firstColItems);
835
836                // Добавляем вторую колонку
837                $row.append($secondCol);
838            }
839        });
840    }
841
842    // Запускаем при загрузке
843    splitColumns();
844
845    // И при изменении размера окна
846    $(window).on('resize', function () {
847        splitColumns();
848    });
849
850    
851    
852    // валидация формы подписки
853    $(function () {
854        var $form = $('#subscription-form');
855        var $submit = $form.find('[type="submit"]');
856    
857        function checkRequired() {
858            var isValid = true;
859    
860            $form.find('[required]').each(function () {
861                var $field = $(this);
862                var type = $field.attr('type');
863    
864                // checkbox
865                if (type === 'checkbox') {
866                    if (!$field.is(':checked')) {
867                        isValid = false;
868                        return false;
869                    }
870                }
871                // date и остальные поля
872                else {
873                    if (!$field.val() || !$field.val().trim()) {
874                        isValid = false;
875                        return false;
876                    }
877                }
878            });
879    
880            $submit.toggleClass('inactive', !isValid);
881        }
882    
883        // реагируем на ввод и изменения
884        $form.on('input change', '[required]', function () {
885            checkRequired();
886        });
887    
888        // первичная проверка (автозаполнение, date-picker и т.п.)
889        checkRequired();
890    });
891
892    
893    
894    // применяем маску к полю ввода телефонного номера
895    $('.phone-mask').mask('+7 (999) 999 99 99');
896
897
898
899    // "спасибо" при успешной отправке формы
900    var lastSubmittedForm = null;
901    
902    $(document).on('submit', 'form[data-bs-thx-title]', function () {
903        lastSubmittedForm = this;
904    });
905    
906    $(document).on('fetchit:success', function () {
907        if (!lastSubmittedForm) return;
908        $('#modal-form-1').modal('hide');
909        var modal = new bootstrap.Modal(
910            document.getElementById('modal-thx')
911        );
912        modal.show(lastSubmittedForm);
913    });
914    
915    var thxModal = document.getElementById('modal-thx');
916    thxModal.addEventListener('show.bs.modal', function (event) {
917        var source = event.relatedTarget;
918        if (!source) return;
919    
920        var title = source.getAttribute('data-bs-thx-title');
921        var text  = source.getAttribute('data-bs-thx-text');
922        var text2 = source.getAttribute('data-bs-thx-text-footer');
923    
924        if (title) {
925            thxModal.querySelector('.modal-title').textContent = title;
926        }
927    
928        if (text) {
929            thxModal.querySelector('.modal-text').textContent = text;
930        }
931    
932        var footer = thxModal.querySelector('.modal-footer');
933        var footerText = thxModal.querySelector('.modal-text-footer');
934    
935        if (text2 && text2.trim() !== '') {
936            footer.style.display = '';
937            footerText.textContent = text2;
938        } else {
939            footer.style.display = 'none';
940            footerText.textContent = '';
941        }
942    });
943    
944
945    
946    // куки
947    if (localStorage.getItem('cookiesAccepted') !== 'true') {
948        $('#cookies').fadeIn(400);
949    }
950    $('#cookieAccept').on('click', function() {
951        localStorage.setItem('cookiesAccepted', 'true');
952        $('#cookies').fadeOut(400); // плавное скрытие
953    });
954
955});

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.