PageSourceSearch

https://www.mirkrasok.ru/local/templates/mirkrasok_2026v2/new_js/bundle.js?178997365936833

js mirkrasok.ru collected 2026-10-03 20:58:33 UTC 36,833 bytes, 1,124 lines download raw bytes

1const header = document.querySelector('header');
2
3if (header) {
4  // Существующий код скролла
5  const toggleScrolledClass = () => {
6    if (window.scrollY > 0) {
7      header.classList.add('scrolled');
8    } else {
9      header.classList.remove('scrolled');
10    }
11  };
12
13  window.addEventListener('scroll', toggleScrolledClass);
14  toggleScrolledClass();
15
16  // Независимое отслеживание изменений класса
17  const observer = new MutationObserver((mutations) => {
18    mutations.forEach((mutation) => {
19      if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
20        if (header.classList.contains('scrolled')) {
21          document.documentElement.classList.add('scrolldig');
22        } else {
23          document.documentElement.classList.remove('scrolldig');
24        }
25      }
26    });
27  });
28
29  observer.observe(header, {
30    attributes: true,
31    attributeFilter: ['class']
32  });
33}
34
35document.addEventListener('DOMContentLoaded', function() {
36  const mobileMenuButton = document.querySelector('.btn-small-screens-menu');
37  const headerNav = document.querySelector('.header-nav');
38  const closeMenuButton = document.querySelector('.close-menu-button');
39
40  function isMobileView() {
41    return window.innerWidth <= 1024;
42  }
43
44  function openMenu() {
45    headerNav.classList.add('show');
46    document.body.style.overflow = 'hidden';
47  }
48
49  function closeMenu() {
50    headerNav.classList.remove('show');
51    document.body.style.overflow = '';
52  }
53
54  function toggleMenu() {
55    if (!isMobileView()) return;
56
57    if (headerNav.classList.contains('show')) {
58      closeMenu();
59    } else {
60      openMenu();
61    }
62  }
63
64  if (mobileMenuButton) {
65    mobileMenuButton.addEventListener('click', toggleMenu);
66    // Метка для атрибутов onclick в разметке: пока её нет, меню открывают они.
67    // На самой кнопке, а не глобально: композит подменяет разметку после
68    // готовности документа, и новая кнопка остаётся без обработчика.
69    mobileMenuButton.mkMenuBound = 1;
70    var closeButton = document.querySelector('.close-menu-button');
71    if (closeButton) {
72      closeButton.mkMenuBound = 1;
73    }
74  }
75
76  if (closeMenuButton) {
77    closeMenuButton.addEventListener('click', function() {
78      if (isMobileView() && headerNav.classList.contains('show')) {
79        closeMenu();
80      }
81    });
82  }
83
84  const menuLinks = document.querySelectorAll('.main-menu a');
85  menuLinks.forEach(link => {
86    link.addEventListener('click', function() {
87      if (isMobileView() && headerNav.classList.contains('show')) {
88        closeMenu();
89      }
90    });
91  });
92
93  window.addEventListener('resize', function() {
94    if (window.innerWidth > 1024) {
95      closeMenu();
96    }
97  });
98});
99
100document.addEventListener('DOMContentLoaded', function() {
101  const catalogButton = document.querySelector('.popup-menu-button');
102  const catalogMenu = document.querySelector('.catalog-menu');
103  const catalogCloseButton = document.querySelector('.catalog-close-button');
104  const burgerCatalogLinks = document.querySelectorAll('.js-burger-catalog');
105
106  function openMenu() {
107    catalogMenu.classList.add('is-active');
108    document.body.style.overflow = 'hidden';
109  }
110
111  function closeMenu() {
112    catalogMenu.classList.remove('is-active');
113    document.body.style.overflow = '';
114  }
115
116  function toggleMenu() {
117    if (catalogMenu.classList.contains('is-active')) {
118      closeMenu();
119    } else {
120      openMenu();
121    }
122  }
123
124  function handleClickOutside(event) {
125    if (catalogMenu.classList.contains('is-active')) {
126      if (!catalogButton.contains(event.target) && !catalogMenu.contains(event.target)) {
127        closeMenu();
128      }
129    }
130  }
131
132  function handleEscKey(event) {
133    if (event.key === 'Escape' && catalogMenu.classList.contains('is-active')) {
134      closeMenu();
135    }
136  }
137
138  if (catalogButton) {
139    catalogButton.addEventListener('click', toggleMenu);
140  }
141
142  if (catalogCloseButton) {
143    catalogCloseButton.addEventListener('click', function() {
144      if (catalogMenu.classList.contains('is-active')) {
145        closeMenu();
146      }
147    });
148  }
149
150  burgerCatalogLinks.forEach(link => {
151    link.addEventListener('click', function(event) {
152      if (!window.matchMedia('(max-width: 1024px)').matches || !catalogMenu) return;
153
154      event.preventDefault();
155      event.stopPropagation();
156
157      const headerNav = document.querySelector('.header-nav');
158      if (headerNav) headerNav.classList.remove('show');
159      openMenu();
160    });
161  });
162
163  document.addEventListener('click', handleClickOutside);
164  document.addEventListener('keydown', handleEscKey);
165
166  window.addEventListener('resize', function() {
167    if (window.innerWidth > 1024) {
168      closeMenu();
169    }
170  });
171});
172
173document.addEventListener('DOMContentLoaded', function() {
174  const anchorLinks = document.querySelectorAll('a[href^="#"]');
175
176  anchorLinks.forEach(link => {
177    link.addEventListener('click', function(e) {
178      const href = this.getAttribute('href');
179
180      if (href === '#') return;
181
182      const targetElement = document.querySelector(href);
183
184      if (targetElement) {
185        e.preventDefault();
186
187        const offset = 100;
188        const elementPosition = targetElement.getBoundingClientRect().top;
189        const offsetPosition = elementPosition + window.pageYOffset - offset;
190
191        window.scrollTo({
192          top: offsetPosition,
193          behavior: 'smooth'
194        });
195      }
196    });
197  });
198});
199
200
201document.addEventListener('DOMContentLoaded', function() {
202  var modalButtons = document.querySelectorAll('.open-modal-dialog'),
203      overlay = document.querySelector('body'),
204      closeButtons = document.querySelectorAll('.modal-dialog .modal-close');
205
206  var currentOpenModal = null;
207
208  async function openModal(modalBtn) {
209    return new Promise(resolve => {
210      var modalId = modalBtn.getAttribute('data-src'),
211          modalElem = document.querySelector('.modal-dialog.' + modalId);
212
213      if (currentOpenModal && currentOpenModal !== modalElem) {
214        closeModalDirectly(currentOpenModal);
215      }
216
217      overlay.classList.add('modal-open');
218      modalElem.style.display = 'flex';
219
220      setTimeout(function() {
221        modalElem.classList.add('modal-opening');
222        currentOpenModal = modalElem;
223        resolve();
224      }, 0);
225    });
226  }
227
228  async function closeModal(closeBtn) {
229    return new Promise(resolve => {
230      var modal = closeBtn.closest('.modal-dialog');
231      modal.classList.remove('modal-opening');
232      modal.classList.add('modal-closing');
233
234      setTimeout(function() {
235        modal.classList.remove('modal-closing');
236        modal.style.display = 'none';
237        overlay.classList.remove('modal-open');
238        if (currentOpenModal === modal) {
239          currentOpenModal = null;
240        }
241        resolve();
242      }, 500);
243    });
244  }
245
246  function closeModalDirectly(modalElem) {
247    modalElem.classList.remove('modal-opening');
248    modalElem.style.display = 'none';
249
250    if (currentOpenModal === modalElem) {
251      currentOpenModal = null;
252    }
253
254    var anyModalOpen = document.querySelector('.modal-dialog[style*="display: flex"]');
255    if (!anyModalOpen) {
256      overlay.classList.remove('modal-open');
257    }
258  }
259
260  /* open modal */
261  modalButtons.forEach(function(modalBtn) {
262    modalBtn.addEventListener('click', async function(e) {
263      e.preventDefault();
264      await openModal(modalBtn);
265    });
266  });
267
268  /* close modal */
269  closeButtons.forEach(function(closeBtn) {
270    closeBtn.addEventListener('click', async function(e) {
271      await closeModal(closeBtn);
272    });
273  });
274
275  document.querySelectorAll('.modal-dialog').forEach(function(modal) {
276    modal.addEventListener('click', async function(e) {
277      const modalBody = modal.querySelector('.modal-body');
278      if (modalBody && !modalBody.contains(e.target)) {
279        await closeModal(modal);
280      }
281    });
282  });
283
284});
285
286document.addEventListener('DOMContentLoaded', function() {
287const sliders = document.querySelectorAll('.products-slider');
288
289sliders.forEach((slider) => {
290  const swiper = new Swiper(slider, {
291    slidesPerView: 1,
292    spaceBetween: 20,
293    navigation: {
294      nextEl: slider.querySelector('.swiper-button-next'),
295      prevEl: slider.querySelector('.swiper-button-prev'),
296    },
297    pagination: {
298      el: slider.querySelector('.swiper-progressbar'),
299      type: 'progressbar',
300    },
301    breakpoints: {
302      0: {
303        slidesPerView: 'auto', // Показываем все слайды
304        spaceBetween: 0,
305        allowTouchMove: false,
306      },
307      600: {
308        slidesPerView: 2,
309        allowTouchMove: true,
310      },
311      1024: {
312        slidesPerView: 2,
313        allowTouchMove: false,
314      },
315      1100: {
316        slidesPerView: 2.9,
317      },
318      1440: { slidesPerView: 3.8 },
319      1600: { slidesPerView: 4.8 }
320    }
321  });
322
323  bindPopularTail(slider, swiper);
324});
325
326  const tabButtons = document.querySelectorAll('.tab-btn');
327
328
329function switchTab(tabId, sectionElement) {
330  // Находим все слайды внутри текущего блока slider-section
331  const slides = sectionElement.querySelectorAll('.swiper-slide.card-wrapper');
332  
333  // Скрываем все слайды
334  slides.forEach(slide => {
335    slide.style.display = 'none';
336  });
337  
338  // Показываем нужные слайды
339  const slidesToShow = [];
340  
341  slides.forEach(slide => {
342    const sectionId = slide.getAttribute('data-section-id');
343    
344    // Если категория "Все" (tabId === '1'), показываем все слайды
345    if (tabId === '1') {
346      slidesToShow.push(slide);
347    } 
348    // Проверяем, содержит ли data-section-id выбранный tabId
349    else if (sectionId) {
350      // Разбиваем строку data-section-id на массив категорий
351      const categories = sectionId.split(',').map(id => id.trim());
352      
353      // Если tabId есть в списке категорий слайда, показываем его
354      if (categories.includes(tabId)) {
355        slidesToShow.push(slide);
356      }
357    }
358  });
359  
360  // Показываем отфильтрованные слайды
361  slidesToShow.forEach(slide => {
362    slide.style.display = 'block';
363  });
364  
365  // Обновляем Swiper после изменения видимости слайдов
366  const swiperContainer = sectionElement.querySelector('.products-slider.swiper-initialized');
367  if (swiperContainer && swiperContainer.swiper) {
368    swiperContainer.swiper.update();
369  }
370}
371
372// Хвост блока «Популярные товары».
373//
374// С сервера приходит только первая порция карточек, остальные — одним
375// запросом, когда посетитель показал к блоку интерес: полистал слайдер или
376// переключил вкладку. Полный список из шести десятков карточек занимал
377// 447 КБ из 758 в разметке главной и две трети всех её тегов, при том что
378// слайдер показывает разом от двух до пяти.
379//
380// Позицию, с которой продолжать, кладёт в data-атрибут включаемая область
381// (include_areas/main_page_v2/popular-block.php). Нет атрибута — значит
382// список уместился целиком и догружать нечего.
383function bindPopularTail(slider, swiper) {
384  const from = parseInt(slider.getAttribute('data-popular-next') || '0', 10);
385  if (!from) {
386    return;
387  }
388
389  let request = null;
390
391  slider.mkLoadTail = function () {
392    if (request) {
393      return request;
394    }
395
396    // Атрибут снимаем сразу: второй раз за тем же хвостом ходить незачем,
397    // даже если запрос не удастся.
398    slider.removeAttribute('data-popular-next');
399
400    request = fetch('/local/ajax_new/main_popular_tail.php?from=' + from, { credentials: 'same-origin' })
401      .then(function (response) { return response.ok ? response.text() : ''; })
402      .then(function (html) {
403        if (!html.trim()) {
404          return;
405        }
406
407        slider.querySelector('.swiper-wrapper').insertAdjacentHTML('beforeend', html);
408
409        if (window.mkInitProductGalleries) {
410          window.mkInitProductGalleries(slider);
411        }
412        if (swiper && swiper.update) {
413          swiper.update();
414        }
415
416        // Пришедшие карточки про выбранную вкладку не знают — прогоняем отбор
417        // ещё раз, иначе в отфильтрованном списке окажутся чужие.
418        const section = slider.closest('.slider-section');
419        const activeTab = section && section.querySelector('.tab-btn.active');
420        if (activeTab) {
421          switchTab(activeTab.getAttribute('data-tab-id'), section);
422        }
423      })
424      .catch(function () {
425        // Не доехало — блок остаётся рабочим на той порции, что уже есть.
426      });
427
428    return request;
429  };
430
431  if (swiper && swiper.on) {
432    // Пары перелистываний хватает, чтобы понять намерение, и остаётся запас
433    // времени: до конца первой порции ещё несколько экранов.
434    swiper.on('slideChange', function () {
435      if (swiper.activeIndex >= 2) {
436        slider.mkLoadTail();
437      }
438    });
439    swiper.on('reachEnd', function () {
440      slider.mkLoadTail();
441    });
442  }
443}
444
445// Инициализация переключателей вкладок
446document.querySelectorAll('.slider-section').forEach(section => {
447  const tabButtons = section.querySelectorAll('.tab-btn');
448  
449  tabButtons.forEach(button => {
450    button.addEventListener('click', function(e) {
451      e.preventDefault();
452      
453      // Убираем класс active у всех кнопок в этом блоке
454      tabButtons.forEach(btn => btn.classList.remove('active'));
455      
456      // Добавляем класс active текущей кнопке
457      this.classList.add('active');
458      
459      // Получаем выбранный tabId
460      const tabId = this.getAttribute('data-tab-id');
461
462      // Переключаем слайды
463      switchTab(tabId, section);
464
465      // Отбор идёт по уже загруженным карточкам, а сразу приходит только
466      // первая порция списка. Просим остаток — придёт, и отбор повторится.
467      const slider = section.querySelector('.products-slider');
468      if (slider && slider.mkLoadTail) {
469        slider.mkLoadTail();
470      }
471    });
472  });
473  
474});
475
476
477
478});
479
480document.addEventListener('DOMContentLoaded', () => {
481  
482  // Единый обработчик для всех кликов
483  document.body.addEventListener('click', (e) => {
484    
485    // 1. Кнопка добавления в корзину
486    const addToCartBtn = e.target.closest('.add-to-cart');
487    if (addToCartBtn) {
488      const cart = addToCartBtn.closest('.product-cart-actions');
489      if (cart) {
490        const btn = cart.querySelector('.add-to-cart');
491        const control = cart.querySelector('.product-cart-control');
492        const input = control?.querySelector('input[type="number"]');
493        var incart = false;
494        if (window.location.pathname.includes('/personal/cart/')) {
495          incart = true;
496          btn.textContent = 'В корзине';
497          btn.classList.add('active');
498        }
499
500        if (btn && control && !incart) {
501          btn.style.display = 'none';
502          control.style.display = 'flex';
503          if (input) input.value = 1;
504        }
505      }
506      return;
507    }
508    
509    // 2. Кнопка minus
510    const minusBtn = e.target.closest('.product-minus');
511    if (minusBtn) {
512      const control = minusBtn.closest('.product-cart-control');
513      const cart = control?.closest('.product-cart-actions');
514      if (cart && control) {
515        const btn = cart.querySelector('.add-to-cart');
516        const input = control.querySelector('input[type="number"]');
517        if (input) {
518          let val = parseInt(input.value, 10);
519          if (!isNaN(val) && val > 0) val--;
520          input.value = val;
521          if (val === 0 && btn) {
522            btn.style.display = 'inline-flex';
523            control.style.display = 'none';
524          }
525        }
526      }
527      return;
528    }
529    
530    // 3. Кнопка plus
531    const plusBtn = e.target.closest('.product-plus');
532    if (plusBtn) {
533      const control = plusBtn.closest('.product-cart-control');
534      const cart = control?.closest('.product-cart-actions');
535      if (cart && control) {
536        const btn = cart.querySelector('.add-to-cart');
537        const input = control.querySelector('input[type="number"]');
538        if (input) {
539          let val = parseInt(input.value, 10);
540          if (isNaN(val)) val = 0;
541          input.value = val + 1;
542          if (btn) {
543            btn.style.display = 'none';
544            control.style.display = 'flex';
545          }
546        }
547      }
548      return;
549    }
550    
551    // 4. Кнопка копирования
552    const copyBtn = e.target.closest('.copy-btn');
553    if (copyBtn) {
554      const productCard = copyBtn.closest('.product-card') || copyBtn.closest('.product-main-block');
555      if (productCard) {
556        const articleSpan = productCard.querySelector('.product-article span span');
557        if (articleSpan) {
558          const articleText = articleSpan.textContent.trim();
559          navigator.clipboard.writeText(articleText).then(() => {
560            copyBtn.classList.add('active');
561            setTimeout(() => {
562              copyBtn.classList.remove('active');
563            }, 1500);
564          });
565        }
566      }
567      return;
568    }
569    
570// 5. Кнопка избранного
571const favoriteBtn = e.target.closest('.add-favorite');
572if (favoriteBtn) {
573  e.preventDefault();
574  favoriteBtn.classList.toggle('active');
575  
576  // Находим родительский блок и item-fav-link
577  const parentBlock = favoriteBtn.closest('.flex');
578  const itemFavLink = parentBlock.closest('.product-actions')?.querySelector('.item-fav-link');
579  
580  if (itemFavLink) {
581    if (favoriteBtn.classList.contains('active')) {
582      itemFavLink.classList.add('active');
583    } else {
584      itemFavLink.classList.remove('active');
585    }
586  }
587  
588  return;
589}
590
591const toFavoriteLink = e.target.closest('.info-box-card .to-favorite');
592if (toFavoriteLink) {
593  e.preventDefault();
594  
595  // Находим родительский блок .flex, в котором находится нажатая ссылка
596  const parentBlock = toFavoriteLink.closest('.flex');
597  
598  // Внутри этого же блока находим кнопку add-favorite
599  const favoriteBtn = parentBlock.querySelector('.add-favorite');
600  
601  // Переключаем класс active только у этой найденной кнопки
602  if (favoriteBtn) {
603    favoriteBtn.classList.toggle('active');
604    
605    // Находим item-fav-link и синхронизируем его класс
606    const itemFavLink = parentBlock.closest('.product-actions')?.querySelector('.item-fav-link');
607    if (itemFavLink) {
608      if (favoriteBtn.classList.contains('active')) {
609        itemFavLink.classList.add('active');
610      } else {
611        itemFavLink.classList.remove('active');
612      }
613    }
614  }
615  
616  return;
617}
618
619// 6. Кнопка сравнения
620const compareBtn = e.target.closest('.add-compare');
621if (compareBtn) {
622  e.preventDefault();
623  compareBtn.classList.toggle('active');
624  
625  // Находим item-compare-link и синхронизируем его класс
626  const parentBlock = compareBtn.closest('.flex');
627  const itemCompareLink = parentBlock.closest('.product-actions')?.querySelector('.item-compare-link');
628  
629  if (itemCompareLink) {
630    if (compareBtn.classList.contains('active')) {
631      itemCompareLink.classList.add('active');
632    } else {
633      itemCompareLink.classList.remove('active');
634    }
635  }
636  
637  return;
638}
639
640const toCompareLink = e.target.closest('.info-box-card .to-compare');
641if (toCompareLink) {
642  e.preventDefault();
643  
644  const parentBlock = toCompareLink.closest('.flex');
645  const compareBtnInBlock = parentBlock.querySelector('.add-compare');
646  
647  if (compareBtnInBlock) {
648    compareBtnInBlock.classList.toggle('active');
649    
650    // Находим item-compare-link и синхронизируем его класс
651    const itemCompareLink = parentBlock.closest('.product-actions')?.querySelector('.item-compare-link');
652    if (itemCompareLink) {
653      if (compareBtnInBlock.classList.contains('active')) {
654        itemCompareLink.classList.add('active');
655      } else {
656        itemCompareLink.classList.remove('active');
657      }
658    }
659  }
660  
661  return;
662}
663
664  });
665  
666  // Отдельный обработчик для input событий
667  document.body.addEventListener('input', (e) => {
668    const input = e.target.closest('.product-cart-control input[type="number"]');
669    if (input) {
670      const control = input.closest('.product-cart-control');
671      const cart = control?.closest('.product-cart-actions');
672      if (cart && control) {
673        const btn = cart.querySelector('.add-to-cart');
674        let val = parseInt(input.value, 10);
675        if (isNaN(val)) val = 0;
676        if (val < 0) val = 0;
677        input.value = val;
678        if (val === 0 && btn) {
679          btn.style.display = 'inline-flex';
680          control.style.display = 'none';
681        }
682      }
683    }
684  });
685  
686});
687
688
689document.addEventListener('DOMContentLoaded', function() {
690  const widget = document.querySelector('.fixed-widgets');
691  const btn = document.querySelector('.btn-up-page');
692  if (!widget || !btn) return;
693
694  window.addEventListener('scroll', function() {
695    widget.classList.toggle('visible', window.scrollY > 300);
696  });
697
698  btn.addEventListener('click', function(e) {
699    e.preventDefault();
700    window.scrollTo({ top: 0, behavior: 'smooth' });
701  });
702});
703
704document.addEventListener('DOMContentLoaded', function() {
705  const cityPickers = document.querySelectorAll('.city-picker');
706
707  cityPickers.forEach(picker => {
708    const currentCity = picker.querySelector('.current-city');
709    const cityItems = picker.querySelector('.city-items');
710    const options = picker.querySelectorAll('.item-option');
711
712    if (!currentCity || !cityItems) return;
713
714    currentCity.addEventListener('click', function(e) {
715      e.stopPropagation();
716      cityItems.classList.toggle('active');
717    });
718
719    options.forEach(option => {
720      option.addEventListener('click', function(e) {
721        e.stopPropagation();
722        const selectedCity = this.textContent;
723        currentCity.textContent = selectedCity;
724        cityItems.classList.remove('active');
725      });
726    });
727  });
728
729  document.addEventListener('click', function() {
730    document.querySelectorAll('.city-items.active').forEach(items => {
731      items.classList.remove('active');
732    });
733  });
734});
735
736document.addEventListener('DOMContentLoaded', function() {
737  const mobileMenu = document.querySelector('.mobile-menu-bottom');
738
739  if (!mobileMenu) return;
740
741  let lastScrollTop = 0;
742  let isMenuVisible = false;
743
744  function showMenu() {
745    if (isMenuVisible) return;
746    mobileMenu.classList.add('show');
747    isMenuVisible = true;
748  }
749
750  window.addEventListener('scroll', function() {
751    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
752
753    if (scrollTop > lastScrollTop && scrollTop > 100) {
754      showMenu();
755    }
756
757    lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
758  });
759});
760var swiper1 = new Swiper(".locations-slider", {
761  // Слайды здесь — ссылки, роль "group" для них недопустима.
762  a11y: { slideRole: "" },
763  observer: true,
764  observeParents: true,
765  observeSlideChildren: true,
766  watchSlidesProgress: true,
767  pagination: {
768    el: ".locations-slider .swiper-progressbar",
769    type: "progressbar",
770  },
771  breakpoints: {
772    0: {
773      slidesPerView: 1.3,
774      spaceBetween: 10,
775      grid: {
776        rows: 2,        // 2 ряда
777        fill: "row"     // заполнение по строкам
778      }
779    },
780    360: {
781      slidesPerView: 1.3,
782      spaceBetween: 10,
783      grid: {
784        rows: 2,
785        fill: "row"
786      }
787    },
788    601: {
789      slidesPerView: 2.5,
790      spaceBetween: 23,
791      grid: {
792        rows: 1        // на больших экранах 1 ряд
793      }
794    },
795    1024: {
796      slidesPerView: 3.5,
797      spaceBetween: 23,
798      grid: { rows: 1 }
799    },
800    1200: {
801      slidesPerView: 6,
802      spaceBetween: 23,
803      grid: { rows: 1 }
804    },
805    1900: {
806      slidesPerView: 6,
807      spaceBetween: 23,
808      grid: { rows: 1 }
809    }
810  }
811});
812
813
814// Галереи плиток каталога. Инициализация висела на DOMContentLoaded, поэтому
815// карточки, пришедшие ajax-ом (смена фильтра, сортировка, следующая страница),
816// оставались без точек и перелистывания. Теперь это функция: её же зовёт
817// obSection.bind() после каждой перерисовки листинга.
818window.mkInitProductGalleries = function (root) {
819  const scope = root || document;
820  const productCards = scope.querySelectorAll('.product-card');
821
822  productCards.forEach((card) => {
823    const galleryContainer = card.querySelector('.product-gallery-slider');
824    if (!galleryContainer) return;
825
826    // Второй раз одну и ту же карточку не трогаем: обработчики бы удвоились.
827    if (galleryContainer.dataset.galleryReady === '1') return;
828    galleryContainer.dataset.galleryReady = '1';
829
830    const slides = galleryContainer.querySelectorAll('.swiper-slide');
831    if (slides.length === 0) return;
832
833    const pagination = galleryContainer.querySelector('.swiper-pagination');
834    let currentIndex = 0;
835
836    if (pagination && slides.length > 0) {
837      pagination.innerHTML = '';
838      slides.forEach((_, i) => {
839        const bullet = document.createElement('span');
840        bullet.classList.add('swiper-pagination-bullet');
841        if (i === 0) bullet.classList.add('swiper-pagination-bullet-active');
842        bullet.addEventListener('click', (e) => {
843          e.stopPropagation();
844          currentIndex = i;
845          showSlide(currentIndex);
846          updatePagination();
847        });
848        pagination.appendChild(bullet);
849      });
850    }
851
852    const updatePagination = () => {
853      if (!pagination) return;
854      const bullets = pagination.querySelectorAll('.swiper-pagination-bullet');
855      bullets.forEach((bullet, i) => {
856        if (i === currentIndex) {
857          bullet.classList.add('swiper-pagination-bullet-active');
858        } else {
859          bullet.classList.remove('swiper-pagination-bullet-active');
860        }
861      });
862    };
863
864    const showSlide = (index) => {
865      slides.forEach((slide, i) => {
866        if (i === index) {
867          slide.style.display = 'flex';
868          slide.style.opacity = '1';
869        } else {
870          slide.style.display = 'none';
871          slide.style.opacity = '0';
872        }
873      });
874      updatePagination();
875    };
876
877    showSlide(0);
878
879    const handleMouseMove = (e) => {
880      const rect = galleryContainer.getBoundingClientRect();
881      const mouseX = e.clientX - rect.left;
882      const width = rect.width;
883
884      let position = mouseX / width;
885      position = Math.min(Math.max(position, 0), 0.999);
886
887      const targetIndex = Math.floor(position * slides.length);
888
889      if (targetIndex !== currentIndex) {
890        currentIndex = targetIndex;
891        showSlide(currentIndex);
892      }
893    };
894
895    const handleMouseLeave = () => {
896      currentIndex = 0;
897      showSlide(0);
898    };
899
900    galleryContainer.addEventListener('mousemove', handleMouseMove);
901    galleryContainer.addEventListener('mouseleave', handleMouseLeave);
902  });
903};
904
905document.addEventListener('DOMContentLoaded', function () {
906  window.mkInitProductGalleries();
907});
908
909var swiper3 = new Swiper(".testimonials-slider", {
910  // Слайды здесь — ссылки, роль "group" для них недопустима.
911  a11y: { slideRole: "" },
912  observer: true,
913  observeParents: true,
914  observeSlideChildren: true,
915  slidesPerView: 2,
916  spaceBetween: 30,
917  watchSlidesProgress: true,
918  navigation: {
919    nextEl: ".testimonials-slider .swiper-button-next",
920    prevEl: ".testimonials-slider .swiper-button-prev",
921  },
922  pagination: {
923    el: ".testimonials-slider .swiper-progressbar",
924    type: 'progressbar',
925  },
926  breakpoints: {
927    0: {
928      slidesPerView: 1.3,
929      spaceBetween: 10,
930    },
931    601: {
932      slidesPerView: 1.3,
933      spaceBetween: 24,
934    },
935    1024: {
936      slidesPerView: 1.7,
937      spaceBetween: 24,
938    },
939    1200: {
940      slidesPerView: 2.1,
941      spaceBetween: 24,
942    },
943    1600: {
944      slidesPerView: 2.5,
945      spaceBetween: 24,
946    },
947  }
948});
949
950var swiper4 = new Swiper(".news-slider", {
951  // Слайды здесь — ссылки, роль "group" для них недопустима.
952  a11y: { slideRole: "" },
953  observer: true,
954  observeParents: true,
955  observeSlideChildren: true,
956  slidesPerView: 2,
957  spaceBetween: 30,
958  watchSlidesProgress: true,
959  navigation: {
960    nextEl: ".news-slider .swiper-button-next",
961    prevEl: ".news-slider .swiper-button-prev",
962  },
963  pagination: {
964    el: ".news-slider .swiper-progressbar",
965    type: 'progressbar',
966  },
967  breakpoints: {
968    300: {
969      slidesPerView: 1.3,
970      spaceBetween: 10,
971    },
972    601: {
973      slidesPerView: 1.5,
974      spaceBetween: 20,
975    },
976    1024: {
977      slidesPerView: 3,
978      spaceBetween: 20,
979    },
980    1100: {
981      slidesPerView: 2,
982      spaceBetween: 20,
983    },
984    1440: {
985      slidesPerView: 3,
986      spaceBetween: 20,
987    }
988  }
989});
990
991document.addEventListener('DOMContentLoaded', function() {
992    // Находим все li.parrent, у которых есть предыдущий соседний элемент li
993    const items = document.querySelectorAll('.main-menu li + li.parrent');
994    
995    items.forEach(function(item) {
996        item.addEventListener('click', function(e) {
997            this.classList.toggle('active');
998            
999            e.stopPropagation();
1000        });
1001    });
1002});
1003// Счётчик мини-корзины после покупки в быстром просмотре.
1004//
1005// Быстрый просмотр — это iframe: корзину меняют внутри него, и число в шапке
1006// родительского окна само не обновится. Фрейм сообщает об изменении через
1007// postMessage (см. components/art-fresh/catalog.element.claude/sside_quick_view.php),
1008// а количество забираем тем же эндпоинтом, каким его берёт остальной сайт —
1009// он отдаёт скрипт виджета и следом само число.
1010//
1011// Обработчик лежит здесь, а не в скрипте плитки: бандл подключается на всех
1012// страницах и в обоих шаблонах, а у плитки в 2026v2 работает форк mk-v2, куда
1013// правка не попадала — там счётчик так и оставался со старым значением.
1014(function () {
1015    function updateBasketAccessibleName(node, value) {
1016        var link = node.closest('.cart-link, .link-cart a');
1017        if (!link) return;
1018
1019        var countText = String(value == null ? node.textContent : value);
1020        var countMatches = countText.match(/\d+/g);
1021        var count = countMatches ? countMatches[countMatches.length - 1] : '';
1022        link.setAttribute('aria-label', count ? 'Корзина, товаров: ' + count : 'Корзина');
1023    }
1024
1025    function setBasketCount(value) {
1026        var nodes = document.querySelectorAll('.icon-link.cart-link span, .link-cart span');
1027        Array.prototype.forEach.call(nodes, function (node) {
1028            node.textContent = value;
1029            updateBasketAccessibleName(node, value);
1030        });
1031    }
1032
1033    function watchBasketCounters() {
1034        var nodes = document.querySelectorAll('.icon-link.cart-link span, .link-cart span');
1035        Array.prototype.forEach.call(nodes, function (node) {
1036            updateBasketAccessibleName(node);
1037            if (typeof MutationObserver === 'undefined') return;
1038
1039            new MutationObserver(function () {
1040                updateBasketAccessibleName(node);
1041            }).observe(node, {childList: true, characterData: true, subtree: true});
1042        });
1043    }
1044
1045    function labelProductControls() {
1046        var copyButtons = document.querySelectorAll('.product-main-block .copy-btn');
1047        Array.prototype.forEach.call(copyButtons, function (button) {
1048            button.type = 'button';
1049            button.setAttribute('aria-label', 'Скопировать код товара');
1050            button.setAttribute('title', 'Скопировать код товара');
1051        });
1052
1053        var reserveInputs = document.querySelectorAll('.sidebar-sh
1053op-delivery .input-box input');
1054        Array.prototype.forEach.call(reserveInputs, function (input) {
1055            input.setAttribute('aria-label', 'Количество товаров для резервирования');
1056            input.setAttribute('inputmode', 'numeric');
1057        });
1058
1059        var catalogQuantityInputs = document.querySelectorAll('.product-card input[data-quantity]');
1060        Array.prototype.forEach.call(catalogQuantityInputs, function (input) {
1061            input.setAttribute('aria-label', 'Количество товара');
1062            input.setAttribute('inputmode', 'numeric');
1063        });
1064
1065        var favoriteButtons = document.querySelectorAll('.product-main-block .add-favorite');
1066        Array.prototype.forEach.call(favoriteButtons, function (button) {
1067            button.type = 'button';
1068            button.setAttribute('aria-label', 'Добавить товар в избранное');
1069        });
1070
1071        var compareButtons = document.querySelectorAll('.product-main-block .add-compare');
1072        Array.prototype.forEach.call(compareButtons, function (button) {
1073            button.type = 'button';
1074            button.setAttribute('aria-label', 'Добавить товар к сравнению');
1075        });
1076
1077        var favoriteLinks = document.querySelectorAll('.product-main-block .item-fav-link, .product-main-block .to-favorite, .product-card .item-fav-link, .product-card .to-favorite');
1078        Array.prototype.forEach.call(favoriteLinks, function (link) {
1079            link.setAttribute('href', '/personal/wishlist/');
1080        });
1081
1082        var compareLinks = document.querySelectorAll('.product-main-block .item-compare-link, .product-main-block .to-compare, .product-card .item-compare-link, .product-card .to-compare');
1083        Array.prototype.forEach.call(compareLinks, function (link) {
1084            link.setAttribute('href', '/compare/');
1085        });
1086    }
1087
1088    function initializeAccessibility() {
1089        watchBasketCounters();
1090        labelProductControls();
1091
1092        window.setTimeout(labelProductControls, 1000);
1093        window.setTimeout(labelProductControls, 3000);
1094    }
1095
1096    if (document.readyState === 'loading') {
1097        document.addEventListener('DOMContentLoaded', initializeAccessibility, {once: true});
1098    } else {
1099        initializeAccessibility();
1100    }
1101
1102    function refreshBasketCounter() {
1103        fetch('/local/components/art-fresh/sale.basket/ajax_new.php', {
1104            method: 'POST',
1105            credentials: 'same-origin',
1106            headers: {'Content-Type': 'application/x-www-form-urlencoded'},
1107            body: 'action=count'
1108        }).then(function (response) {
1109            return response.text();
1110        }).then(function (text) {
1111            var match = String(text).replace(/<script[\s\S]*?<\/script>/gi, '').match(/(\d+)\s*$/);
1112            if (match) {
1113                setBasketCount(match[1]);
1114            }
1115        }).catch(function () {
1116        });
1117    }
1118
1119    window.addEventListener('message', function (event) {
1120        if (event.origin !== window.location.origin) return;
1121        if (!event.data || event.data.mkQuickView !== 'basket-changed') return;
1122        refreshBasketCounter();
1123    });
1124})();

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.