PageSourceSearch

https://northbloom.ru/local/templates/sat/js/main.js?1.1

js northbloom.ru collected 2026-10-09 18:14:33 UTC 41,925 bytes, 1,050 lines download raw bytes

1$(document).ready(function () {
2    // === Часть 1: Навигация (только если есть #state-nav) ===
3    const $navSection = $('#state-nav');
4    if ($navSection.length > 0) {
5        const navItems = [];
6        const $wrapper = $('.wrapper');
7        const $navSection = $('#state-nav');
8
9        // Если навигационного блока нет — ничего не делаем
10        if ($navSection.length === 0) return;
11
12        // Обрабатываем каждую секцию .cl_state-text
13        $wrapper.find('section.cl_state-text').each(function () {
14            const $section = $(this);
15            const $h4 = $section.find('h4').first();
16
17            if ($h4.length) {
18                const titleText = $h4.text().trim();
19
20                let slug = titleText
21                    .toLowerCase()
22                    .replace(/[^\w\s-]/g, '')
23                    .replace(/[\s_-]+/g, '-')
24                    .replace(/^-+|-+$/g, '')
25                    .substring(0, 100); // ограничение длины на всякий случай
26
27                if (!slug) slug = 'section';
28
29                let finalId = slug;
30                let counter = 1;
31
32                while (document.getElementById(finalId)) {
33                    finalId = `${slug}-${counter}`;
34                    counter++;
35                }
36
37                $section.attr('id', finalId);
38                navItems.push({ text: titleText, id: finalId });
39            }
40        });
41
42        // Формируем список ссылок
43        if (navItems.length > 0) {
44            const listHtml = navItems
45                .map(item => `<li><a href="#${item.id}" class="sat-underline">${item.text}</a></li>`)
46                .join('');
47
48            // Находим ближайший ul внутри #state-nav и заменяем его содержимое
49            const $navList = $navSection.find('ul');
50            if ($navList.length) {
51                $navList.html(listHtml);
52            } else {
53                // Если ul нет — создаём структуру по вашему шаблону
54                const fullNavContent = `
55                    <div class="sat-container xxl:sat-w-[1920px]">
56                        <div class="sat-px-[0.875rem] md:sat-px-[3.75rem] lg:sat-px-[19.75rem] sat-mt-[2.5rem] xl:sat-mt-[3.6rem]">
57                            <div class="sat-flex sat-flex-col sat-gap-5 xl:sat-gap-[1.8rem]">
58                                <div class="sat-tracking-satspasing1 sat-text-2xl xl:sat-text-[2.16rem] f-color sat-leading-7 xl:sat-leading-[2.52rem]">Содержание</div>
59                                <ul class="sat-flex sat-flex-col sat-gap-5 xl:sat-gap-[1.8rem] sat-text-base xl:sat-text-[1.26rem] f-color-lite-grey sat-leading-tight sat-tracking-satspasing0 sat-menu-hover">
60                                    ${listHtml}
61                                </ul>
62                            </div>
63                        </div>
64                    </div>
65                `;
66                $navSection.html(fullNavContent);
67            }
68        }
69    }
70    // === END Часть 1 ===
71
72
73    // === Часть 2: Скрипты обработчики форм ===
74
75        function initSatForm($form) {
76        // Защита от повторной инициализации
77        if ($form.data('sat-initialized')) {
78            // Если форма уже инициализирована — просто обновим состояние кнопки
79            const $submitBtn = $form.find('button[type="submit"][name="web_form_apply"]');
80            const $placeholder = $form.find('.test.sat_btn');
81            if ($submitBtn.length && $placeholder.length) {
82                // Повторно обновим видимость
83                const allValid = $form.find('input[name^="form_"]:not([type="hidden"]):not([name="lic"])').toArray()
84                    .every(el => {
85                        const $f = $(el);
86                        return ($f.attr('name') === 'lic') || isValidFieldLocal($f);
87                    });
88                if (allValid) {
89                    $placeholder.hide();
90                    $submitBtn.show();
91                } else {
92                    $submitBtn.hide();
93                    $placeholder.show();
94                }
95            }
96            return;
97        }
98
99        const $submitBtn = $form.find('button[type="submit"][name="web_form_apply"]');
100        if ($submitBtn.length === 0) return;
101
102        // Удаляем старую заглушку, если вдруг осталась
103        $form.find('.test.sat_btn').remove();
104
105        // Сразу скрываем оригинальную кнопку
106        $submitBtn.hide();
107
108        // Определяем текст кнопки по имени формы
109        const formName = $form.attr('name');
110        let buttonText = 'Отправить'; // fallback
111
112        if (formName === 'SATZAPCATALOG') {
113            buttonText = 'Запросить прайс';
114        } else if (formName === 'PARTHER') {
115            buttonText = 'Подать заявку';
116        }
117
118        const $placeholder = $(`<div class="test w-100 sat_btn sat-w-[100%] color-white sat-text-center sat-text-[20px] max-md:sat-text-base sat-py-[19px] max-md:sat-py-[13.5px] sat-rounded-xl btn-orange">${buttonText}</div>`);
119        $submitBtn.after($placeholder);
120
121        // Помечаем форму как инициализированную
122        $form.data('sat-initialized', true);
123
124        // === Остальная логика ===
125        const $fields = $form.find('input[name^="form_"]:not([type="hidden"]):not([name="lic"])');
126
127        function isEmailField($field) {
128            const name = $field.attr('name');
129            return name === 'form_email_57' || name === 'form_email_31';
130        }
131        function isPhoneField($field) {
132            const name = $field.attr('name');
133            return name === 'form_text_56' || name === 'form_text_30';
134        }
135        function isLicField($field) {
136            return $field.attr('name') === 'lic';
137        }
138
139        function getPhoneDigits(val) { return val.replace(/\D/g, ''); }
140        function formatPhoneNumber(digits) {
141            if (digits.length < 2) return '+7';
142            let s = '+7 (' + digits.substring(1, 4);
143            if (digits.length >= 4) s += ') ' + digits.substring(4, 7);
144            if (digits.length >= 7) s += '-' + digits.substring(7, 9);
145            if (digits.length >= 9) s += '-' + digits.substring(9, 11);
146            return s;
147        }
148
149        // Локальная функция для проверки — нужна для обновления кнопки
150        function isValidFieldLocal($field) {
151            if (isLicField($field)) return true;
152            const val = $field.val().trim();
153            if (isEmailField($field)) {
154                return val === '' || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val);
155            }
156            if (isPhoneField($field)) {
157                const digits = getPhoneDigits(val);
158                return digits.length === 11 && digits.startsWith('7');
159            }
160            return val !== '';
161        }
162
163        function showFieldError($field, message = 'Поле обязательно для заполнения') {
164            if (isLicField($field)) return;
165            $field.css('border', '1px solid red');
166            const $next = $field.next();
167            if ($next.hasClass('field-error')) $next.remove();
168            $field.after(`<div class="field-error" style="color: red; font-size: 0.875rem; margin-top: 4px;">${message}</div>`);
169        }
170
171        function hideFieldError($field) {
172            if (isLicField($field)) return;
173            $field.css('border', '');
174            const $next = $field.next();
175            if ($next.hasClass('field-error')) $next.remove();
176        }
177
178        function validateField($field) {
179            if (isLicField($field)) return;
180            const val = $field.val().trim();
181
182            if (isPhoneField($field)) {
183                const digits = getPhoneDigits(val);
184                if (digits === '') showFieldError($field, 'Поле обязательно для заполнения');
185                else if (digits.length !== 11 || !digits.startsWith('7')) showFieldError($field, 'Некорректный телефон');
186                else hideFieldError($field);
187                return;
188            }
189
190            if (isEmailField($field)) {
191                if (val !== '' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) showFieldError($field, 'Некорректный email');
192                else hideFieldError($field);
193                return;
194            }
195
196            val === '' ? showFieldError($field) : hideFieldError($field);
197        }
198
199        function validateAllForSubmit() {
200            let valid = true;
201            $fields.each(function () {
202                const $f = $(this);
203                if (isLicField($f)) return;
204                if (!isValidFieldLocal($f)) {
205                    showFieldError($f);
206                    valid = false;
207                } else {
208                    hideFieldError($f);
209                }
210            });
211            return valid;
212        }
213
214        function updateSubmitButton() {
215            const allValid = $fields.toArray().every(el => isValidFieldLocal($(el)));
216            if (allValid) {
217                $placeholder.hide();
218                $submitBtn.show();
219            } else {
220                $submitBtn.hide();
221                $placeholder.show();
222            }
223        }
224
225        const $phoneField = $fields.filter(function () { return isPhoneField($(this)); }).first();
226        if ($phoneField.length) {
227            $phoneField.on('focus', function () { $(this).val(getPhoneDigits($(this).val())); });
228            $phoneField.on('input', function () {
229                let val = $(this).val().replace(/\D/g, '');
230                if (val.length > 11) val = val.substring(0, 11);
231                $(this).val(val);
232                updateSubmitButton();
233            });
234            $phoneField.on('blur', function () {
235                let digits = getPhoneDigits($(this).val());
236                if (digits.startsWith('8')) digits = '7' + digits.substring(1);
237                else if (digits.startsWith('9')) digits = '7' + digits;
238                if (digits.length > 11) digits = digits.substring(0, 11);
239                $(this).val(digits ? formatPhoneNumber(digits) : '');
240                validateField($(this));
241            });
242        }
243
244        const otherFields = $fields.not($phoneField);
245        otherFields.on('focus', function () { $(this).data('touched', true); });
246        otherFields.on('input change blur', function () {
247            validateField($(this));
248            updateSubmitButton();
249        });
250
251        $placeholder.on('click', function () {
252            if (validateAllForSubmit()) {
253                $form.submit();
254            }
255        });
256
257        $form.on('submit', function (e) {
258            if (!validateAllForSubmit()) e.preventDefault();
259        });
260
261        updateSubmitButton();
262        }
263
264        // Для статичных форм
265        $('form[name="SATZAPCATALOG"], form[name="PARTHER"]').each(function () {
266            initSatForm($(this));
267        });
268
269        // Для Fancybox v3
270        $(document).on('afterShow.fb', function (e, instance, slide) {
271            $(slide.$slide).find('form[name="SATZAPCATALOG"], form[name="PARTHER"]').each(function () {
272                // Сброс флага, если форма "чистая" (например, если Fancybox кэширует контент)
273                // Или оставить как есть — защита внутри initSatForm сработает
274                initSatForm($(this));
275            });
276        });
277    // ===  END Часть 2 ===
278
279    var hashTagActive = "";
280    $("a[href^='#']").on("click touchstart", function (event) {
281        if (hashTagActive != this.hash) { // предотвращение повторных кликов на одну и ту же ссылку
282            event.preventDefault();
283            // расчет места назначения
284            var dest = 0;
285            if ($(this.hash).offset().top > $(document).height() - $(window).height()) {
286                dest = $(document).height() - $(window).height();
287            } else {
288                dest = $(this.hash).offset().top;
289            }
290            // прокрутка к месту назначения
291            $('html,body').animate({
292                scrollTop: dest
293            }, 2000, 'swing');
294            hashTagActive = this.hash;
295        }
296    });
297
298    $('.preloader').on('click', function (e) {
299        e.preventDefault();
300
301        const $iframe = $('#rutube-player');
302        const $preloader = $('.preloader');
303
304        let src = $iframe.attr('src');
305
306        // Очищаем и добавляем autoplay + muted
307        src = src
308            .replace(/&?autoplay=\d*/g, '')
309            .replace(/&?muted=\d*/g, '');
310        const sep = src.includes('?') ? '&' : '?';
311        src = src + sep + 'autoplay=1&muted=1';
312
313        // 1. Сразу меняем src — в рамках клика
314        $iframe.attr('src', src);
315
316        // 2. Плавно скрываем preloader
317        $preloader.fadeOut(500);
318
319        // 3. Через 1–2 секунды даём "второй толчок"
320        setTimeout(() => {
321            // Попытка 1: клик по iframe
322            $iframe[0].click();
323
324            // Попытка 2: postMessage (на всякий случай)
325            try {
326                $iframe[0].contentWindow.postMessage('{"method":"play"}', 'https://rutube.ru  ');
327            } catch (e) {
328                // Игнорируем ошибки CORS
329            }
330
331            // Попытка 3: фокус + клик
332            $iframe[0].focus();
333            $iframe[0].click();
334        }, 1500); // 1.5 секунды — обычно хватает для инициализации плеера
335    });
336
337(function () {
338    // Создаём оверлей один раз
339    if ($('#simple-modal-backdrop').length === 0) {
340        $('body').append('<div id="simple-modal-backdrop"></div>');
341    }
342
343    // === ФУНКЦИЯ ЗАКРЫТИЯ (добавим логирование для отладки) ===
344    window.closeSimpleModal = function () {
345        console.log("Закрытие модального окна запрошено");
346
347        const $backdrop = $('#simple-modal-backdrop');
348        const $activeModals = $('.simple-modal.active');
349
350        if ($activeModals.length === 0 && !$backdrop.hasClass('active')) {
351            console.warn("Нет активных модалок для закрытия");
352            return;
353        }
354
355        // Убираем класс active
356        $activeModals.removeClass('active');
357        $backdrop.removeClass('active');
358
359        // Скрываем через 300 мс (время анимации)
360        setTimeout(() => {
361            $activeModals.hide();
362            $('body').css('overflow', '');
363            console.log("Модальное окно закрыто");
364        }, 300);
365    };
366
367    // === ОБРАБОТЧИКИ ЗАКРЫТИЯ (надёжные) ===
368    $(document).on('click', '#simple-modal-backdrop', function (e) {
369        console.log("Клик по оверлею");
370        if ($(e.target).is('#simple-modal-backdrop')) {
371            closeSimpleModal();
372        }
373    });
374
375    $(document).on('click', '.simple-modal__close', function (e) {
376        e.preventDefault();
377        console.log("Клик по крестику");
378        closeSimpleModal();
379    });
380
381    // === ESC ===
382    $(document).on('keydown', function (e) {
383        if (e.key === 'Escape' && $('#simple-modal-backdrop').hasClass('active')) {
384            closeSimpleModal();
385        }
386    });
387
388    // === ОТКРЫТИЕ (оставим как есть, но без дублирования) ===
389    function openSimpleModal(modalId) {
390        const $modal = $(modalId);
391        if ($modal.length === 0) return;
392
393        if ($('body').hasClass('mob-active')) {
394            $('body').removeClass('mob-active');
395            $('.sat-mob-btn-menu').removeClass('active');
396            $('.mob-menu').slideUp();
397        }
398
399        $('#simple-modal-backdrop').addClass('active');
400        $modal.show(300, function () {
401            $modal.addClass('active');
402        });
403        $('body').css('overflow', 'hidden');
404
405        // Капча (если нужно)
406        if (modalId === '#call_back' && !window.yaCaptchaInitialized) {
407            const container = $modal.find('.ya-captcha-container')[0];
408            if (container && typeof ymaps !== 'undefined' && ymaps.Captcha) {
409                window.yaCaptchaInstance = new ymaps.Captcha({ container: container });
410                window.yaCaptchaInitialized = true;
411            }
412        }
413    }
414
415    $(document).on('click', '.call_back', function (e) {
416        e.preventDefault();
417        openSimpleModal('#call_back');
418    });
419
420    $(document).on('click', '.call_parners', function (e) {
421        e.preventDefault();
422        openSimpleModal('#call_parners');
423    });
424})();
425
426
427    // $(document).on('click', '.call_parners', function (e) {
428    //     e.preventDefault();
429    //     if ($('body').hasClass('mob-active')) {
430    //         $('body').removeClass('mob-active');
431    //         $('.sat-mob-btn-menu').removeClass('active');
432    //         $('.mob-menu').slideUp();
433    //     }
434
435    //     Fancybox.show([{
436    //         src: '#call_parners',
437    //         type: 'inline',
438    //         touch: false,
439    //     }]);
440    // });
441
442    // Инициализация всех dropdown внутри контейнера
443    $('body').each(function () {
444        var $container = $(this);
445        var $dropdown = $container.find('.wrapper-dropdown-3');
446
447        // Инициализация dropdown
448        initializeDropdown($dropdown);
449
450        // Обработчик для открытия/закрытия ul и добавления класса active
451        $container.find('#dd').click(function (e) {
452            e.stopPropagation();
453            var $ul = $(this).find('ul');
454
455            if ($ul.is(':visible')) {
456                $ul.slideUp();
457                $(this).removeClass('active');
458            } else {
459                $ul.slideDown();
460                $(this).addClass('active');
461            }
462        });
463    });
464
465    // Обработка клика вне всех контейнеров
466    $(document).click(function () {
467        $('body').each(function () {
468            $(this).find('#dd').find('ul').slideUp();
469            $(this).find('#dd').removeClass('active');
470        });
471    });
472
473    $('.copy-link-btn').on('click', function () {
474        const $btn = $(this);
475        const $copyExet = $('.copy-exet');
476
477        if ($btn.hasClass('copied')) return;
478
479        const copyToClipboard = (text) => {
480            // Современный способ
481            if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
482                return navigator.clipboard.writeText(text);
483            }
484
485            // Fallback для старых браузеров (включая мобильные)
486            const textarea = document.createElement('textarea');
487            textarea.value = text;
488            textarea.setAttribute('readonly', '');
489            textarea.style.position = 'absolute';
490            textarea.style.left = '-9999px';
491            document.body.appendChild(textarea);
492            textarea.select();
493            try {
494                const success = document.execCommand('copy');
495                document.body.removeChild(textarea);
496                if (success) {
497                    return Promise.resolve();
498                } else {
499                    return Promise.reject(new Error('execCommand failed'));
500                }
501            } catch (err) {
502                document.body.removeChild(textarea);
503                return Promise.reject(err);
504            }
505        };
506
507        copyToClipboard(window.location.href)
508            .then(() => {
509                $btn.addClass('copied');
510                $copyExet.find('.copy-success').remove();
511
512                const $msg = $('<span class="copy-success">Ссылка скопирована</span>');
513                $copyExet.append($msg);
514
515                setTimeout(() => $msg.addClass('show'), 20);
516
517                setTimeout(() => {
518                    $msg.removeClass('show');
519                    $btn.removeClass('copied');
520                    setTimeout(() => $msg.remove(), 350);
521                }, 2000);
522            })
523            .catch(err => {
524                console.error('Ошибка копирования:', err);
525                // Опционально: показать сообщение об ошибке
526                alert('Не удалось скопировать ссылку. Попробуйте вручную.');
527            });
528    });
529
530    function initializeDropdown($dropdown) {
531        var $span = $dropdown.find('> span');
532        var $input = $dropdown.closest('form').find('input.spec_input[name="form_text_44"]');
533        var $firstLi = $dropdown.find('ul li').first();
534        var defaultText = $firstLi.find('a').text();
535        var defaultValue = $firstLi.find('a').data('value');
536
537        // Устанавливаем начальное значение
538        $span.text(defaultText);
539        $input.val(defaultValue);
540
541        // Обработчик клика по элементам списка
542        $dropdown.find('ul li a').click(function (e) {
543            e.preventDefault();
544            var text = $(this).text();
545            var value = $(this).data('value');
546
547            $span.text(text);
548            $input.val(value);
549        });
550    }
551
552    // === АККАРДЕОНЫ ===
553
554// SVG-иконка как строка (готова к вставке)
555    var arrowSvg = `
556        <svg xmlns="http://www.w3.org/2000/svg" 
557             class="sat-absolute accordion-arrow" 
558             viewBox="0 0 10 15" fill="none">
559            <use xlink:href="#icon-navbtn"></use>
560        </svg>
561    `;
562
563    // === Добавляем иконки после всех h4 внутри product-in-accardion (если ещё не добавлены) ===
564    $('.product-in-accardion h4').each(function () {
565        var $h4 = $(this);
566        // Проверяем, есть ли уже иконка рядом (чтобы не дублировать)
567        if ($h4.next('.accordion-arrow').length === 0 && !$h4.siblings('svg').hasClass('accordion-arrow')) {
568            $h4.after(arrowSvg);
569        }
570    });
571
572    // === Основной аккордеон ===
573    $('.q-items').on('click', '.q-item', function (e) {
574        if ($(e.target).closest('.sub_text').length) return;
575
576        var $thisItem = $(this);
577        var $subText = $thisItem.find('.sub_text');
578
579        if ($thisItem.hasClass('active')) {
580            $thisItem.removeClass('active');
581            $subText.find('.state-text').removeClass('q-sub-active').find('p').hide();
582            $subText.stop(true, true).slideUp(500);
583        } else {
584            $('.q-item').removeClass('active');
585            $('.sub_text').each(function () {
586                var $st = $(this);
587                $st.find('.state-text').removeClass('q-sub-active').find('p').hide();
588                $st.stop(true, true).slideUp(500);
589            });
590            $thisItem.addClass('active');
591            $subText.stop(true, true).slideDown(500);
592        }
593    });
594
595    // === Клик по h4 — управление подаккордеоном ===
596    $('.product-in-accardion').on('click', 'h4', function () {
597        var $h4 = $(this);
598        var $stateText = $h4.closest('.state-text');
599        // Берём ТОЛЬКО <p>, идущие до следующего <h4>
600        var $targetPs = $h4.nextUntil('h4').filter('p');
601
602        if ($targetPs.length === 0) return;
603
604        if ($stateText.hasClass('q-sub-active')) {
605            $stateText.removeClass('q-sub-active');
606            $targetPs.slideUp(300);
607        } else {
608            var $block = $stateText.closest('.product-in-accardion');
609            $block.find('.state-text').removeClass('q-sub-active');
610            $block.find('p').slideUp(300);
611
612            $stateText.addClass('q-sub-active');
613            $targetPs.slideDown(300);
614        }
615    });
616
617    // === Автооткрытие первого подблока при загрузке ===
618    var $firstActiveQItem = $('.q-item.active').first();
619    if ($firstActiveQItem.length) {
620        var $firstStateText = $firstActiveQItem.find('.sub_text .state-text').first();
621        if ($firstStateText.length) {
622            var $firstPs = $firstStateText.find('p');
623            if ($firstPs.length) {
624                $firstStateText.addClass('q-sub-active');
625                $firstPs.show(); // или .slideDown(300)
626            }
627        }
628    }
629
630    // === END АККАРДЕОНЫ ===
631
632
633    function setFullHeight() {
634        var viewportHeight = $(window).height();
635        $('.full-height').css('height', (viewportHeight - 60) + 'px');
636    }
637
638    // Обновляем высоту при клике на кнопку
639    $('.sat-mob-btn-menu').on('click', function (e) {
640        e.preventDefault();
641
642        if ($('body').hasClass('mob-active')) {
643            $('body').removeClass('mob-active');
644            $('.sat-mob-btn-menu').removeClass('active');
645            $('.mob-menu').slideUp();
646        } else {
647            $('body').addClass('mob-active');
648            $('.sat-mob-btn-menu').addClass('active');
649            $('.mob-menu').slideDown();
650
651            // Обновляем высоту блока при открытии меню
652            setFullHeight();
653        }
654    });
655
656    // Можно оставить вызов при загрузке, чтобы задать начальную высоту
657    setFullHeight();
658
659    // Обновлять при изменении размера окна
660    $(window).on('resize', function () {
661        setFullHeight();
662    });
663
664    var owl = $('.owl-slidertop');
665    owl.owlCarousel({
666        margin: 10,
667        nav: true,
668        loop: true,
669        autoplay: true,
670        slideSpeed: 3000,
671        autoplayTimeout: 10000,
672        // animateOut: 'fadeOut',
673        // animateIn: 'fadeIn',
674        // autoplayHoverPause:false,
675        lazyLoad: true,
676        navText: ["<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 15' fill='none'><path d='M9.00001 13.4995L3.00001 7.49951L9.00001 1.49951' stroke-width='3'/></svg>", "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 15' fill='none'><path d='M2 1.49951L8 7.49951L2 13.4995' stroke-width='3'/></svg>"],
677        items: 1,
678    });
679
680    var owl = $('.owl-slidercome');
681    owl.owlCarousel({
682        margin: 10,
683        nav: true,
684        loop: true,
685        autoplay: true,
686        slideSpeed: 3000,
687        autoplayTimeout: 10000,
688        // animateOut: 'fadeOut',
689        // animateIn: 'fadeIn',
690        // autoplayHoverPause:false,
691        lazyLoad: true,
692        navText: ["<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 15' fill='none'><path d='M9.00001 13.4995L3.00001 7.49951L9.00001 1.49951' stroke-width='3'/></svg>", "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 15' fill='none'><path d='M2 1.49951L8 7.49951L2 13.4995' stroke-width='3'/></svg>"],
693        items: 1,
694    });
695
696    var owl = $('.owl-banner');
697    owl.owlCarousel({
698        margin: 10,
699        nav: true,
700        dots: true,
701        loop: true,
702        autoplay: true,
703        slideSpeed: 3000,
704        autoplayTimeout: 10000,
705        // animateOut: 'fadeOut',
706        // animateIn: 'fadeIn',
707        // autoplayHoverPause:false,
708        lazyLoad: true,
709        navText: ["<svg viewBox='0 0 12 20' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M10.5 18L2.5 10L10.5 2' stroke-width='3'/></svg>", "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 20' fill='none'><path d='M1.5 18L9.5 10L1.5 2' stroke-width='3'/></svg>"],
710        items: 1,
711    });
712
713
714    function getTotalSlidesWidth(owlContainer) {
715        var totalWidth = 0;
716        owlContainer.find('.owl-item').each(function () {
717            totalWidth += $(this).outerWidth(true);
718        });
719        return totalWidth;
720    }
721    function getContainerWidth(owlContainer) {
722        return owlContainer.get(0).getBoundingClientRect().width;
723    }
724    function limitTranslate(owlContainer) {
725        var stage = owlContainer.find('.owl-stage');
726        var transform = stage.css('transform');
727
728        // Получение translateX из transform
729        var translateX = 0;
730        if (transform !== 'none') {
731            var matrix = window.getComputedStyle(stage.get(0)).transform;
732            if (matrix !== 'none') {
733                var values = matrix.split('(')[1].split(')')[0].split(',');
734                translateX = parseFloat(values[4]);
735            }
736        }
737
738        var totalSlidesWidth = getTotalSlidesWidth(owlContainer);
739        var containerWidth = getContainerWidth(owlContainer);
740        var maxTranslate = totalSlidesWidth - containerWidth;
741        if (maxTranslate < 0) maxTranslate = 0;
742
743        if (translateX > maxTranslate) {
744            stage.css('transform', 'translate3d(-' + maxTranslate + 'px, 0, 0)');
745        }
746    }
747    function getTotalSlidesWidth(owl) {
748        var totalWidth = 0;
749        owl.find(".owl-item").each(function () {
750            totalWidth += $(this).outerWidth(true);
751        });
752        return totalWidth;
753    }
754    function getContainerWidth(owl) {
755        return owl.get(0).getBoundingClientRect().width;
756    }
757    function limitTranslate(owl) {
758        var stage = owl.find('.owl-stage');
759        if (stage.length === 0) {
760            // Элемент не найден, ничего не делаем
761            return;
762        }
763        var stageElement = stage.get(0);
764        if (!stageElement) {
765            return;
766        }
767        var rectStage = stage.get(0).getBoundingClientRect();
768        var transform = stage.css('transform');
769        var translateX = Math.abs(parseInt(transform.split(',')[4])); // текущий translateX
770
771        var totalSlidesWidth = getTotalSlidesWidth(owl);
772        var containerWidth = getContainerWidth(owl);
773
774        var maxTranslate = totalSlidesWidth - containerWidth;
775        if (maxTranslate < 0) maxTranslate = 0;
776
777        if (translateX > maxTranslate) {
778            stage.css('transform', 'translate3d(-' + maxTranslate + 'px, 0, 0)');
779        }
780    }
781
782    // Функция для инициализации каруселей и привязки событий
783    function initCarousel(selector) {
784        var owl = $(selector);
785        owl.owlCarousel({
786            margin: 10,
787            nav: true,
788            lazyLoad: true,
789            dots: false,
790            autoWidth: true,
791            navText: [
792                "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 15' fill='none'><path d='M9.00001 13.4995L3.00001 7.49951L9.00001 1.49951' stroke-width='3'/></svg>",
793                "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 15' fill='none'><path d='M2 1.49951L8 7.49951L2 13.4995' stroke-width='3'/></svg>"
794            ],
795        });
796
797        // Обновляем при смене слайда и при изменении размера окна
798        owl.on('translated.owl.carousel', function () {
799            limitTranslate(owl);
800        });
801        $(window).on('resize', function () {
802            limitTranslate(owl);
803        });
804    }
805
806    // Инициализация трех каруселей
807    if (window.innerWidth >= 780) {
808        initCarousel('.owl-states');
809    }
810    initCarousel('.owl-our-team');
811    initCarousel('.owl-product');
812
813 // Находим все карточки товаров, содержащие новый контейнер со слайдами
814
815    // Функция для создания полос
816    function initHoverBands(itemProduct) {
817        const containerImg = itemProduct.find('.img-container');
818        const mainImg = containerImg.find('> img'); // основное изображение
819        if (!mainImg.length) return;
820
821        // Находим контейнер со слайдами
822        const sliderContainer = itemProduct.find('.product-item-image-slider-slide-container');
823        if (!sliderContainer.length) return;
824
825        // Получаем все элементы-слайды
826        const itemsImg = sliderContainer.find('.product-item-image-slide.item');
827
828        // Если нет слайдов — выходим
829        if (itemsImg.length === 0) return;
830
831        // Создаем контейнер для полос
832        const bandsContainer = $('<div class="hover-bands"></div>').css({
833            position: 'absolute',
834            top: 0,
835            left: 0,
836            width: '100%',
837            height: '100%',
838            display: 'flex',
839            paddingLeft: '5px',
840            paddingRight: '5px',
841            cursor: 'pointer',
842            zIndex: 4
843        }).appendTo(containerImg.css('position', 'relative'));
844
845        // Создаем полосы
846        itemsImg.each(function (index) {
847            let bgImageStyle = $(this).css('background-image');
848            let src = '';
849            if (bgImageStyle && bgImageStyle !== 'none') {
850                src = bgImageStyle.replace(/^url\(['"]?|['"]?\)$/g, '');
851            }
852            if (!src) {
853                src = $(this).data('src') || '';
854            }
855            if (!src) return;
856
857            const span = $('<span></span>')
858                .attr('id', 'band-' + index)
859                .data('src', src)
860                .css({
861                    flex: 1,
862                    height: '98%',
863                    display: 'block',
864                    position: 'relative',
865                    marginBottom: '10px',
866                });
867
868            const borderDiv = $('<div></div>')
869                .css({
870                    width: '90%',
871                    borderBottom: '2px solid rgba(161, 161, 161, 0.16)',
872                    margin: '0 auto',
873                    position: 'absolute',
874                    bottom: 0,
875                    left: 0,
876                    right: 0,
877                });
878
879            span.append(borderDiv);
880
881            if (index === 0) {
882                span.addClass('active').find('div').css('border-bottom-color', '#D0D0D0');
883            }
884
885            span.hover(
886                function () {
887                    const src = $(this).data('src');
888                    if (src) mainImg.attr('src', src);
889                    $(this).addClass('active');
890                    $(this).find('div').css('border-bottom-color', '#D0D0D0');
891                    $(this).siblings().removeClass('active');
892                    $(this).siblings().find('div').css('border-bottom-color', 'rgba(161, 161, 161, 0.16)');
893                },
894                function () {
895                    const firstSpan = bandsContainer.find('span').first();
896                    const firstSrc = firstSpan.data('src');
897                    if (firstSrc) mainImg.attr('src', firstSrc);
898                    firstSpan.addClass('active');
899                    firstSpan.find('div').css('border-bottom-color', '#D0D0D0');
900                    firstSpan.siblings().removeClass('active');
901                    firstSpan.siblings().find('div').css('border-bottom-color', 'rgba(161, 161, 161, 0.16)');
902                }
903            ).appendTo(bandsContainer);
904        });
905    }
906
907    // === ИСПРАВЛЕНО: Безопасная инициализация товаров с ограничением попыток ===
908    // Было: бесконечная рекурсия при отсутствии слайдов → зависание на мобильных.
909    // Стало: максимум 20 попыток (2 секунды), затем сдаёмся.
910    let initProductsAttempts = 0;
911    const MAX_INIT_ATTEMPTS = 20;
912
913    function initAllProducts() {
914        if (initProductsAttempts >= MAX_INIT_ATTEMPTS) {
915            console.warn('initAllProducts: gave up after too many attempts');
916            return;
917        }
918
919        let foundAll = true;
920        $('.sat-products .item-product').each(function () {
921            const itemProduct = $(this);
922            if (itemProduct.find('.product-item-image-slider-slide-container .product-item-image-slide.item').length > 0) {
923                initHoverBands(itemProduct);
924            } else {
925                foundAll = false;
926            }
927        });
928
929        if (!foundAll) {
930            initProductsAttempts++;
931            setTimeout(initAllProducts, 100);
932        } else {
933            // Сбрасываем счётчик, если всё загрузилось — на случай повторного вызова после смены SKU
934            initProductsAttempts = 0;
935        }
936    }
937
938    // /* СТАРЫЙ КОД — УДАЛИТЬ ПОСЛЕ ТЕСТИРОВАНИЯ
939    // function initAllProducts() {
940    //     $('.sat-products .item-product').each(function () {
941    //         const itemProduct = $(this);
942    //         // Проверяем, есть ли уже контейнер слайдера
943    //         if (itemProduct.find('.product-item-image-slider-slide-container .product-item-image-slide.item').length > 0) {
944    //             // Если есть — инициализируем полосы
945    //             initHoverBands(itemProduct);
946    //         } else {
947    //             // Если нет — ставим таймер и пробуем снова через 100мс
948    //             setTimeout(function() {
949    //                 initAllProducts(); // ← ОПАСНАЯ РЕКУРСИЯ!
950    //             }, 100);
951    //         }
952    //     });
953    // }
954    // */
955
956    // Запускаем первую проверку
957    initAllProducts();
958
959    // Также подписываемся на событие изменения SKU (если есть)
960    // Это важно, если пользователь меняет вариант — нужно заново инициализировать полосы
961    $(document).on('click', '.bx_catalog_item_scu_ul li', function() {
962        // После клика по SKU — ждем немного, чтобы DOM обновился
963        setTimeout(function() {
964            // /* СТАРЫЙ КОД — УДАЛИТЬ
965            // initAllProducts();
966            // */
967            // Новый: просто пробуем один раз, без рекурсии
968            $('.sat-products .item-product').each(function () {
969                const item = $(this);
970                if (item.find('.product-item-image-slider-slide-container .product-item-image-slide.item').length > 0) {
971                    initHoverBands(item);
972                }
973            });
974        }, 300);
975    });
976
977
978
979    const $logButton = $('.sub-state-nav-log');
980    const $dropdown = $('.sub-state-nav');
981    const $dropdownLinks = $dropdown.find('a');
982
983    // Изначально скрываем выпадающий список
984    $dropdown.hide();
985
986    // Переключение видимости списка по клику на кнопку
987    $logButton.on('click', function (e) {
988        e.stopPropagation();
989        $dropdown.stop(true, true).slideToggle(300);
990    });
991
992    // Обработка клика по ссылке внутри выпадающего списка
993    $dropdownLinks.on('click', function (e) {
994        e.preventDefault();
995        const linkText = $(this).text();
996        $logButton.contents().first()[0].textContent = linkText;
997        $dropdown.stop(true, true).slideUp(300);
998    });
999
1000    // Закрытие выпадающего списка при клике вне его области
1001    $(document).on('click', function (e) {
1002        if (!$(e.target).closest('.mob_state_nav').length) {
1003            $dropdown.stop(true, true).slideUp(300);
1004        }
1005    });
1006
1007
1008
1009    const items = [
1010        { selector: '#item_1', value: 28 },
1011        { selector: '#item_2', value: 500 },
1012        { selector: '#item_3', value: 3 },
1013        { selector: '#item_4', value: 23500 }
1014    ];
1015
1016    const maxDuration = 3000; // 3 секунды
1017
1018    function formatNumber(num) {
1019        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
1020    }
1021
1022    let animated = false; // чтобы запускать один раз
1023
1024    const target = document.querySelector('#team'); // замените на ваш блок
1025
1026    const observer = new IntersectionObserver((entries, obs) => {
1027        entries.forEach(entry => {
1028            if (entry.isIntersecting && !animated) {
1029                animated = true;
1030                items.forEach(item => {
1031                    $({ count: 0 }).animate({ count: item.value }, {
1032                        duration: maxDuration,
1033                        easing: 'linear',
1034                        step: function () {
1035                            $(item.selector).text(formatNumber(Math.floor(this.count)));
1036                        },
1037                        complete: function () {
1038                            $(item.selector).text(formatNumber(item.value));
1039                        }
1040                    });
1041                });
1042                obs.disconnect(); // отключаем наблюдение после запуска
1043            }
1044        });
1045    }, { threshold: 0.1 }); // срабатывает, когда 10% блока видно
1046
1047    if (target) {
1048        observer.observe(target);
1049    }
1050});

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.