PageSourceSearch

https://xcmg-ru.ru/local/templates/xcmg2024/js/app-project-run.js?236226683

js xcmg-ru.ru collected 2026-10-02 19:40:09 UTC 117,293 bytes, 2,742 lines download raw bytes

1function fix100vh() {
2    let vh = window.innerHeight * 0.01;
3    document.documentElement.style.setProperty('--vh', `${vh}px`);
4}
5
6function get_cookie(name){
7  return document.cookie.split(';').some(c => {
8    return c.trim().startsWith(name + '=');
9  });
10}
11
12function delete_cookie(name) {
13  if (get_cookie(name)) {
14    document.cookie = name + "=true;expires=Thu, 01 Jan 1970 00:00:01 GMT";
15  }
16}
17/*
18$(document).ready(function(){
19	$('#form_filter').submit(function(evt) {
20		evt.preventDefault();
21		let arr = [];
22		arr = $('#form_filter').serializeArray();
23		console.log(arr);
24		console.log(arr['name']);
25
26		var length = arr.length,
27		element = null;
28		for (var i = 0; i < length; i++) {
29		  element = arr[i];
30		}
31	});
32});*/
33
34let filterSliders = document.querySelectorAll(".filter-slider")
35
36filterSliders.forEach(slider => {
37const rangeSlider = slider.querySelector(`.range-slider`)
38const rangeSlider_ormode = slider.querySelector(`.range-slider.or_mode`)
39const rangeInput0 = slider.querySelector(`.range-slider__input1`)
40const rangeInput1 = slider.querySelector(`.range-slider__input2`)
41const rangeInputs = [rangeInput0, rangeInput1]
42
43let startValue1 = +slider.getAttribute("data-start")
44let startValue2 = +slider.getAttribute("data-start2")
45let min = +slider.getAttribute("data-min")
46let max = +slider.getAttribute("data-max")
47
48if (rangeSlider) {
49  noUiSlider.create(rangeSlider, {
50	start: [startValue1, startValue2],
51	connect: true,
52	step: 1,
53	range: {
54	  'min': min,
55	  'max': max
56	}
57  });
58}
59if (rangeSlider) {
60    rangeSlider.noUiSlider.on('update', function(values, handle) {
61        rangeInputs[handle].value = Math.round(values[handle])
62    })
63    rangeSlider.noUiSlider.on('slide', function(values, handle) {
64      all_inputs = $('.filter-slider__inputs').find('input').not(rangeInputs)
65      all_inputs.each(function () {
66          $(this).val('');
67      })
68    })
69    rangeInputs.forEach((el, index) => {
70        el.addEventListener('change', (e) => {
71            setRangeSlider(index, e.currentTarget.value)
72        })
73    })
74    $('.or_mode').find('.filter-slider__inputs').find('input').each(function(){
75        //console.log($(this))
76        //$(this).val(10000);
77    })
78}
79const setRangeSlider = (i, value) => {
80    let array = [null, null];
81    array[i] = value;
82
83    rangeSlider.noUiSlider.set(array);
84  };
85})
86
87
88document.addEventListener("DOMContentLoaded", function () {
89	fix100vh();
90    window.addEventListener('resize', () => {
91        fix100vh();
92    })
93// Массив данных (пример)
94  console.log(arrayDealers)
95  let destinations = arrayDealers;
96  let arrcities = arrayCities;
97  let arrspecs = [];
98  if(typeof arraySpecs !== "undefined")  { arrspecs = arraySpecs; }
99//console.log(arrspecs);
100
101//delete_cookie('cookie-modal', '')
102//delete_cookie('max-modal', '')
103  $('.cookie-modal__btn').click(function () {
104    $('.cookie-modal').removeClass('_active');
105    document.cookie = "cookie-modal=true; path=/; max-age=2592000";
106    return false;
107  });
108  
109  $('.maxmodal-wrapper__close-wrapper').click(function () {
110    $('.maxmodal-container').removeClass('maxmodal-container_opened');
111    document.cookie = "max-modal=true; path=/; max-age=2592000";
112    return false;
113  });
114  
115
116  $('.filter_zapchasti').change(function() {
117      parentDiv = $(this).closest('.filter__list-row');
118      parentData = parentDiv.find('.filter_zapchasti_data');
119      // Создаём массив со значениями выбранных чекбоксов
120      var allFilterBoxes = $('.filter_zapchasti-row');
121      var arr = [];
122      var filterItems = [];
123      var filterRows = [];
124      var tmpArr = [];
125      var ucode = parentData.data('code');
126      var ufields = parentData.data('fields');
127      var pfields = parentData.data('parentfields');
128
129      allFilterBoxes.each(function () {
130          var code = $(this).find('.filter_zapchasti_data').data('code');
131          arr = $(this).find('.filter_zapchasti:checked').map((_, el) => el.value).get();
132          if(arr.length > 0) {
133              filterRows.push(code);
134              filterItems.push(arr)
135          }
136          tmpArr = [filterRows, filterItems];
137      });
138
139      if($(this).prop('checked')) { //Если галочка была нажата (другие события возникают, когда она снимается
140          var pushedCheck = $(this).val();
141          var objs = {
142              fields: ufields,
143              parentfields: pfields,
144              code: ucode,
145              value: pushedCheck};
146          tmpArr.push(objs);
147
148          $.ajax({
149            url: "/local/ajax/zapchasti_filter.php",
150            method: "POST",
151            data: JSON.stringify(tmpArr),
152            success: function (result) {
153                if (result.STATUS == "ERROR") {
154                    alert('Ошибка! Попробуйте снова');
155                } else if(result.length > 0) {
156                    var obj = jQuery.parseJSON(result);
157
158                    var items = obj['items'];
159                    console.log(items)
160                    $.each(items, function(key,value) {
161                        var field_name = key;
162                        var subarr = value;
163                        var parent_box = $('.set_'+field_name);
164                        var field_checkboxes = parent_box.find('input[type=checkbox]');
165                        field_checkboxes.prop('disabled', true);
166                        field_checkboxes.closest('.filter__list-item-checkbox').addClass('filter_checkbox_disabled');
167                        //console.log(subarr);
168                        $.each(subarr, function(k,val) {
169                            var valid_checkbox = parent_box.find('input[type=checkbox][value="'+val+'"]');
170                            valid_checkbox.prop('disabled', false);
171                            valid_checkbox.closest('.filter__list-item-checkbox').removeClass('filter_checkbox_disabled');
172                        })
173                    })
174                }
175            }
176          })
177      } else { //Галочка снялась, надо для этого элемента отключить все дочерние другим скриптом
178          var removedCheck = $(this).val();
179          objs = {
180              fields: ufields,
181              parentfields: pfields,
182              code: ucode,
183              value: removedCheck,
184              status: 'unchecked',
185          };
186          arr=[objs, tmpArr];
187          $.ajax({
188              url: "/local/ajax/zapchasti_filter.php",
189              method: "POST",
190              data: JSON.stringify(arr),
191              success: function (result) {
192                  if (result.STATUS == "ERROR") {
193                      alert('Ошибка! Попробуйте снова');
194                  } else if (result.length > 0) {
195                      var items = jQuery.parseJSON(result);
196                      var field = parentData.data('fields').split('|');//Берем только первую зависимую группу чекбоксов. Остальные рекурсивно подействуют при имитации отжатия чекбокса
197                      if(field.length > 1) field = field[0];
198                      console.log(items);
199                      $.each(items, function(key,value) {
200                          var checkbox = $('.set_'+field).find('input[type=checkbox][value="'+value+'"]');
201                          checkbox.closest('.filter__list-item-checkbox').addClass('filter_checkbox_disabled');
202                          checkbox.prop('disabled', true);
203                          checkbox.prop('checked', false);
204                      })
205                  }
206              }
207          })
208      }
209  })
210
211
212    $('.zapchasti_filtered').each(function () {
213        $(this).closest('.filter__list-item-checkbox').removeClass('filter_checkbox_disabled');
214        $(this).prop('disabled', false);
215        $(this).trigger('click');
216    });
217
218  headerInit();
219  navInit();
220  coverInit();
221  filterInit();
222  newsInit();
223  mapInit();
224  presentationInit();
225  videoInit();
226  productInit();
227  carouselInit();
228  accordionInit();
229  historyInit();
230  textInit();
231  dealerNetworkInit();
232  footerInit();
233  specsDealersInit()
234});
235
236function accordionInit() {
237  const accordion = document.querySelectorAll(".accordion");
238
239  if (accordion) {
240    const accordionItems = document.querySelectorAll(".accordion__list-item");
241
242    /*
243     * При клике на элемент аккордеона происходит переключение класса "accordion__list-item--active" у этого элемента.
244     * Таким образом, при клике на элемент аккордеона, он будет открываться или закрываться
245     */
246    accordionItems.forEach((item) => {
247      item.addEventListener("click", () => {
248        item.classList.toggle("accordion__list-item--active");
249      });
250    });
251  }
252}
253
254
255function dealerNetworkInit() {
256  const dealerNetwork = document.querySelector(".dealer-network");
257
258  if (dealerNetwork) {
259
260
261    // Обработка адресов
262    listOfDealers.forEach((dealer) => {
263      let address = dealer.address.trim();
264      let lastTab = address.lastIndexOf("\t"); // Найти последнее сочетание символов "\t"
265      let spaceIndex = address.indexOf(" ", lastTab); // Найти первый пробел после последнего "\t"
266      address = address
267        .substring(spaceIndex)
268        .replace(/[\s\n\t]+/g, " ")
269        .trim();
270
271      // Замена "city" на "город" и "Сity" на "Город"
272      address = address.replace(/city/g, "город").replace(/Сity/g, "Город");
273
274      dealer.address = address;
275    });
276
277    // Обработка контактов
278    const phoneRegex = /^(\s*)?(\+)?([- _():=+]?\d[- _():=+]?){10,14}(\s*)?$/;
279
280    listOfDealers.forEach((dealer) => {
281      let contacts = dealer.contacts.trim();
282      let phones = [];
283      let emails = [];
284
285      // Разделение на номера телефонов и email
286      contacts.split("\n").forEach((contact) => {
287        if (phoneRegex.test(contact)) {
288          let phone = contact
289            .trim()
290            .replace(/^8/, "+7")
291            .replace(
292              /([\d]{1})([\d]{3})([\d]{3})([\d]{2})([\d]{2})/,
293              "+7 ($2) $3-$4-$5"
294            );
295          phones.push(phone);
296        } else {
297          emails.push(contact.trim());
298        }
299      });
300
301      // Создаем новые поля для номеров телефонов
302      phones.forEach((phone, index) => {
303        dealer[`phone${index + 1}`] = phone;
304      });
305
306      // Создаем новые поля для email адресов
307      emails.forEach((email, index) => {
308        dealer[`email${index + 1}`] = email;
309      });
310
311      // Удаление поля "contacts"
312      delete dealer.contacts;
313    });
314
315    // Получение координат
316    listOfDealers.forEach((dealer) => {
317      let address = dealer.address.trim();
318      let city = dealer.city.trim();
319      let lastTab = address.lastIndexOf("\t");
320      let spaceIndex = address.indexOf(" ", lastTab);
321      let lowerCorner;
322      // let coords = [];
323      address = address
324        .substring(spaceIndex)
325        .replace(/[\s\n\t]+/g, " ")
326        .trim();
327      dealer.address = address;
328
329
330      // ymaps.geocode(address).then((res) => {
331      //   let firstGeoObject = res.geoObjects.get(0);
332      //   let coords = firstGeoObject.geometry.getCoordinates();
333      //   dealer.coords = coords;
334      // });
335      // const url =
336      // "https://geocode-maps.yandex.ru/1.x/?apikey=4ba8f8f6-4a7e-4835-b801-917a745db72f&geocode=бульвар+Мухаммед+Бин+Рашид+1&format=json";
337      const url =
338        "https://geocode-maps.yandex.ru/1.x/?apikey=4ba8f8f6-4a7e-4835-b801-917a745db72f&geocode=" +
339        city +
340        ", " +
341        address +
342        "+1&format=json";
343      // alert(city + address);
344      fetch(url)
345        .then((response) => {
346          if (!response.ok) {
347            throw new Error("Network response was not ok");
348          }
349          return response.json();
350        })
351        .then((data) => {
352          // Доступ к значению lowerCorner
353          lowerCorner =
354            data.response.GeoObjectCollection.featureMember[0].GeoObject
355              .boundedBy.Envelope.lowerCorner;
356
357          // Создаем новые поля для email адресов
358          // coords.forEach((coord, index) => {
359          //   dealer[`coord${index + 1}`] = lowerCorner;
360          // });
361          // console.log(lowerCorner);
362        })
363        .catch((error) => {
364          console.error(
365            "There has been a problem with your fetch operation:",
366            error,
367            address
368          );
369        });
370      // dealer.coords = lowerCorner;
371      // dealer.push({coords.lowerCorner});
372      dealer.push({ coords: lowerCorner });
373    });
374
375  }
376}
377
378function carouselInit() {
379  const carousel = document.querySelector(".carousel");
380
381  if (carousel) {
382    let dotsStatus = $(".carousel__slider .carousel__item").length > 1;
383
384    /*
385     * Инициализируем карусель, используя библиотеку Slick Carousel, задавая опции для отображения слайдов,
386     * скорости пролистывания, наличия стрелок для навигации, наличия точек для навигации,
387     * автоматического проигрывания и других параметров.
388     * https://kenwheeler.github.io/slick/
389     */
390    $(".carousel__slider--simple").slick({
391      slidesToShow: 1,
392      slidesToScroll: 1,
393      arrows: false,
394      fade: false,
395      dots: dotsStatus,
396      autoplay: false,
397      speed: 800,
398    });
399
400    /*
401     * Инициализируем основной слайдер с помощью библиотеки Slick Carousel.
402     * https://kenwheeler.github.io/slick/
403     */
404    // fade slider
405    $(".carousel__slider--with-thumbs").slick({
406      slidesToShow: 1,
407      slidesToScroll: 1,
408      arrows: false,
409      fade: false,
410      dots: false,
411      autoplay: false,
412      speed: 500,
413      asNavFor: ".carousel__nav",
414      // responsive: [
415      //   {
416      //     breakpoint: 768,
417      //     settings: {
418      //       dots: true,
419      //     },
420      //   },
421      //   // You can unslick at a given breakpoint now by adding:
422      //   // settings: "unslick"
423      //   // instead of a settings object
424      // ],
425    });
426    /*
427     * Инициализируем навигационный слайдер, который служит для навигации по элементам основного слайдера.
428     * https://kenwheeler.github.io/slick/
429     */
430    // navigation slider
431    $(".carousel__nav").slick({
432      slidesToShow: 4,
433      slidesToScroll: 1,
434      asNavFor: ".carousel__slider--with-thumbs",
435      dots: false,
436      arrows: false,
437      vertical: false,
438      centerMode: false,
439      focusOnSelect: true,
440      speed: 500,
441    });
442
443    const link = document.querySelector(".carousel__item");
444
445    if (link) {
446      let gallery = document.querySelector(".carousel__slider");
447      /*
448       * Инициализируем галерею изображений для карусели, используя библиотеку LightGallery.
449       * Здесь мы настраиваем опции галереи, такие как отключение кнопки скачивания,
450       * настройка чувствительности свайпа, селектор изображений и другие параметры.
451       * https://www.lightgalleryjs.com
452       */
453      lightGallery(gallery, {
454        download: false, // отключение кнопки скачивания
455        swipeThreshold: 50, // настройка чувствительности свайпа,
456        selector: "a.carousel__item",
457        counter: false,
458      });
459    }
460  }
461}
462
463function coverInit() {
464  const cover = document.querySelector(".cover");
465
466  if (cover) {
467    /*
468     * Инициализируем основной слайдер с помощью библиотеки Slick Carousel.
469     * Для основного слайдера заданы опции, такие как отображение одного слайда за раз,
470     * прокрутка одного слайда, скрытие стрелок навигации, использование эффекта затухания между слайдами,
471     * связывание с навигационным слайдером и другие параметры.
472     * https://kenwheeler.github.io/slick/
473     */
474    // fade slider
475    $(".cover__slider").slick({
476      slidesToShow: 1,
477      slidesToScroll: 1,
478      arrows: false,
479      fade: true,
480      asNavFor: ".cover__nav",
481      autoplay: true,
482      autoplaySpeed: 20000,
483      speed: 3000,
484      responsive: [
485        {
486          breakpoint: 768,
487          settings: {
488            dots: true,
489          },
490        },
491        // You can unslick at a given breakpoint now by adding:
492        // settings: "unslick"
493        // instead of a settings object
494      ],
495    });
496
497    var filtered = false;
498    if ($(window).width() < 768) {
499      if (filtered === false) {
500        $('.cover__slider').slick('slickFilter',':not(.hide-on-mobile)');
501        filtered = true;
502      }
503    };
504    /*
505     * Инициализируем навигационный слайдер, который служит для навигации по элементам основного слайдера.
506     * Навигационный слайдер также создается с помощью библиотеки Slick Carousel и настроен на показ четырех слайдов,
507     * прокрутку одного слайда, вертикальное отображение, выделение текущего элемента и другие опции.
508     * https://kenwheeler.github.io/slick/
509     */
510    // navigation slider
511    $(".cover__nav").slick({
512      slidesToShow: 4,
513      slidesToScroll: 1,
514      asNavFor: ".cover__slider",
515      dots: false,
516      arrows: false,
517      vertical: true,
518      centerMode: false,
519      focusOnSelect: true,
520      speed: 3000,
521    });
522  }
523}
524
525function filterInit() {
526  const filter = document.querySelector(".filter");
527
528  if (filter) {
529    const navFilterBtn = document.querySelector(".filter__nav-btn");
530    const navFilterList = document.querySelector(".filter__list");
531
532    navFilterBtn.addEventListener("click", (event) => {
533      event.preventDefault();
534      navFilterList.classList.toggle("filter__list--active");
535    });
536  }
537}
538
539function headerInit() {
540  const header = document.querySelector(".header");
541
542  if (header) {
543    /*
544    Находим все необходимые элементы внутри .header: кнопку поиска, иконки поиска и закрытия,
545    а также элемент с классом ".search".
546    */
547    const headerBtnSearch = document.querySelector(".header__btn--search");
548    const headerIconSearch = document.querySelector(".header__icon--search");
549    const headerIconClose = document.querySelector(".header__icon--close");
550    const search = document.querySelector(".search");
551
552    /* 
553    Добавляем обработчик события 'click' для кнопки ".header__btn--search".
554    При клике на нее происходит переключение класса "header__icon--hidden" для иконок поиска и закрытия,
555    а также переключение класса "search--active" для элемента ".search".
556    */
557    headerBtnSearch.addEventListener("click", (event) => {
558      event.preventDefault();
559      if (headerIconSearch.classList.contains("header__icon--hidden")) {
560        headerIconSearch.classList.remove("header__icon--hidden");
561        headerIconClose.classList.add("header__icon--hidden");
562      } else {
563        headerIconSearch.classList.add("header__icon--hidden");
564        headerIconClose.classList.remove("header__icon--hidden");
565      }
566
567      search.classList.toggle("search--active");
568    });
569
570    /*
571    Добавляем обработчик события 'click' на документ. Если элемент ".search" имеет класс "search--active"
572    и был произведен клик вне этого элемента и вне ".header", то класс "search--active" удаляется,
573    и иконки поиска и закрытия возвращаются в исходное состояние.
574    */
575    document.addEventListener("click", (event) => {
576      if (
577        search.classList.contains("search--active") &&
578        !header.contains(event.target) &&
579        !search.contains(event.target)
580      ) {
581        search.classList.remove("search--active");
582        headerIconSearch.classList.remove("header__icon--hidden");
583        headerIconClose.classList.add("header__icon--hidden");
584      }
585    });
586
587    // Получаем нужные элементы из DOM
588    const secondaryNavItems = document.querySelectorAll(
589      ".header__nav-item--nav-secondary"
590    );
591    const navMenus = document.querySelectorAll(".nav");
592    const tabControls = document.querySelectorAll(".nav__tabs-controls-item");
593    const tabContents = document.querySelectorAll(".nav__tabs-content-item");
594
595    // Функция для показа/скрытия второстепенного меню
596    secondaryNavItems.forEach((item) => {
597      item.addEventListener("click", function () {
598        const navNumber = this.getAttribute("data-nav");
599        const targetNav = document.querySelector(
600          '.nav[data-nav="' + navNumber + '"]'
601        );
602
603        if (targetNav.classList.contains("nav--active")) {
604          targetNav.classList.remove("nav--active");
605          this.classList.remove("nav__tabs-content-item--active");
606        } else {
607          // Закрываем все второстепенные меню
608          navMenus.forEach((menu) => {
609            menu.classList.remove("nav--active");
610          });
611
612          // Удаляем активный класс у всех .header__nav-item--nav-secondary
613          secondaryNavItems.forEach((item) => {
614            item.classList.remove("nav__tabs-content-item--active");
615          });
616
617          targetNav.classList.add("nav--active");
618          this.classList.add("nav__tabs-content-item--active");
619        }
620      });
621    });
622
623    // Закрытие меню при клике вне элемента .header или его дочерних элементов
624    document.addEventListener("click", function (event) {
625      const isClickInsideHeader = document
626        .querySelector(".header")
627        .contains(event.target);
628      if (!isClickInsideHeader) {
629        navMenus.forEach((menu) => {
630          menu.classList.remove("nav--active");
631        });
632        secondaryNavItems.forEach((item) => {
633          item.classList.remove("nav__tabs-content-item--active");
634        });
635      }
636    });
637    tabControls.forEach((control) => {
638      control.addEventListener("click", function () {
639        const tabValue = control.getAttribute("data-tab");
640
641        // Найти активный .nav, который содержит данный таб
642        const activeNav = document.querySelector(".nav.nav--active[data-nav]");
643
644        if (activeNav) {
645          // Убираем активный класс у всех контролов в текущем активном .nav
646          activeNav
647            .querySelectorAll(".nav__tabs-controls-item")
648            .forEach((c) => {
649              c.classList.remove("nav__tabs-controls-item--active");
650            });
651
652          // Назначаем активный класс кликнутому контролу
653          control.classList.add("nav__tabs-controls-item--active");
654
655          // Убираем активный класс у всех контентов в текущем активном .nav
656          activeNav.querySelectorAll(".nav__tabs-content-item").forEach((c) => {
657            c.classList.remove("nav__tabs-content-item--active");
658          });
659
660          // Назначаем активный класс контенту с соответствующим data-tab значением
661          const selectedContent = activeNav.querySelector(
662            '.nav__tabs-content-item[data-tab="' + tabValue + '"]'
663          );
664
665          if (selectedContent) {
666            /*
667             * Класс "nav__tabs-content-item--active" добавляется к выбранному контенту,
668             * чтобы обозначить его активным состоянием.
669             */
670            selectedContent.classList.add("nav__tabs-content-item--active");
671
672            // Находим родительский элемент выбранного контента (parentContent)
673            const parentContent = selectedContent.parentNode;
674
675            /*
676             * Проверяем, содержит ли выбранный контент класс "nav__tabs-content-item--single-img".
677             * Если содержит, то добавляем класс "nav__tabs-content--remove-padding" к родительскому контенту,
678             * в противном случае удаляем этот класс.
679             */
680            if (
681              selectedContent.classList.contains(
682                "nav__tabs-content-item--single-img"
683              )
684            ) {
685              parentContent.classList.add("nav__tabs-content--remove-padding");
686            } else {
687              parentContent.classList.remove(
688                "nav__tabs-content--remove-padding"
689              );
690            }
691
692            // Ищем внутри выбранного контента элемент с классом "nav__secondary" (вторичное меню).
693            const secondaryMenu =
694              selectedContent.querySelector(".nav__secondary");
695            if (secondaryMenu) {
696              /*
697               * Если вторичное меню найдено, мы вычисляем необходимую высоту paddingTop
698               * для позиционирования по вертикали.
699               */
700              const paddingTop = parseInt(
701                window.getComputedStyle(selectedContent).paddingTop
702              );
703              /*
704               * Вычисляем offsetTop для позиционирования вторичного меню относительно некоторых параметров
705               * выбранного контента и control
706               */
707              const offsetTop =
708                control.offsetTop +
709                control.offsetHeight -
710                control.clientHeight -
711                paddingTop -
712                45 -
713                2;
714              /*
715               * Устанавливаем top для вторичного меню, чтобы правильно позиционировать его
716               * относительно выбранного контента.
717               */
718              secondaryMenu.style.top = offsetTop + "px";
719            }
720          }
721        }
722      });
723    });
724
725    // Показать/скрыть навигацию на мобильных
726    const headerBtnNav = document.querySelector(".header__link-menu");
727    const nav = document.querySelector(".nav--mobile");
728    const body = document.querySelector("body");
729    const headerPhone = document.querySelector(".header__phone");
730
731    // Добавляем обработчик события клика на .header__btn-nav
732    headerBtnNav.addEventListener("click", () => {
733      // Добавляем класс nav--active к .nav
734      nav.classList.toggle("nav--active");
735      // Добавляем класс hidden-scroll к body
736      body.classList.toggle("hidden-scroll");
737      headerPhone.classList.toggle("header__phone--hidden");
738    });
739  }
740}
741
742function footerInit() {
743  let footer = document.querySelector(".footer");
744  if (footer) {
745    /*
746     * Весь нижеприведенный код не более чем копия из blocks/map/map.js
747     * 1) Заменены имена классов для .footer__form, .footer__form-input-wrapper,
748     * .footer__form-input-close, .footer__form-input-text, .footer__form-list, .footer__form-btn
749     * 2) Вырезан функционал фильтрации по категории
750     */
751
752    // Находим все элементы формы ввода текста на странице
753    let textInputs = document.querySelectorAll(".footer__form-input-text");
754    // Переменная для сохранения введенного текста
755    let inputText = "";
756
757    // Устанавливаем обработчик события ввода текста для каждого элемента формы
758    textInputs.forEach((textInput) => {
759      textInput.addEventListener("input", function () {
760        // Сохраняем введенный текст в нижнем регистре
761        inputText = this.value.toLowerCase();
762
763        btn = this.closest(".footer__form").querySelector(".footer__form-btn");
764
765        // Находим родителя элемента ".footer__form-input-wrapper"
766        let parentWrapper = this.closest(".footer__form-input-wrapper");
767        // Находим элемент ".footer__form-input-close" внутри родителя
768        let inputClose = parentWrapper.querySelector(
769          ".footer__form-input-close"
770        );
771
772        // Добавляем класс "footer__form-input-close--active" элементу при вводе текста
773        if (inputText !== "") {
774          inputClose.classList.add("footer__form-input-close--active");
775        } else {
776          // Удаляем класс "footer__form-input-close--active" если в инпуте нет символов
777          inputClose.classList.remove("footer__form-input-close--active");
778        }
779
780        // Фильтруем города на основе введенного текста и категории
781        let filteredCities = getFilteredCities(inputText);
782
783
784        // Находим все элементы с классом .footer__form-list
785        const filterLists = document.querySelectorAll(".footer__form-list");
786
787        // Для каждого найденного элемента добавляем обработчик события на клик
788        filterLists.forEach((filterList) => {
789          filterList.addEventListener("click", function (event) {
790            // Находим родителя элемента с классом .footer__form
791            const formItem = event.target.closest(".footer__form");
792
793            // Если родитель был найден, ищем дочерний элемент с классом .footer__form-input-text
794            if (formItem) {
795              const textInput = formItem.querySelector(
796                ".footer__form-input-text"
797              );
798
799              // Если элемент найден, выводим его значение
800              if (textInput) {
801                setTimeout(function () {
802                  // Создадим флаг, который показывает, было ли хоть одно совпадение
803                  let cityMatched = false;
804                  // Пройдем по каждому элементу в filteredCities
805                  filteredCities.forEach((obj) => {
806                    // Проверим, есть ли у текущего объекта поле city
807                    if (obj) {
808                      let city = obj.toLowerCase();
809
810                      // Сравним значение поля city с textInput.value
811
812                      if (city === textInput.value.toLowerCase()) {
813                        // Если хоть одно значение совпадает, установим флаг в true
814                        cityMatched = true;
815                      }
816                    }
817                  });
818
819
820                  // После прохода по всем значениям, проверим флаг и включим/выключим кнопку "Найти"
821                  if (cityMatched) {
822                    btn.classList.remove("footer__form-btn--disabled");
823                  } else {
824                    btn.classList.add("footer__form-btn--disabled");
825                  }
826                }, 350);
827              }
828            }
829          });
830        });
831
832        // Находим список подсказок для текущего элемента ввода
833        let suggestionsList = this.nextElementSibling;
834
835        // Очищаем список подсказок
836        suggestionsList.innerHTML = "";
837        // Если есть подходящие города и введен текст
838        if (filteredCities.length > 0 && inputText !== "") {
839          // Показываем список подсказок
840          suggestionsList.classList.add("footer__form-list--active");
841          // Добавляем подходящие города в список подсказок
842          filteredCities.forEach(function (destination) {
843            suggestionsList.innerHTML +=
844              "<li class='footer__form-list-item'>" +
845              destination +
846              "</li>";
847          });
848          const items = suggestionsList.querySelectorAll(
849            ".footer__form-list-item"
850          ); // Найти все элементы списка
851
852          if (items.length > 7) {
853            const totalItems = Math.min(items.length, 7); // Подсчитываем не более 7 элементов
854
855            let totalHeight = 0;
856            for (let i = 0; i < totalItems; i++) {
857              totalHeight += items[i].clientHeight;
858            }
859
860            suggestionsList.style.height = totalHeight + "px"; // Применяем сумму высот к suggestionsList
861          }
862        } else {
863          // Скрываем список подсказок, если нет подходящих городов
864          suggestionsList.classList.remove("footer__form-list--active");
865        }
866      });
867    });
868
869    // Устанавливаем обработчик события клика на элементах ".footer__form-input-close"
870    document.querySelectorAll(".footer__form").forEach((item) => {
871      item.addEventListener("click", function (event) {
872        if (event.target.classList.contains("footer__form-input-close")) {
873          // Находим родителя элемента ".footer__form-input-close"
874          let parentWrapper = event.target.closest(
875            ".footer__form-input-wrapper"
876          );
877          // Находим родителя элемента ".footer__form"
878          let form = event.target.closest(".footer__form");
879          // Находим кнопку
880          let btn = form.querySelector(".footer__form-btn");
881          // Выключаем кнопку "Найти"
882          btn.disabled = true;
883
884          // Очищаем инпут
885          parentWrapper.querySelector(".footer__form-input-text").value = "";
886
887          // Удаляем пункты списка
888          parentWrapper.querySelector(".footer__form-list").innerHTML = "";
889
890          // Скрываем список, удалив активный класс
891          parentWrapper
892            .querySelector(".footer__form-list")
893            .classList.remove("footer__form-list--active");
894
895          // Удаляем класс "footer__form-input-close--active" при сбросе значения
896          event.target.classList.remove("footer__form-input-close--active");
897        }
898      });
899    });
900
901    // Функция для фильтрации городов на основе введенного текста
902    function getFilteredCities(input) {
903
904      let cities = $('#citiesdata').val().split(',');
905      console.log(cities);
906
907      return cities.filter(function (city) {
908        let destinationText = city.toLowerCase();
909        let matched = true;
910
911        // Проверяем каждый символ во введенном тексте на соответствие
912        for (let i = 0; i < input.length; i++) {
913          if (destinationText[i] !== input[i]) {
914            matched = false;
915            break;
916          }
917        }
918        // Возвращаем только совпадающие города с выбранной
919        return matched;
920      });
921    }
922
923    // Устанавливаем обработчик события клика на элементах списка подсказок
924    document.querySelectorAll(".footer__form").forEach((item) => {
925      item.addEventListener("click", function (event) {
926        if (event.target.classList.contains("footer__form-list-item")) {
927          // Получаем выбранный город
928          let selectedCity = event.target.textContent;
929
930          // Заполняем текстовое поле выбранным городом
931          event.target
932            .closest(".footer__form")
933            .querySelector(".footer__form-input-text").value = selectedCity;
934
935          // Скрываем список подсказок и очищаем его
936          event.target
937            .closest(".footer__form-list")
938            .classList.remove("footer__form-list--active");
939          event.target.closest(".footer__form-list").innerHTML = "";
940        }
941      });
942    });
943
944    // Клик на "Найти"
945    document
946      .querySelector(".footer__form-btn")
947      .addEventListener("click", function (event) {
948        // Получаем значение введенного города
949        const selectedCity = document.querySelector("#input-find-city").value;
950
951        // Формируем URL-адрес с учетом правил API Яндекса
952        const yandexMapsURL =
953          "/dilerskaya-set/?city=" +
954          encodeURIComponent(selectedCity);
955        // Перенаправляем пользователя на страницу карты Яндекса с запросом для выбранного города
956        window.location.href = yandexMapsURL;
957      });
958  }
959}
960
961function mapInit(arrcities) {
962  let mapContainer = document.querySelector(".map");
963
964  if (mapContainer) {
965    let map;
966
967    // Массив данных (пример)
968	let destinations = [];
969	if(arrayDealers) destinations = arrayDealers;
970	let destinations_services = arrayServices;
971
972    // Находим все элементы формы ввода текста на странице
973    let textInputs = document.querySelectorAll(".map__filters-item-text-input");
974    // Переменная для сохранения введенного текста
975    let inputText = "";
976    // Переменная для сохранения выбранной категории
977    let category = "";
978
979    // Устанавливаем обработчик события ввода текста для каждого элемента формы
980    textInputs.forEach((textInput) => {
981      textInput.addEventListener("input", function () {
982        // Сохраняем введенный текст в нижнем регистре
983        inputText = this.value.toLowerCase();
984
985        // Получаем категорию из ближайшего родительского элемента
986        category = this.closest(".map__filters-item-form").dataset.category;
987
988        btn = this.closest(".map__filters-item-form").querySelector(
989          ".map__filters-item-btn"
990        );
991
992        // Находим родителя элемента ".map__filters-item-text-input-wrapper"
993        let parentWrapper = this.closest(
994          ".map__filters-item-text-input-wrapper"
995        );
996        // Находим элемент ".map__filters-item-text-input-close" внутри родителя
997        let inputClose = parentWrapper.querySelector(
998          ".map__filters-item-text-input-close"
999        );
1000
1001        // Добавляем класс "map__filters-item-text-input-close--active" элементу при вводе текста
1002        if (inputText !== "") {
1003          inputClose.classList.add(
1004            "map__filters-item-text-input-close--active"
1005          );
1006        } else {
1007          // Удаляем класс "map__filters-item-text-input-close--active" если в инпуте нет символов
1008          inputClose.classList.remove(
1009            "map__filters-item-text-input-close--active"
1010          );
1011        }
1012
1013        // Фильтруем города на основе введенного текста и категории
1014        let filteredCities = getFilteredCities2(inputText, category);
1015
1016
1017        // Находим все элементы с классом .map__filters-list
1018        const filterLists = document.querySelectorAll(".map__filters-list");
1019
1020        // Для каждого найденного элемента добавляем обработчик события на клик
1021        filterLists.forEach((filterList) => {
1022          filterList.addEventListener("click", function (event) {
1023            // Находим родителя элемента с классом .map__filters-item-form
1024            const formItem = event.target.closest(".map__filters-item-form");
1025
1026            // Если родитель был найден, ищем дочерний элемент с классом .map__filters-item-text-input
1027            if (formItem) {
1028              const textInput = formItem.querySelector(
1029                ".map__filters-item-text-input"
1030              );
1031
1032              // Если элемент найден, выводим его значение
1033              if (textInput) {
1034
1035                setTimeout(function () {
1036                  // Создадим флаг, который показывает, было ли хоть одно совпадение
1037                  let cityMatched = false;
1038
1039                  // Пройдем по каждому элементу в filteredCities
1040                  filteredCities.forEach((obj) => {
1041                    // Проверим, есть ли у текущего объекта поле city
1042                    if (obj) {
1043                      let city = obj.toLowerCase();
1044                      if (city === textInput.value.toLowerCase()) {
1045                        // Если хоть одно значение совпадает, установим флаг в true
1046                        cityMatched = true;
1047                      }
1048                    }
1049                  });
1050                  // После прохода по всем значениям, проверим флаг и включим/выключим кнопку "Найти"
1051                  if (cityMatched) {
1052                    btn.disabled = false;
1053                  } else {
1054                    btn.disabled = true;
1055                  }
1056                }, 350);
1057              }
1058            }
1059          });
1060        });
1061
1062        // Находим список подсказок для текущего элемента ввода
1063        let suggestionsList = this.nextElementSibling;
1064
1065        // Очищаем список подсказок
1066        suggestionsList.innerHTML = "";
1067
1068        // Если есть подходящие города и введен текст
1069        if (filteredCities.length > 0 && inputText !== "") {
1070          // Показываем список подсказок
1071          suggestionsList.classList.add("map__filters-list--active");
1072          // Добавляем подходящие города в список подсказок
1073          filteredCities.forEach(function (city) {
1074            suggestionsList.innerHTML +=
1075              "<li class='map__filters-list-item'>" +
1076              city +
1077              "</li>";
1078          });
1079          const items = suggestionsList.querySelectorAll(
1080            ".map__filters-list-item"
1081          ); // Найти все элементы списка
1082
1083          if (items.length > 7) {
1084            const totalItems = Math.min(items.length, 7); // Подсчитываем не более 7 элементов
1085
1086            let totalHeight = 0;
1087            for (let i = 0; i < totalItems; i++) {
1088              totalHeight += items[i].clientHeight;
1089            }
1090
1091            suggestionsList.style.height = totalHeight + "px"; // Применяем сумму высот к suggestionsList
1092          }
1093        } else {
1094          // Скрываем список подсказок, если нет подходящих городов
1095          suggestionsList.classList.remove("map__filters-list--active");
1096        }
1097      });
1098    });
1099
1100    // Устанавливаем обработчик события клика на элементах ".map__filters-item-text-input-close"
1101    document.querySelectorAll(".map__filters-item").forEach((item) => {
1102
1103      item.addEventListener("click", function (event) {
1104        if (
1105          event.target.classList.contains("map__filters-item-text-input-close")
1106        ) {
1107          // Находим родителя элемента ".map__filters-item-text-input-close"
1108          let parentWrapper = event.target.closest(
1109            ".map__filters-item-text-input-wrapper"
1110          );
1111          // Находим родителя элемента ".map__filters-item-form"
1112          let form = event.target.closest(".map__filters-item-form");
1113          // Находим кнопку
1114          let btn = form.querySelector(".map__filters-item-btn");
1115          // Выключаем кнопку "Найти"
1116          btn.disabled = true;
1117
1118          // Очищаем инпут
1119          parentWrapper.querySelector(".map__filters-item-text-input").value =
1120            "";
1121
1122          // Удаляем пункты списка
1123          parentWrapper.querySelector(".map__filters-list").innerHTML = "";
1124
1125          // Скрываем список, удалив активный класс
1126          parentWrapper
1127            .querySelector(".map__filters-list")
1128            .classList.remove("map__filters-list--active");
1129
1130          // Удаляем класс "map__filters-item-text-input-close--active" при сбросе значения
1131          event.target.classList.remove(
1132            "map__filters-item-text-input-close--active"
1133          );
1134        }
1135      });
1136    });
1137    
1138    
1139
1140    // Функция для фильтрации городов на основе введенного текста и категории
1141    function getFilteredCities2(input, category) {
1142      let cities;
1143      if(category == 'dealer') cities = $('#citiesdata').val().split(',');
1144      else if(category == 'service-partner-repair') cities = $('#scitiesdata').val().split(',');
1145      else if(category == 'service-partner-parts') cities = $('#scitiesdata').val().split(',');
1146
1147      return cities.filter(function (сity) {
1148        let destinationText = сity.toLowerCase();
1149        let matched = true;
1150
1151        // Проверяем каждый символ во введенном тексте на соответствие
1152        for (let i = 0; i < input.length; i++) {
1153          if (destinationText[i] !== input[i]) {
1154            matched = false;
1155            break;
1156          }
1157        }
1158
1159        // Возвращаем только совпадающие города с выбранной категорией
1160        return matched;
1161      });
1162    }
1163
1164    // Устанавливаем обработчик события клика на элементах списка подсказок
1165    document.querySelectorAll(".map__filters-item").forEach((item) => {
1166      item.addEventListener("click", function (event) {
1167        if (event.target.classList.contains("map__filters-list-item")) {
1168          // Получаем выбранный город
1169          let selectedCity = event.target.textContent;
1170
1171          // Заполняем текстовое поле выбранным городом
1172          event.target
1173            .closest(".map__filters-item")
1174            .querySelector(".map__filters-item-text-input").value =
1175            selectedCity;
1176
1177          // Скрываем список подсказок и очищаем его
1178          event.target
1179            .closest(".map__filters-list")
1180            .classList.remove("map__filters-list--active");
1181          event.target.closest(".map__filters-list").innerHTML = "";
1182        }
1183      });
1184    });
1185
1186    // Координаты партнеров
1187    let userCoords = "";
1188
1189    /*
1190     * Проверяем наличие разрешения на определения местоположения.
1191     * Если разрешение есть, то геолокация определить координаты пользователя корректно
1192     * Если разрешения нет или пользователь отказал в доступе к геолокации, то вызываем карту без начальных координат
1193     */
1194    if (navigator.geolocation) {
1195      navigator.permissions
1196        .query({ name: "geolocation" })
1197        .then(function (permissionStatus) {
1198          if (permissionStatus.state === "granted") {
1199            navigator.geolocation.getCurrentPosition(function (position) {
1200
1201              let latitude = position.coords.latitude;
1202              let longitude = position.coords.longitude;
1203              userCoords = latitude.toString() + "," + longitude.toString();
1204              initYandexMap(userCoords);
1205            });
1206          } else if (permissionStatus.state === "denied") {
1207            console.log("Пользователь отказал в доступе к геолокации.");
1208            userCoords = "";
1209            initYandexMap(userCoords);
1210          } else {
1211            console.log(
1212              "Состояние доступа к геолокации: " + permissionStatus.state
1213            );
1214            userCoords = "";
1215            initYandexMap(userCoords);
1216          }
1217        });
1218    } else {
1219      console.log("Geolocation не поддерживается вашим браузером.");
1220    }
1221
1222    // Логика инициализации карты
1223    function initYandexMap(userCoords) {
1224      ymaps.ready(function () {
1225        map = new ymaps.Map("map-small", {
1226          center: [55.76, 37.64],
1227          zoom: 5,
1228        });
1229
1230        let geoObjects = [];
1231
1232        // Создание макета балуна
1233        MyBalloonLayout = ymaps.templateLayoutFactory.createClass(
1234          '<div class="map__canvas-balloon">' +
1235            '<div class="map__canvas-btn-close"></div>' +
1236            '<div class="map__canvas-balloon-container">' +
1237/*            '<div class="map__canvas-title">Ближайший к Вам центр</div>' +*/
1238            "<div>$[[options.contentLayout observeSize]]</div>" +
1239            '<div class="map__canvas-arrow"></div>' +
1240            "</div>" +
1241            "</div>",
1242          {
1243            /**
1244             * Строит экземпляр макета на основе шаблона и добавляет его в родительский HTML-элемент.
1245             * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/layout.templateBased.Base.xml#build
1246             * @function
1247             * @name build
1248             */
1249            build: function () {
1250              this.constructor.superclass.build.call(this);
1251
1252              this._$element = $(
1253                ".map__canvas-balloon",
1254                this.getParentElement()
1255              );
1256
1257              this.applyElementOffset();
1258
1259              this._$element
1260                .find(".map__canvas-btn-close")
1261                .on("click", $.proxy(this.onCloseClick, this));
1262            },
1263
1264            /**
1265             * Удаляет содержимое макета из DOM.
1266             * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/layout.templateBased.Base.xml#clear
1267             * @function
1268             * @name clear
1269             */
1270            clear: function () {
1271              this._$element.find(".map__canvas-btn-close").off("click");
1272
1273              this.constructor.superclass.clear.call(this);
1274            },
1275
1276            /**
1277             * Метод будет вызван системой шаблонов АПИ при изменении размеров вложенного макета.
1278             * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
1279             * @function
1280             * @name onSublayoutSizeChange
1281             */
1282            onSublayoutSizeChange: function () {
1283              MyBalloonLayout.superclass.onSublayoutSizeChange.apply(
1284                this,
1285                arguments
1286              );
1287
1288              if (!this._isElement(this._$element)) {
1289                return;
1290              }
1291
1292              this.applyElementOffset();
1293
1294              this.events.fire("shapechange");
1295            },
1296
1297            /**
1298             * Сдвигаем балун, чтобы "хвостик" указ
1298ывал на точку привязки.
1299             * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
1300             * @function
1301             * @name applyElementOffset
1302             */
1303            applyElementOffset: function () {
1304              this._$element.css({
1305                left: -(this._$element[0].offsetWidth / 2),
1306                top: -(
1307                  this._$element[0].offsetHeight +
1308                  this._$element.find(".map__canvas-arrow")[0].offsetHeight
1309                ),
1310              });
1311            },
1312
1313            /**
1314             * Закрывает балун при клике на крестик, кидая событие "userclose" на макете.
1315             * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
1316             * @function
1317             * @name onCloseClick
1318             */
1319            onCloseClick: function (e) {
1320              e.preventDefault();
1321
1322              this.events.fire("userclose");
1323            },
1324
1325            /**
1326             * Используется для автопозиционирования (balloonAutoPan).
1327             * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/ILayout.xml#getClientBounds
1328             * @function
1329             * @name getClientBounds
1330             * @returns {Number[][]} Координаты левого верхнего и правого нижнего углов шаблона относительно точки привязки.
1331             */
1332            getShape: function () {
1333              if (!this._isElement(this._$element)) {
1334                return MyBalloonLayout.superclass.getShape.call(this);
1335              }
1336
1337              var position = this._$element.position();
1338
1339              return new ymaps.shape.Rectangle(
1340                new ymaps.geometry.pixel.Rectangle([
1341                  [position.left, position.top],
1342                  [
1343                    position.left + this._$element[0].offsetWidth,
1344                    position.top +
1345                      this._$element[0].offsetHeight +
1346                      this._$element.find(".arrow")[0].offsetHeight,
1347                  ],
1348                ])
1349              );
1350            },
1351
1352            /**
1353             * Проверяем наличие элемента (в ИЕ и Опере его еще может не быть).
1354             * @function
1355             * @private
1356             * @name _isElement
1357             * @param {jQuery} [element] Элемент.
1358             * @returns {Boolean} Флаг наличия.
1359             */
1360            _isElement: function (element) {
1361              return element && element[0] && element.find(".arrow")[0];
1362            },
1363          }
1364        );
1365
1366        // Разбираем массив, создаем балуны и формируем содержимое балуна
1367      // для сервисных центров и диллеров<k
1368        destinations.forEach(function (place) {
1369          // Создание вложенного макета содержимого балуна.
1370		  let statusname = '';
1371		  if(place.categoryFlag == 'service-partner-repair') {statusname = 'Сервисный центр';}
1372		  if(place.categoryFlag == 'dealer') {statusname = 'Дилер';}
1373		  if(place.categoryFlag == 'distributor') {statusname = 'Дистрибьютор';}
1374          MyBalloonContentLayout = ymaps.templateLayoutFactory.createClass(
1375            '<div class="map__canvas-title">'+place.name+'<span>'+statusname+'</span></div>' +
1376			/*'<div class="map__canvas-title">'+place.categoryFlag+'</div>' +*/
1377			
1378			'<div class="map__canvas-text">' +place.address +'<span class="telephone">'+place.phone+
1379              "</span></div>"
1380              /*'<div class="map__canvas-btn-route-wrapper"><button class="map__canvas-btn-route" onclick="window.open(\'https://www.yandex.ru/maps/?rtext=' +
1381              userCoords +
1382              "~" +
1383              place.coords +
1384              "')\">Проложить маршрут</button></div>"*/
1385          );
1386          let icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1387          if(place.categoryFlag == 'service-partner-repair' || place.categoryFlag == 'service-partner-parts') icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1388          else icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1389          let placemark = new ymaps.Placemark(JSON.parse(place.coords), map.getCenter(), {
1390            hintContent: place.address,
1391            balloonShadow: false,
1392            balloonLayout: MyBalloonLayout,
1393            balloonContentLayout: MyBalloonContentLayout,
1394            iconLayout: "default#image",
1395            // Своё изображение иконки метки
1396            iconImageHref: icon,
1397            // Размеры метки
1398            iconImageSize: [41.92, 54],
1399            // Смещение левого верхнего угла иконки относительно
1400            // её "ножки" (точки привязки)
1401            iconImageOffset: [-21, -54],
1402          });
1403
1404          //geoObjects.push(placemark);
1405          map.geoObjects.add(placemark);
1406
1407        });
1408        //map.geoObjects.add(geoObjects);
1409        /*let icon = "/local/templates/xcmg2024/img/map/icon-marker.webp";
1410        let clusterer = new ymaps.Clusterer({
1411          clusterDisableClickZoom: true,
1412          clusterOpenBalloonOnClick: true,
1413          clusterBalloonContentLayout: null,
1414          iconLayout: "default#image",
1415          // Своё изображение иконки метки
1416          iconImageHref: icon,
1417          // Размеры метки
1418          iconImageSize: [41.92, 54],
1419          // Смещение левого верхнего угла иконки относительно
1420          // её "ножки" (точки привязки)
1421          iconImageOffset: [-21, -54],
1422        });
1423
1424        clusterer.add(geoObjects);
1425        map.geoObjects.add(clusterer);*/
1426
1427      });
1428    }
1429
1430    // Клик на кнопку "Найти" Сервисные центры (второй и третий баннер)
1431    document
1432      .querySelector(".map__filters")
1433      .addEventListener("click", function (event) {
1434
1435        event.preventDefault();
1436        if (event.target.classList.contains("map__filters-item-btn")) {
1437
1438          let button = event.target;
1439
1440          let selectedCity = button.parentNode.querySelector(
1441            ".map__filters-item-text-input"
1442          ).value;
1443
1444          let category = button.parentNode.getAttribute("data-category");
1445
1446          let filteredOrganizations = destinations.filter(function (destination) {
1447
1448            return (
1449              destination.city === selectedCity &&
1450                (destination.categoryFlag === category )
1451            );
1452
1453          });
1454
1455
1456
1457          let organizationsData = filteredOrganizations.map(function (
1458            destination
1459          ) {
1460
1461            return {
1462              name: destination.name,
1463              category: destination.categoryFlag,
1464              address: destination.address,
1465              phone: destination.phone,
1466              coords: JSON.parse(destination.coords),
1467            };
1468          });
1469          // initYandexMap(organizationsData.coords);
1470
1471
1472          // Отображаем выбранные организации на Яндекс.Картах
1473          // Логика инициализации карты остается той же
1474          ymaps.ready(function () {
1475
1476            map.destroy();
1477            let mapCenter;
1478            let category;
1479
1480            organizationsData.forEach(function (place) {
1481              console.log(place)
1482              mapCenter = place.coords;
1483              category = place.category;
1484            });
1485            let icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1486            if(category == 'service-partner-repair' || category == 'service-partner-parts')
1487              icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1488            else icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1489
1490            map = new ymaps.Map("map-small", {
1491              center: mapCenter,
1492              zoom: 4,
1493            });
1494
1495
1496
1497            let geoObjects = [];
1498
1499            // Создание макета балуна
1500            MyBalloonLayout = ymaps.templateLayoutFactory.createClass(
1501              '<div class="map__canvas-balloon">' +
1502                '<div class="map__canvas-btn-close"></div>' +
1503                '<div class="map__canvas-balloon-container">' +
1504                /*'<div class="map__canvas-title">Ближайший к Вам адрес</div>' +*/
1505                "$[[options.contentLayout observeSize]]" +
1506                '<div class="map__canvas-arrow"></div>' +
1507                "</div>" +
1508                "</div>",
1509              {
1510                /**
1511                 * Строит экземпляр макета на основе шаблона и добавляет его в родительский HTML-элемент.
1512                 * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/layout.templateBased.Base.xml#build
1513                 * @function
1514                 * @name build
1515                 */
1516                build: function () {
1517                  this.constructor.superclass.build.call(this);
1518
1519                  this._$element = $(
1520                    ".map__canvas-balloon",
1521                    this.getParentElement()
1522                  );
1523
1524                  this.applyElementOffset();
1525
1526                  this._$element
1527                    .find(".map__canvas-btn-close")
1528                    .on("click", $.proxy(this.onCloseClick, this));
1529                },
1530
1531                /**
1532                 * Удаляет содержимое макета из DOM.
1533                 * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/layout.templateBased.Base.xml#clear
1534                 * @function
1535                 * @name clear
1536                 */
1537                clear: function () {
1538                  this._$element.find(".map__canvas-btn-close").off("click");
1539
1540                  this.constructor.superclass.clear.call(this);
1541                },
1542
1543                /**
1544                 * Метод будет вызван системой шаблонов АПИ при изменении размеров вложенного макета.
1545                 * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
1546                 * @function
1547                 * @name onSublayoutSizeChange
1548                 */
1549                onSublayoutSizeChange: function () {
1550                  MyBalloonLayout.superclass.onSublayoutSizeChange.apply(
1551                    this,
1552                    arguments
1553                  );
1554
1555                  if (!this._isElement(this._$element)) {
1556                    return;
1557                  }
1558
1559                  this.applyElementOffset();
1560
1561                  this.events.fire("shapechange");
1562                },
1563
1564                /**
1565                 * Сдвигаем балун, чтобы "хвостик" указ
1565ывал на точку привязки.
1566                 * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
1567                 * @function
1568                 * @name applyElementOffset
1569                 */
1570                applyElementOffset: function () {
1571                  this._$element.css({
1572                    left: -(this._$element[0].offsetWidth / 2),
1573                    top: -(
1574                      this._$element[0].offsetHeight +
1575                      this._$element.find(".map__canvas-arrow")[0].offsetHeight
1576                    ),
1577                  });
1578                },
1579
1580                /**
1581                 * Закрывает балун при клике на крестик, кидая событие "userclose" на макете.
1582                 * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
1583                 * @function
1584                 * @name onCloseClick
1585                 */
1586                onCloseClick: function (e) {
1587                  e.preventDefault();
1588
1589                  this.events.fire("userclose");
1590                },
1591
1592                /**
1593                 * Используется для автопозиционирования (balloonAutoPan).
1594                 * @see https://api.yandex.ru/maps/doc/jsapi/2.1/ref/reference/ILayout.xml#getClientBounds
1595                 * @function
1596                 * @name getClientBounds
1597                 * @returns {Number[][]} Координаты левого верхнего и правого нижнего углов шаблона относительно точки привязки.
1598                 */
1599                getShape: function () {
1600                  if (!this._isElement(this._$element)) {
1601                    return MyBalloonLayout.superclass.getShape.call(this);
1602                  }
1603
1604                  var position = this._$element.position();
1605
1606                  return new ymaps.shape.Rectangle(
1607                    new ymaps.geometry.pixel.Rectangle([
1608                      [position.left, position.top],
1609                      [
1610                        position.left + this._$element[0].offsetWidth,
1611                        position.top +
1612                          this._$element[0].offsetHeight +
1613                          this._$element.find(".arrow")[0].offsetHeight,
1614                      ],
1615                    ])
1616                  );
1617                },
1618
1619                /**
1620                 * Проверяем наличие элемента (в ИЕ и Опере его еще может не быть).
1621                 * @function
1622                 * @private
1623                 * @name _isElement
1624                 * @param {jQuery} [element] Элемент.
1625                 * @returns {Boolean} Флаг наличия.
1626                 */
1627                _isElement: function (element) {
1628                  return element && element[0] && element.find(".arrow")[0];
1629                },
1630              }
1631            );
1632
1633            // Разбираем массив, создаем балуны и формируем содержимое балуна
1634            organizationsData.forEach(function (place) {
1635              // Создание вложенного макета содержимого балуна.
1636			
1637			  let statusname = '';
1638			  if(place.category == 'service-partner-repair') {statusname = 'Сервисный центр';}
1639			  if(place.category == 'dealer') {statusname = 'Дилер';}
1640			  if(place.category == 'distributor') {statusname = 'Дистрибьютор';}
1641              MyBalloonContentLayout = ymaps.templateLayoutFactory.createClass(
1642				 '<div class="map__canvas-title">'+place.name+'<span>'+statusname+'</span></div>' +
1643                '<div class="map__canvas-text">' +
1644                  place.address + 
1645				  '<span class="telephone">'+place.phone + '</span>' +
1646                  "</div>"
1647                  /*'<div class="map__canvas-btn-route-wrapper"><button class="map__canvas-btn-route" onclick="window.open(\'https://www.yandex.ru/maps/?rtext=' +
1648                  userCoords +
1649                  "~" +
1650                  place.coords +
1651                  "')\">Проложить маршрут</button></div>"*/
1652              );
1653              let icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1654              if(category == 'service-partner-repair' || category == 'service-partner-parts') icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1655              else icon = "/local/templates/xcmg2024/img/map/icon-marker-new.png";
1656
1657              let placemark = new ymaps.Placemark(
1658                place.coords,
1659                map.getCenter(),
1660                {
1661                  hintContent: place.address,
1662                  balloonShadow: false,
1663                  balloonLayout: MyBalloonLayout,
1664                  balloonContentLayout: MyBalloonContentLayout,
1665                  iconLayout: "default#image",
1666                  // Своё изображение иконки метки
1667                  iconImageHref: icon,
1668                  // Размеры метки
1669                  iconImageSize: [41.92, 54],
1670                  // Смещение левого верхнего угла иконки относительно
1671                  // её "ножки" (точки привязки)
1672                  iconImageOffset: [-21, -54],
1673                }
1674              );
1675
1676              //geoObjects.push(placemark);
1677              //alert(geoObjects)
1678              map.geoObjects.add(placemark);
1679            });
1680            /*let icon = "/local/templates/xcmg2024/img/map/icon-marker.webp";
1681            let clusterer = new ymaps.Clusterer({
1682              clusterDisableClickZoom: true,
1683              clusterOpenBalloonOnClick: true,
1684              clusterBalloonContentLayout: null,
1685              iconLayout: "default#image",
1686              // Своё изображение иконки метки
1687              iconImageHref: icon,
1688              // Размеры метки
1689              iconImageSize: [41.92, 54],
1690              // Смещение левого верхнего угла иконки относительно
1691              // её "ножки" (точки привязки)
1692              iconImageOffset: [-21, -54],
1693            });
1694
1695            clusterer.add(geoObjects);*/
1696//            map.geoObjects.add(clusterer);
1697
1698
1699          });
1700        }
1701      });
1702
1703    // ----------------------------------------------------------------
1704    const regionsToCities = arrRegionsCities;
1705    console.log(arrRegionsCities);
1706        /*{
1707      moscow: ["Москва", "Алабино"],
1708      krasnoyarsk: ["Артемовск", "Балахта", "Кежма"],
1709      bashkortostan: ["Иглино", "Старобалтачево", "Старый Оскол", "Чернянка"],
1710    };*/
1711
1712    const regionsSelect = document.querySelector(
1713      ".map__search-form-select--region"
1714    );
1715    const citiesSelect = document.querySelector(
1716      ".map__search-form-select--city"
1717    );
1718    const findBtn = document.querySelector(".map__search-form-btn");
1719
1720    regionsSelect.addEventListener("change", function () {
1721      const selectedRegion = this.value;
1722      citiesSelect.innerHTML =
1723        '<option value="" disabled selected>Выберите город</option>';
1724
1725      if (selectedRegion) {
1726        regionsToCities[selectedRegion].forEach((city) => {
1727          const option = document.createElement("option");
1728          option.text = city;
1729          citiesSelect.add(option);
1730        });
1731        citiesSelect.disabled = false;
1732      } else {
1733        citiesSelect.disabled = true;
1734        findBtn.disabled = true;
1735      }
1736    });
1737
1738    citiesSelect.addEventListener("change", function () {
1739      findBtn.disabled = !this.value;
1740    });
1741
1742    findBtn.addEventListener("click", function () {
1743      const selectedCity = citiesSelect.value;
1744
1745      if (selectedCity) {
1746        const urlParams = new URLSearchParams();
1747        urlParams.append("region", regionsSelect.value);
1748        urlParams.append("city", selectedCity);
1749
1750        const queryString = urlParams.toString();
1751
1752        //location.href = "/dilerskaya-set/?" + queryString;
1753        form = findBtn.closest('.map__search-form')
1754        //let searchUrl = "/garantiya-i-servis/set-servisnyix-czentrov/?" + queryString;
1755        let searchUrl = "/dilerskaya-set/?" + queryString;
1756        window.location.href=searchUrl;
1757      }
1758    });
1759    // ----------------------------------------------------------------
1760  }
1761}
1762
1763function newsInit() {
1764  const news = document.querySelector(".news");
1765
1766  if (news) {
1767    let gallery = document.querySelector(".news__list");
1768
1769    /*
1770     * Cоздаем новый экземпляр Masonry для организации плиточного макета
1771     * https://masonry.desandro.com
1772     */
1773    // Инициализация Masonry
1774    let msnry = new Masonry(gallery, {
1775      itemSelector: ".news__list-item",
1776      columnWidth: ".news__list-item",
1777      gutter: 28,
1778      horizontalOrder: true,
1779      fitWidth: true,
1780    });
1781
1782    // Функция для проверки ширины окна браузера и поворота устройства, и изменения настроек Masonry
1783    function checkViewportWidth() {
1784      if (window.innerWidth <= 768) {
1785        msnry.destroy(); // Уничтожаем текущий Masonry
1786
1787        msnry = new Masonry(gallery, {
1788          itemSelector: ".news__list-item",
1789          columnWidth: ".news__list-item",
1790          gutter: 6,
1791          horizontalOrder: true,
1792          fitWidth: true,
1793          percentPosition: true, // Добавляем новую опцию
1794        });
1795      } else {
1796        msnry.destroy(); // Уничтожаем текущий Masonry
1797
1798        msnry = new Masonry(gallery, {
1799          itemSelector: ".news__list-item",
1800          columnWidth: ".news__list-item",
1801          gutter: 28,
1802          horizontalOrder: true,
1803          fitWidth: true,
1804        });
1805      }
1806    }
1807
1808    // Функция для инициализации Masonry и проверки ширины окна при загрузке страницы
1809    function initMasonryAndCheckWidth() {
1810      checkViewportWidth(); // Проверяем ширину окна при загрузке страницы
1811
1812      // Отслеживание изменения размера окна браузера
1813      window.addEventListener("resize", checkViewportWidth);
1814
1815      // Отслеживание поворота устройства
1816      window.addEventListener("orientationchange", checkViewportWidth);
1817    }
1818
1819    // Вызываем функцию для инициализации Masonry и проверки ширины окна при загрузке
1820    initMasonryAndCheckWidth();
1821  }
1822}
1823
1824function historyInit() {
1825  const history = document.querySelector(".history");
1826
1827  if (history) {
1828    // Получаем все элементы табов и их контента
1829    const tabs = document.querySelectorAll(".history__tabs-nav-item");
1830    const tabContents = document.querySelectorAll(
1831      ".history__tabs-content-item"
1832    );
1833
1834    // Добавляем обработчик клика для каждого элемента таба
1835    tabs.forEach((tab) => {
1836      tab.addEventListener("click", () => {
1837        const tabIndex = tab.dataset.tab; // Получаем индекс таба
1838
1839        // Убираем активный класс у всех табов
1840        tabs.forEach((tab) => {
1841          tab.classList.remove("history__tabs-nav-item--active");
1842        });
1843
1844        // Добавляем активный класс к текущему табу
1845        tab.classList.add("history__tabs-nav-item--active");
1846
1847        // Переключаем активное содержимое
1848        tabContents.forEach((content) => {
1849          if (content.dataset.tab === tabIndex) {
1850            content.classList.add("history__tabs-content-item--active");
1851          } else {
1852            content.classList.remove("history__tabs-content-item--active");
1853          }
1854        });
1855      });
1856    });
1857  }
1858}
1859
1860$(".nav__item--second-lvl>.nav__link").click(function () {
1861    console.log(true)
1862    $(this).parent(".nav__item--second-lvl").toggleClass("_open")
1863    $(this).siblings(".nav__submenu").slideToggle()
1864})
1865
1866function navInit() {
1867  return 
1868  const nav = document.querySelector(".nav");
1869
1870  if (nav) {
1871    const navItems = document.querySelectorAll(".nav__item--second-lvl");
1872    let openedSubMenu = null; // Ссылка на текущее открытое подменю
1873
1874    function hideSubMenu(submenu) {
1875      if (submenu) {
1876        submenu.style.height = "0";
1877        // Удаляем класс у текущего открытого элемента .nav__item--second-lvl
1878        if (openedSubMenu) {
1879          openedSubMenu.parentElement.classList.remove("nav__item--active");
1880        }
1881        openedSubMenu = null; // Сбрасываем ссылку на открытое меню
1882      }
1883    }
1884
1885    function showSubMenu(submenu) {
1886      // submenu.style.height = submenu.scrollHeight + "px";
1887	  submenu.style.height = "auto";
1888      // Удаляем класс у текущего открытого элемента .nav__item--second-lvl
1889      if (openedSubMenu) {
1890        openedSubMenu.parentElement.classList.remove("nav__item--active");
1891      }
1892      openedSubMenu = submenu; // Сохраняем ссылку на новое открытое меню
1893      submenu.parentElement.classList.add("nav__item--active");
1894    }
1895
1896    navItems.forEach((navItem) => {
1897
1898      navItem.querySelector(".nav__item--second-lvl>.nav__link").addEventListener("click", (event) => {
1899        event.stopPropagation();
1900
1901        const submenu = navItem.querySelector(".nav__submenu");
1902
1903        if (!submenu) return;
1904
1905        if (submenu !== openedSubMenu) {
1906          // Если кликнутое меню отличается от открытого, закрываем открытое и открываем новое
1907          //hideSubMenu(openedSubMenu);
1908          //showSubMenu(submenu);
1909        } else {
1910          // Если кликнутое меню совпадает с открытым, просто закрываем его
1911          //hideSubMenu(openedSubMenu);
1912        }
1913      });
1914    });
1915
1916    document.addEventListener("click", (event) => {
1917      // Закрываем открытое меню при клике за его пределами
1918      if (openedSubMenu && !nav.contains(event.target)) {
1919        hideSubMenu(openedSubMenu);
1920      }
1921    });
1922  }
1923}
1924
1925function productInit() {
1926  const product = document.querySelector(".product");
1927
1928  if (product) {
1929    /*
1930     * Инициализируем карусель, используя библиотеку Slick Carousel
1931     * https://kenwheeler.github.io/slick/
1932     */
1933    $(".product__slider").slick({
1934      slidesToShow: 1,
1935      slidesToScroll: 1,
1936      arrows: false,
1937      fade: true,
1938      asNavFor: ".product__nav",
1939      autoplay: false,
1940      speed: 800,
1941      responsive: [
1942        {
1943          breakpoint: 1024,
1944          settings: {
1945            slidesToShow: 1,
1946            slidesToScroll: 1,
1947            arrows: false,
1948            dots: true,
1949            fade: false,
1950            autoplay: false,
1951            speed: 800,
1952          },
1953        },
1954        // You can unslick at a given breakpoint now by adding:
1955        // settings: "unslick"
1956        // instead of a settings object
1957      ],
1958    });
1959    /*
1960     * Инициализируем карусель, используя библиотеку Slick Carousel для навигации по основному слайдеру
1961     * https://kenwheeler.github.io/slick/
1962     */
1963    $(".product__nav").slick({
1964      slidesToShow: 4,
1965      slidesToScroll: 1,
1966      asNavFor: ".product__slider",
1967      dots: false,
1968      arrows: false,
1969      vertical: false,
1970      centerMode: false,
1971      focusOnSelect: true,
1972      speed: 800,
1973      responsive: [
1974        {
1975          breakpoint: 1024,
1976          settings: "unslick",
1977        },
1978        // You can unslick at a given breakpoint now by adding:
1979        // settings: "unslick"
1980        // instead of a settings object
1981      ],
1982    });
1983    /*
1984     * Инициализируем галерею изображений для карусели, используя библиотеку LightGallery.
1985     * Здесь мы настраиваем опции галереи, такие как отключение кнопки скачивания,
1986     * настройка чувствительности свайпа, селектор изображений и другие параметры.
1987     * https://www.lightgalleryjs.com
1988     */
1989    let gallery = document.querySelector(".product__slider");
1990    lightGallery(gallery, {
1991      download: false, // отключение кнопки скачивания
1992      swipeThreshold: 50, // настройка чувствительности свайпа,
1993      selector: ".product__slider-item",
1994      counter: false,
1995    });
1996
1997    // Получаем все элементы табов и их контента
1998    const tabs = document.querySelectorAll(".product__tabs-nav-item");
1999    const tabContents = document.querySelectorAll(
2000      ".product__tabs-content-item"
2001    );
2002
2003    // Добавляем обработчик клика для каждого элемента таба
2004    tabs.forEach((tab) => {
2005      tab.addEventListener("click", () => {
2006        const tabIndex = tab.dataset.tab; // Получаем индекс таба
2007
2008        // Убираем активный класс у всех табов
2009        tabs.forEach((tab) => {
2010          tab.classList.remove("product__tabs-nav-item--active");
2011        });
2012
2013        // Добавляем активный класс к текущему табу
2014        tab.classList.add("product__tabs-nav-item--active");
2015
2016        // Переключаем активное содержимое
2017        tabContents.forEach((content) => {
2018          if (content.dataset.tab === tabIndex) {
2019            content.classList.add("product__tabs-content-item--active");
2020          } else {
2021            content.classList.remove("product__tabs-content-item--active");
2022          }
2023        });
2024
2025        // Устанавливаем таймер, чтобы с задержкой применить высоту таблицы
2026        setTimeout(() => {
2027          calcTableHeight();
2028          accordionInit();
2029        }, 200);
2030      });
2031    });
2032
2033    function calcTableHeight() {
2034      /*
2035       * Выбираем активную вкладку.
2036       * Функция ищет активную вкладку с классом .product__tabs-content-item--active.
2037       */
2038      let activeTab = document.querySelector(
2039        ".product__tabs-content-item--active"
2040      );
2041
2042      /*
2043      * Вычисляем высоту таблицы.
2044      Происходит подсчет общей высоты таблицы, суммируя высоту строк (.product__tr) до четырех строк или до конца таблицы.
2045      */
2046      let totalHeight = 0;
2047      const rows = activeTab.querySelectorAll(".product__tr");
2048      for (let i = 0; i < 4 && i < rows.length; i++) {
2049        totalHeight += rows[i].offsetHeight;
2050      }
2051
2052      /*
2053       * Обновляем текст кнопки "Показать больше".
2054       * Если на активной вкладке есть кнопка с классом .product__link--show-more,
2055       * ей присваивается текст "Показать больше".
2056       */
2057      if (activeTab && activeTab.querySelector(".product__link--show-more")) {
2058        activeTab.querySelector(".product__link--show-more").textContent =
2059          "Показать больше";
2060      }
2061
2062      // Выбираем обертку таблицы относительно активного таба
2063      let tableWrapper = activeTab.querySelector(".product__table-wrapper");
2064      // console.log("tableWrapper: " + tableWrapper);
2065
2066      if (tableWrapper) {
2067        /*
2068         * Устанавливаем высоту обертки таблицы.
2069         * Устанавливается высота обертки таблицы равная подсчитанной общей высоте.
2070         */
2071        tableWrapper.style.height = totalHeight + "px";
2072
2073        let btnMore = tableWrapper
2074          .closest(".product__tabs-content-item--active")
2075          .querySelector(".product__link--show-more");
2076
2077        /*
2078         * Добавляем слушателя событий на кнопку "Показать больше".
2079         * При клике на кнопку, проверяется текущий текст кнопки.
2080         * Если он равен "Скрыть", текст меняется на "Показать больше"
2081         * и устанавливается фиксированная высота таблицы, иначе устанавливается автоматическая высота
2082         * и текст меняется на "Скрыть".
2083         */
2084
2085        let showContentFlag = false;
2086
2087        // console.log(btnMore);
2088
2089        btnMore.addEventListener("click", (event) => {
2090          // showContentFlag = true;
2091          console.log(event.target);
2092
2093          if (event.target.textContent === "Скрыть") {
2094            event.target.textContent = "Показать больше";
2095            tableWrapper.style.height = totalHeight + "px";
2096          } else {
2097            tableWrapper.style.height = "auto";
2098            event.target.textContent = "Скрыть";
2099          }
2100        });
2101      }
2102    }
2103
2104    // Устанавливаем таймер, чтобы с задержкой применить высоту таблицы
2105    setTimeout(() => {
2106      // calcTableHeight();
2107      accordionMobileInit();
2108    }, 100);
2109
2110    $('.product__accordion-link--open-all').click(function() {
2111      if($(this).hasClass('opened')) {
2112        $(this).removeClass('opened');
2113      } else {
2114        $(this).addClass('opened');
2115      }
2116    });
2117
2118    // Аккордеон внутри товарного блока
2119    function accordionInit() {
2120      // Ищем контейнер аккордеона с классом "product__accordion"
2121      let accordion = document.querySelector(".product__accordion");
2122
2123      if (accordion) {
2124        // Добавляем обработчик события click на контейнер аккордеона
2125        accordion.addEventListener("click", function (event) {
2126          const targetItem = event.target.closest(".product__accordion-item");
2127          if (targetItem) {
2128            if (
2129              !targetItem.classList.contains("product__accordion-item--active")
2130            ) {
2131              targetItem.classList.add("product__accordion-item--active");
2132            }
2133          }
2134
2135          // Обработчик клика на пункт аккордеона или на иконку "минус"
2136          const targetIcon = event.target.closest(
2137            ".product__accordion-icon.product__accordion-icon--minus"
2138          );
2139          // Если клик на иконку "минус", удаляем класс активности у родительского элемента.
2140          if (targetIcon) {
2141            const parentItem = targetIcon.closest(".product__accordion-item");
2142            if (parentItem) {
2143              parentItem.classList.remove("product__accordion-item--active");
2144            }
2145          }
2146
2147          /*
2148           * Обработчик клика на кнопку "Открыть все/Закрыть все"
2149           * Если нажата кнопка "Открыть все", открываем все пункты аккордеона,
2150           * если нажата кнопка "Закрыть все", закрываем все пункты
2151           */
2152          if (event.target.classList.contains("product__accordion-link--open-all")) {
2153            const isOpeningAll = !event.target.classList.contains("opened");
2154            const accordionItems = accordion.querySelectorAll(
2155              ".product__accordion-item"
2156            );
2157            accordionItems.forEach((item) => {
2158              if (isOpeningAll) {
2159                item.classList.remove("product__accordion-item--active");
2160              } else {
2161                item.classList.add("product__accordion-item--active");
2162              }
2163            });
2164
2165            const openAllButtons = accordion.querySelectorAll(
2166              ".product__accordion-link--open-all"
2167            );
2168            openAllButtons.forEach((button) => {
2169              // Текст кнопки меняется между "Открыть всё" и "Закрыть всё"
2170              button.textContent = isOpeningAll ? "Открыть всё" : "Закрыть всё";
2171              //button.classList.toggle("opened");
2172            });
2173          }
2174        });
2175      }
2176    }
2177
2178    // Функция, вызываемая при загрузке страницы или изменении размеров
2179    function handleResize() {
2180      // Устанавливаем таймер, чтобы с задержкой применить высоту таблицы
2181      setTimeout(() => {
2182        calcTableHeight();
2183        accordionInit();
2184      }, 200);
2185    }
2186
2187    // Слушаем события загрузки страницы, изменения размера окна и изменения ориентации устройства
2188    window.addEventListener("DOMContentLoaded", handleResize);
2189    window.addEventListener("resize", handleResize);
2190    window.addEventListener("orientationchange", handleResize);
2191
2192    // Мобильный аккордеон
2193    function accordionMobileInit() {
2194      const tabs = document.querySelectorAll(
2195        ".product__tabs-content-item-title"
2196      );
2197
2198      tabs.forEach((tab) => {
2199        tab.addEventListener("click", () => {
2200          const parent = tab.parentNode;
2201          const activeTab = document.querySelector(
2202            ".product__tabs-content-item--active"
2203          );
2204
2205          if (parent.classList.contains("product__tabs-content-item--active")) {
2206            parent.classList.remove("product__tabs-content-item--active");
2207          } else {
2208            if (activeTab) {
2209              activeTab.classList.remove("product__tabs-content-item--active");
2210            }
2211            parent.classList.add("product__tabs-content-item--active");
2212          }
2213        });
2214      });
2215    }
2216
2217    
2218  }
2219}
2220
2221// Показ/скрытие контента в правой колонке на мобильных устройствах
2222    document.addEventListener("click", function (event) {
2223      const btn = event.target.closest(".product__text-more");
2224      if (btn) {
2225        const textContainer = btn.closest(".product__col--right");
2226        if (textContainer) {
2227          // Если у .text нет класса text--visible, добавляем его, иначе убираем
2228          textContainer.classList.toggle("product__col--visible");
2229
2230          // Меняем текст кнопки в зависимости от наличия класса text--visible
2231          if (textContainer.classList.contains("product__col--visible")) {
2232            btn.textContent = "Скрыть";
2233          } else {
2234            btn.textContent = "Подробнее";
2235          }
2236        }
2237      }
2238    });
2239
2240function presentationInit() {
2241  const presentation = document.querySelector(".presentation");
2242
2243  if (presentation) {
2244    const videoElement = presentation.querySelector(".presentation__player");
2245    /*
2246     * Создаем новый объект Plyr, который представляет собой кастомизированный HTML5 видеоплеер.
2247     * Указываем некоторые опции для этого плеера, такие как автовоспроизведение, предзагрузка,
2248     * доступные элементы управления, начальная громкость и другие.
2249     * https://github.com/sampotts/plyr/#options
2250     */
2251    const player = new Plyr(videoElement, {
2252      autoplay: false,
2253      preload: "none",
2254      controls: ["play", "progress", "fullscreen", "volume"],
2255      hideControls: true,
2256      volume: 15,
2257      iconUrl: "/local/templates/xcmg2024/img/presentation/plyr.svg",
2258      blankVideo: "/local/templates/xcmg2024/video/blank.mp4",
2259    });
2260
2261    // Получаем массив элементов управления Plyr и добавляем обработчик события click на элемент презентации.
2262    let controls = Array.from(presentation.querySelectorAll(".plyr__control"));
2263
2264    /*
2265     * В обработчике события проверяем, был ли клик выполнен на элементах управления Plyr.
2266     * Если да, останавливаем дальнейшее всплытие события, иначе переключаем проигрывание видео.
2267     */
2268    presentation.addEventListener("click", (event) => {
2269      // Проверяем, был ли клик выполнен на элементах управления Plyr.js
2270      if (controls.some((control) => control.contains(event.target))) {
2271        event.stopPropagation(); // Останавливаем дальнейшее всплытие события
2272        return; // Не предпринимаем никаких действий
2273      } else {
2274        // Если клик был не на элементах управления, то переключаем проигрывание видео
2275        player.togglePlay();
2276      }
2277    });
2278
2279    /*
2280     * Устанавливаем обработчики событий "playing" и "pause" для изменения
2281     * состояния презентации при проигрывании видео.
2282     */
2283    player.on("playing", (event) => {
2284      presentation.classList.add("presentation--active");
2285    });
2286
2287    player.on("pause", (event) => {
2288      presentation.classList.remove("presentation--active");
2289    });
2290
2291    /*
2292     * Добавляем обработчик события scroll на окне, чтобы при прокрутке страницы
2293     * автоматически приостанавливать воспроизведение видео и изменять состояние презентации.
2294     */
2295    window.addEventListener("scroll", function () {
2296      let bounding = videoElement.getBoundingClientRect();
2297
2298      if (bounding.top > window.innerHeight || bounding.bottom < 0) {
2299        player.pause();
2300        presentation.classList.remove("presentation--active");
2301      }
2302    });
2303
2304    // Отключаем элементы управления плеера по умолчанию
2305    player.toggleControls(false);
2306  }
2307}
2308
2309function textInit() {
2310  let text = document.querySelector(".text");
2311
2312  if (text) {
2313    // Шаблон "Новость". Показать/скрыть текстовый контент
2314    const cut = document.querySelector(".text__cut");
2315
2316    if (cut) {
2317      // Функция для проверки, активировать ли класс .text__cut--active
2318      function checkTextCutHeight() {
2319        const textCut = document.querySelector(".text__cut");
2320        const textMoreButton = document.querySelector(".text__more");
2321        const rightColHeight =
2322          document.querySelector(".text__col--right").offsetHeight;
2323
2324        // Устанавливаем высоту правой колонки для .text__cut
2325        textCut.style.height = rightColHeight + "px";
2326
2327        let totalChildrenHeight = 0;
2328
2329        // Получаем дочерние элементы и вычисляем их суммарную высоту с отступами
2330        const children = textCut.children;
2331        for (let i = 0; i < children.length; i++) {
2332          totalChildrenHeight += children[i].getBoundingClientRect().height;
2333        }
2334
2335        // Добавляем или удаляем класс .text__cut--active в зависимости от вычислений
2336        if (totalChildrenHeight > rightColHeight) {
2337          textCut.classList.remove("text__cut--active");
2338          textMoreButton.classList.remove("text__more--hidden");
2339        } else {
2340          textCut.classList.add("text__cut--active");
2341          textMoreButton.classList.add("text__more--hidden");
2342        }
2343      }
2344
2345      // Функция, вызываемая при загрузке страницы или изменении размеров
2346      function handleResize() {
2347        checkTextCutHeight();
2348      }
2349
2350      // Слушаем события загрузки страницы, изменения размера окна и изменения ориентации устройства
2351      window.addEventListener("DOMContentLoaded", handleResize);
2352      window.addEventListener("resize", handleResize);
2353      window.addEventListener("orientationchange", handleResize);
2354
2355      // Получаем ссылки на элементы DOM
2356      const textCutElement = document.querySelector(".text__cut");
2357      const textMoreButton = document.querySelector(".text__more");
2358
2359      // Обработчик клика по кнопке
2360      const handleMoreButtonClick = () => {
2361        // Переключаем класс у элемента .text__cut
2362        textCutElement.classList.toggle("text__cut--active");
2363
2364        // Переключаем текст кнопки в зависимости от наличия класса text__cut--active
2365        if (textCutElement.classList.contains("text__cut--active")) {
2366          textMoreButton.textContent = "Скрыть";
2367        } else {
2368          textMoreButton.textContent = "Показать больше";
2369        }
2370      };
2371
2372      // Назначаем обработчик клика на кнопку
2373      textMoreButton.addEventListener("click", handleMoreButtonClick);
2374    }
2375
2376    // Шаблон "Новость". Показать/скрыть текстовый контент
2377    // 1) Находим элемент .text--simple
2378    const simpleText = document.querySelector(".text--simple");
2379
2380    // Проверяем, существует ли такой элемент
2381    if (simpleText) {
2382      // Находим первый h1 внутри .text--simple
2383      const h1Element = simpleText.querySelector("h1");
2384
2385      // Проверяем, существует ли такой h1
2386      if (h1Element) {
2387        // Находим следующие элементы на одном уровне после h1
2388        const nextElements = h1Element.nextElementSibling;
2389
2390        // Проверяем, есть ли следующие элементы
2391        if (nextElements) {
2392          // Если количество следующих элементов больше одного, добавляем класс .text__read-more--active
2393          const siblings = Array.from(nextElements.parentNode.children).filter(
2394            (element) => element !== h1Element
2395          );
2396          if (siblings.length > 1) {
2397            const readMoreButton = simpleText.querySelector(".text__read-more");
2398            if (readMoreButton) {
2399              readMoreButton.classList.add("text__read-more--active");
2400            }
2401          }
2402        }
2403      }
2404    }
2405
2406    // 2) Обработчик клика на .text__read-more
2407    document.addEventListener("click", function (event) {
2408      const readMoreButton = event.target.closest(".text__read-more");
2409      if (readMoreButton) {
2410        const textContainer = readMoreButton.closest(".text");
2411        if (textContainer) {
2412          // Если у .text нет класса text--visible, добавляем его, иначе убираем
2413          textContainer.classList.toggle("text--visible");
2414
2415          // Меняем текст кнопки в зависимости от наличия класса text--visible
2416          if (textContainer.classList.contains("text--visible")) {
2417            readMoreButton.textContent = "Скрыть";
2418          } else {
2419            readMoreButton.textContent = "Подробнее";
2420          }
2421        }
2422      }
2423    });
2424  }
2425}
2426
2427function videoInit() {
2428  const video = document.querySelector(".video");
2429
2430  if (video) {
2431    const videoElement = video.querySelector(".video__player");
2432    /*
2433     * Создаем новый объект Plyr, который представляет собой кастомизированный HTML5 видеоплеер.
2434     * Указываем некоторые опции для этого плеера, такие как автовоспроизведение, предзагрузка,
2435     * доступные элементы управления, начальная громкость и другие.
2436     * https://github.com/sampotts/plyr/#options
2437     */
2438    const player = new Plyr(videoElement, {
2439      autoplay: false,
2440      preload: "none",
2441      controls: ["play", "progress", "fullscreen", "volume"],
2442      hideControls: true,
2443      volume: 15,
2444      iconUrl: "/local/templates/xcmg2024/img/video/plyr.svg",
2445      blankVideo: "/local/templates/xcmg2024/video/blank.mp4",
2446    });
2447
2448    // Получаем массив элементов управления Plyr и добавляем обработчик события click на элемент презентации.
2449    let controls = Array.from(video.querySelectorAll(".plyr__control"));
2450
2451    /*
2452     * В обработчике события проверяем, был ли клик выполнен на элементах управления Plyr.
2453     * Если да, останавливаем дальнейшее всплытие события, иначе переключаем проигрывание видео.
2454     */
2455    video.addEventListener("click", (event) => {
2456      // Проверяем, был ли клик выполнен на элементах управления Plyr.js
2457      if (controls.some((control) => control.contains(event.target))) {
2458        event.stopPropagation(); // Останавливаем дальнейшее всплытие события
2459        return; // Не предпринимаем никаких действий
2460      } else {
2461        // Если клик был не на элементах управления, то переключаем проигрывание видео
2462        player.togglePlay();
2463      }
2464    });
2465
2466    /*
2467     * Устанавливаем обработчики событий "playing" и "pause" для изменения
2468     * состояния презентации при проигрывании видео.
2469     */
2470    player.on("playing", (event) => {
2471      video.classList.add("video--active");
2472    });
2473
2474    player.on("pause", (event) => {
2475      video.classList.remove("video--active");
2476    });
2477
2478    /*
2479     * Добавляем обработчик события scroll на окне, чтобы при прокрутке страницы
2480     * автоматически приостанавливать воспроизведение видео и изменять состояние презентации.
2481     */
2482    window.addEventListener("scroll", function () {
2483      let bounding = videoElement.getBoundingClientRect();
2484
2485      if (bounding.top > window.innerHeight || bounding.bottom < 0) {
2486        player.pause();
2487        video.classList.remove("video--active");
2488      }
2489    });
2490
2491    // Отключаем элементы управления плеера по умолчанию
2492    player.toggleControls(false);
2493  }
2494}
2495
2496function specsDealersInit() {
2497    let arrspecs = [];
2498    if(typeof arraySpecs !== "undefined")  { arrspecs = arraySpecs; }
2499    let arrcities = arrayCities;
2500    
2501    // Находим все элементы формы ввода текста на странице
2502    let textSpecsInputs = document.querySelectorAll(".specs__filters-item-text-input");
2503    // Переменная для сохранения введенного текста
2504    let inputSpecText = "";
2505    // Переменная для сохранения выбранной категории
2506    let spec_category = "";
2507	console.log(textSpecsInputs);
2508    // Устанавливаем обработчик события ввода текста для каждого элемента формы
2509    textSpecsInputs.forEach((textInput) => {
2510      textInput.addEventListener("input", function () {
2511        // Сохраняем введенный текст в нижнем регистре
2512        inputSpecText = this.value.toLowerCase();
2513
2514        // Получаем категорию из ближайшего родительского элемента
2515        spec_category = this.dataset.category;
2516
2517        btn = this.closest(".specs__filters-item-form").querySelector(
2518          ".specs__filters-item-btn"
2519        );
2520
2521        // Находим родителя элемента ".specs__filters-item-text-input-wrapper"
2522        let parentWrapper = this.closest(
2523          ".specs__filters-item-text-input-wrapper"
2524        );
2525        // Находим элемент ".specs__filters-item-text-input-close" внутри родителя
2526        let inputSpecClose = parentWrapper.querySelector(
2527          ".specs__filters-item-text-input-close"
2528        );
2529
2530        // Добавляем класс "specs__filters-item-text-input-close--active" элементу при вводе текста
2531        if (inputSpecText !== "") {
2532          inputSpecClose.classList.add(
2533            "specs__filters-item-text-input-close--active"
2534          );
2535        } else {
2536          // Удаляем класс "specs__filters-item-text-input-close--active" если в инпуте нет символов
2537          inputSpecClose.classList.remove(
2538            "specs__filters-item-text-input-close--active"
2539          );
2540        }
2541		
2542		// Фильтруем города на основе введенного текста и категории
2543        //let filteredCities = getFilteredCities3(inputSpecText, spec_category);
2544
2545
2546        // Находим все элементы с классом .specs__filters-list
2547        const filterLists = document.querySelectorAll(".specs__filters-list");
2548
2549
2550
2551        // Для каждого найденного элемента добавляем обработчик события на клик
2552        filterLists.forEach((filterList) => {
2553          filterList.addEventListener("click", function (event) {
2554            // Находим родителя элемента с классом .specs__filters-item-form
2555            const formItem = event.target.closest(".specs__filters-item-form");
2556
2557            // Если родитель был найден, ищем дочерний элемент с классом .specs__filters-item-text-input
2558            if (formItem) {
2559              const textInput = formItem.querySelector(
2560                ".specs__filters-item-text-input"
2561              );
2562                const textInputSpec = formItem.querySelector(
2563                    "#input-dealer-spec"
2564                );
2565
2566              // Если элемент найден, выводим его значение
2567
2568                console.log('filteredCities'+filteredCities)
2569              if (textInput && !textInputSpec) {
2570                  setTimeout(function () {
2571                    // Создадим флаг, который показывает, было ли хоть одно совпадение
2572                    let cityMatched = false;
2573
2574                    // Пройдем по каждому элементу в filteredCities
2575                    filteredCities.forEach((obj) => {
2576                      // Проверим, есть ли у текущего объекта поле city
2577                      if (obj) {
2578                        let city = obj.toLowerCase();
2579                        console.log(city+' -- '+textInput.value.toLowerCase())
2580                        if (city === textInput.value.toLowerCase()) {
2581                          // Если хоть одно значение совпадает, установим флаг в true
2582                          cityMatched = true;
2583                        }
2584                      }
2585                    });
2586                    console.log(cityMatched)
2587                    // После прохода по всем значениям, проверим флаг и включим/выключим кнопку "Найти"
2588                    if (cityMatched) {
2589                      btn.disabled = false;
2590                    } else {
2591                      btn.disabled = true;
2592                    }
2593                  }, 350);
2594              } else if(textInput && textInputSpec) {
2595                    setTimeout(function () {
2596                        // Создадим флаг, который показывает, было ли хоть одно совпадение
2597                        let cityMatched = false;
2598
2599                        // Пройдем по каждому элементу в filteredCities
2600                        filteredCities.forEach((obj) => {
2601                            // Проверим, есть ли у текущего объекта поле city
2602                            if (obj) {
2603                                let city = obj.toLowerCase();
2604                                console.log(city+' -- '+textInputSpec.value.toLowerCase())
2605                                if (city === textInputSpec.value.toLowerCase()) {
2606                                    // Если хоть одно значение совпадает, установим флаг в true
2607                                    cityMatched = true;
2608                                }
2609                            }
2610                        });
2611                        console.log(cityMatched)
2612                        // После прохода по всем значениям, проверим флаг и включим/выключим кнопку "Найти"
2613                        if (cityMatched) {
2614                            btn.disabled = false;
2615                        } else {
2616                            btn.disabled = true;
2617                        }
2618                    }, 350);
2619                }
2620            }
2621          });
2622        });
2623
2624        // Находим список подсказок для текущего элемента ввода
2625        let suggestionsList = this.nextElementSibling;
2626
2627        // Очищаем список подсказок
2628        suggestionsList.innerHTML = "";
2629
2630        // Если есть подходящие города и введен текст
2631        if (filteredCities.length > 0 && inputSpecText !== "") {
2632          // Показываем список подсказок
2633          suggestionsList.classList.add("specs__filters-list--active");
2634          // Добавляем подходящие города в список подсказок
2635          filteredCities.forEach(function (city) {
2636            suggestionsList.innerHTML +=
2637              "<li class='specs__filters-list-item'>" +
2638              city +
2639              "</li>";
2640          });
2641          const items = suggestionsList.querySelectorAll(
2642            ".specs__filters-list-item"
2643          ); // Найти все элементы списка
2644
2645          if (items.length > 7) {
2646            const totalItems = Math.min(items.length, 7); // Подсчитываем не более 7 элементов
2647
2648            let totalHeight = 0;
2649            for (let i = 0; i < totalItems; i++) {
2650              totalHeight += items[i].clientHeight;
2651            }
2652
2653            suggestionsList.style.height = totalHeight + "px"; // Применяем сумму высот к suggestionsList
2654          }
2655        } else {
2656          // Скрываем список подсказок, если нет подходящих городов
2657          suggestionsList.classList.remove("specs__filters-list--active");
2658        }
2659	  });
2660	});
2661    
2662    // Устанавливаем обработчик события клика на элементах ".specs__filters-item-text-input-close"
2663    document.querySelectorAll(".specs__filters-item").forEach((item) => {
2664
2665      item.addEventListener("click", function (event) {
2666        if (
2667          event.target.classList.contains("specs__filters-item-text-input-close")
2668        ) {
2669          // Находим родителя элемента ".specs__filters-item-text-input-close"
2670          let parentWrapper = event.target.closest(
2671            ".specs__filters-item-text-input-wrapper"
2672          );
2673          // Находим родителя элемента ".specs__filters-item-form"
2674          let form = event.target.closest(".specs__filters-item-form");
2675          // Находим кнопку
2676          let btn = form.querySelector(".specs__filters-item-btn");
2677          // Выключаем кнопку "Найти"
2678          btn.disabled = true;
2679
2680          // Очищаем инпут
2681          parentWrapper.querySelector(".specs__filters-item-text-input").value =
2682            "";
2683
2684          // Удаляем пункты списка
2685          parentWrapper.querySelector(".specs__filters-list").innerHTML = "";
2686
2687          // Скрываем список, удалив активный класс
2688          parentWrapper
2689            .querySelector(".specs__filters-list")
2690            .classList.remove("specs__filters-list--active");
2691
2692          // Удаляем класс "specs__filters-item-text-input-close--active" при сбросе значения
2693          event.target.classList.remove(
2694            "specs__filters-item-text-input-close--active"
2695          );
2696        }
2697      });
2698    });
2699	
2700	// Функция для фильтрации городов на основе введенного текста и категории
2701    function getFilteredCities3(input, category) {
2702      let cities;
2703      if(category == 'dealer-city') cities = $('#citiesdata2').val().split(',');
2704      else if(category == 'dealer-spec') cities = $('#specsdata2').val().split(',');
2705
2706      return cities.filter(function (сity) {
2707        let destinationText = сity.toLowerCase();
2708        let matched = true;
2709
2710        // Проверяем каждый символ во введенном тексте на соответствие
2711        for (let i = 0; i < input.length; i++) {
2712          if (destinationText[i] !== input[i]) {
2713            matched = false;
2714            break;
2715          }
2716        }
2717
2718        // Возвращаем только совпадающие города с выбранной категорией
2719        return matched;
2720      });
2721    }
2722    
2723    // Устанавливаем обработчик события клика на элементах списка подсказок
2724    document.querySelectorAll(".specs__filters-item").forEach((item) => {
2725      item.addEventListener("click", function (event) {
2726        if (event.target.classList.contains("specs__filters-list-item")) {
2727          // Получаем выбранный город
2728          let selectedCity = event.target.textContent;
2729
2730          // Заполняем текстовое поле выбранным городом
2731          console.log(event.target);
2732          event.target.closest(".specs__filters-item-text-input-wrapper").querySelector('.specs__filters-item-text-input').value = selectedCity;
2733
2734          // Скрываем список подсказок и очищаем его
2735          event.target
2736            .closest(".specs__filters-list")
2737            .classList.remove("specs__filters-list--active");
2738          event.target.closest(".specs__filters-list").innerHTML = "";
2739        }
2740      });
2741    });
2742}

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.