Commit 694201330f9d1eaf2089ff2d570ba2e139c484ba

Authored by Андрей Ларионов
1 parent 357df98e45
Exists in master

Добавление координат в карточку офиса и работа с картой

Showing 10 changed files with 1716 additions and 1523 deletions Side-by-side Diff

app/Http/Controllers/MainController.php
... ... @@ -679,8 +679,11 @@ class MainController extends Controller
679 679 * Карта объектов
680 680 */
681 681 public function MapsObj(Request $request) {
  682 + //$houses = House::with('areas');
  683 + //$houses = $houses->orderBy('id')->get();
  684 + $areas = Area::query()->orderBy('id')->get();
682 685  
683   - return view('mapsobj');
  686 + return view('mapsobj', compact('areas'));
684 687 }
685 688  
686 689 /*
database/migrations/2023_03_01_073202_create_houses_table.php
... ... @@ -56,10 +56,11 @@ return new class extends Migration
56 56 $table->text('description_house')->nullable(true);
57 57 $table->string('map_coord', 255)->default('');
58 58 $table->boolean('best')->default('0');
59   - $table->string('description_2')->default('');
60   - $table->string('sos_obj')->default('Рабочая');
61   - $table->string('type_plan')->default('Открытая');
62   -
  59 + $table->string('description_2', 255)->default('');
  60 + $table->string('sos_obj', 255)->default('Рабочая');
  61 + $table->string('type_plan', 255)->default('Открытая');
  62 + $table->string('coord_x', 50)->default('0');
  63 + $table->string('coord_y', 50)->default('0');
63 64 $table->timestamps();
64 65 });
65 66 }
public/js/main.js
Changes suppressed. Click to show
... ... @@ -1,1507 +0,0 @@
1   -// управляющий класс App с методом init(), в котором собраны все используемые методы с комментариями о том, что конкретно делает каждый метод
2   -
3   -class App {
4   -
5   - constructor() {
6   - this.patternPhone = /^(\+7|7|8)?[\s\-]?\(?[489][0-9]{2}\)?[\s\-]?[0-9]{3}[\s\-]?[0-9]{2}[\s\-]?[0-9]{2}$/; // рег. выражение для поля 'телефон';
7   - this.patternEmail = /^[a-zA-Z0-9._%+-\.]+@[a-z0-9.-]+\.[a-z]{2,}$/i; // рег. выражение для поля 'электронная почта';
8   - }
9   -
10   - init() {
11   -
12   - console.log('init');
13   -
14   - this.stickyHeader(); // липкий хедер;
15   - this.controlBurgerMenu(); // бургер-меню;
16   - this.smoothScroll(); // плавный скролл к якорю (smooth scroll);
17   - this.scrollUp(); // кнопка наверх;
18   - this.addToFavorites(); // добавить в избранное (звёздочка);
19   - this.initTypicalSlider(); // типовые слайдеры;
20   - this.initPartnerslSlider(); // слайдер с партнёрами;
21   - this.controlFilters(); // фильтры на главном экране;
22   - this.controlPopups(); // открытие/закрытие поп-апов;
23   - this.controlContactUsPopup(); // открытие/закрытие поп-апа 'обратный звонок';
24   -
25   - this.sendForm('.js_popup_feedback_form', '[data-popup="success"]'); // отправка формы в поп-апе обратной связи;
26   - this.sendForm('.js_popup_viewing_form', '[data-popup="success"]'); // отправка формы в поп-апе 'записаться на просмотр';
27   - this.sendForm('.js_footer_feedback_form', '[data-popup="success"]'); // отправка формы в футере;
28   - this.sendForm('.js_contacts_form', '.js_contacts_success'); // отправка формы на странице контакты;
29   - this.sendForm('.js_popup_sending_form_', '[data-popup="success"]');
30   - //this.sendOffer(); //отправка предложения по e-mail;
31   -
32   - this.setGeneralMap(); // карта на странице карт;
33   - this.setComplexMap('complex-map', [55.726591050908745, 37.57244549999999], 'ЖК Садовые кварталы'); // карта на странице 'ЖК';
34   - this.setComplexMap('offer-map', [55.70851106903402, 37.65864349999999], 'Аренда торгового помещения 321,6 м2'); // карта на странице 'Предложение';
35   - this.setCatalogSorts(); // сортировка на странице 'каталог';
36   - this.initIntroSlider(); // слайдер на странице жк и на странице предложения;
37   - this.setTabs('.js_offer_side_tab', '.js_offer_side_item'); // табы с планами объекат и этажа на странице предложения;
38   - this.setTabs('.js_offer_side_popup_tab', '.js_offer_side_popup_item'); // табы с планами объекат и этажа в поп-апе на странице предложения;
39   - this.sontrolOfferSidePopup(); // логика открытия нужного таба при открытии поп-апа с планами объекат и этажа на странице предложения;
40   - this.setCustomGallery(); // галлерея;
41   - this.setCookies() // куки;
42   - this.setFooterSpoilers() // аккордеон в футере;
43   -
44   - }
45   -
46   - // фиксация <body>
47   - fixBodyPosition() {
48   -
49   - setTimeout(function () {
50   - // ставим необходимую задержку, чтобы не было «конфликта» в случае, если функция фиксации вызывается сразу после расфиксации (расфиксация отменяет действия расфиксации из-за одновременного действия)
51   -
52   - if (!document.body.hasAttribute('data-body-scroll-fix')) {
53   -
54   - // получаем позицию прокрутки
55   - let scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
56   -
57   - // ставим нужные стили
58   - document.body.setAttribute('data-body-scroll-fix', scrollPosition); // Cтавим атрибут со значением прокрутки
59   - document.body.style.overflow = 'hidden';
60   - document.body.style.position = 'fixed';
61   - document.body.style.top = '-' + scrollPosition + 'px';
62   - document.body.style.left = '0';
63   - document.body.style.width = '100%';
64   -
65   - if (window.innerWidth >= 1200) {
66   - document.body.style.paddingRight = '8px';
67   - }
68   - }
69   -
70   - }, 15); // можно задержку ещё меньше, но работает хорошо именно с этим значением на всех устройствах и браузерах
71   -
72   - }
73   -
74   -
75   - // расфиксация <body>
76   - unfixBodyPosition() {
77   -
78   - if (document.body.hasAttribute('data-body-scroll-fix')) {
79   -
80   - // получаем позицию прокрутки из атрибута
81   - let scrollPosition = document.body.getAttribute('data-body-scroll-fix');
82   -
83   - // удаляем атрибут
84   - document.body.removeAttribute('data-body-scroll-fix');
85   -
86   - // удаляем ненужные стили
87   - document.body.style.overflow = '';
88   - document.body.style.position = '';
89   - document.body.style.top = '';
90   - document.body.style.left = '';
91   - document.body.style.width = '';
92   - document.body.style.paddingRight = '';
93   -
94   - // прокручиваем страницу на полученное из атрибута значение
95   - window.scroll(0, scrollPosition);
96   -
97   - }
98   -
99   - }
100   -
101   -
102   - // бургер-меню
103   - controlBurgerMenu() {
104   -
105   - const headerBurger = document.querySelector('.js_header_burger');
106   -
107   - if (headerBurger) {
108   -
109   - const menu = document.querySelector('.js_menu');
110   - const menuClose = menu.querySelector('.js_menu_close');
111   -
112   - headerBurger.addEventListener('click', () => {
113   - menu.classList.add('active');
114   - this.fixBodyPosition();
115   - });
116   -
117   - menu.addEventListener('click', (e) => {
118   -
119   - if (e.target == menu) {
120   - menu.classList.remove('active');
121   - this.unfixBodyPosition();
122   - }
123   -
124   - });
125   -
126   - menuClose.addEventListener('click', () => {
127   - menu.classList.remove('active');
128   - this.unfixBodyPosition();
129   - });
130   -
131   - }
132   -
133   - }
134   -
135   -
136   - // липкий хедер
137   - stickyHeader() {
138   -
139   - const header = document.querySelector('.js_header');
140   -
141   - if (header) {
142   -
143   - window.addEventListener('scroll', () => {
144   -
145   - if (window.scrollY > 200) {
146   - header.classList.add('fixed');
147   - } else {
148   - header.classList.remove('fixed');
149   - }
150   -
151   - });
152   -
153   - };
154   -
155   - }
156   -
157   -
158   - // плавный скролл к якорю (smooth scroll)
159   - smoothScroll() {
160   -
161   - const smoothLinks = document.querySelectorAll('.js_smooth_link');
162   -
163   - if (smoothLinks.length) {
164   -
165   - smoothLinks.forEach(link => {
166   -
167   - link.addEventListener('click', function (e) {
168   -
169   - e.preventDefault();
170   -
171   - let href = this.getAttribute('href').substring(1);
172   -
173   - const scrollTarget = document.getElementById(href);
174   -
175   - // const topOffset = document.querySelector('.header').offsetHeight;
176   - const topOffset = 0; // если не нужен отступ сверху
177   - const elementPosition = scrollTarget.getBoundingClientRect().top;
178   - const offsetPosition = elementPosition - topOffset;
179   -
180   - window.scrollBy({
181   - top: offsetPosition,
182   - behavior: 'smooth'
183   - });
184   -
185   - });
186   -
187   - });
188   -
189   - }
190   -
191   - }
192   -
193   -
194   - // кнопка наверх
195   - scrollUp() {
196   -
197   - const toTopBtn = document.querySelector('.js_btn_up');
198   -
199   - if (toTopBtn) {
200   -
201   - toTopBtn.addEventListener('click', function () {
202   -
203   - window.scrollTo({
204   - top: 0,
205   - behavior: 'smooth'
206   - });
207   -
208   - });
209   -
210   - }
211   -
212   - }
213   -
214   -
215   - // добавить в избранное (звёздочка)
216   - addToFavorites() {
217   -
218   - const cardFavorites = document.querySelectorAll('.js_card_favorites');
219   -
220   - if (cardFavorites.length) {
221   -
222   - cardFavorites.forEach(item => {
223   -
224   - item.addEventListener('click', (e) => {
225   - e.preventDefault();
226   - item.classList.toggle('active');
227   - });
228   -
229   - });
230   -
231   - }
232   -
233   - }
234   -
235   -
236   - // типовые слайдеры
237   - initTypicalSlider() {
238   -
239   - const slidersWraps = document.querySelectorAll('.slider__wrap');
240   -
241   - if (slidersWraps.length) {
242   -
243   - slidersWraps.forEach(wrap => {
244   -
245   - const slider = wrap.querySelector('.swiper');
246   - const prev = wrap.querySelector('.swiper-button-prev');
247   - const next = wrap.querySelector('.swiper-button-next');
248   - const pagination = wrap.querySelector('.swiper-pagination');
249   -
250   - let swiper1 = new Swiper(slider, {
251   - navigation: {
252   - nextEl: next,
253   - prevEl: prev,
254   - },
255   - pagination: {
256   - el: pagination,
257   - clickable: true,
258   - },
259   - slidesPerView: 1,
260   - spaceBetween: 20,
261   - observer: true,
262   - observeParents: true,
263   - observeSlideChildren: true,
264   - breakpoints: {
265   - 480: {
266   - slidesPerView: 1.5,
267   - },
268   - 640: {
269   - slidesPerView: 2,
270   - },
271   - 780: {
272   - slidesPerView: 2.5,
273   - },
274   - 920: {
275   - slidesPerView: 3,
276   - },
277   - 1024: {
278   - slidesPerView: 3.4
279   - },
280   - 1200: {
281   - slidesPerView: 4,
282   - }
283   - }
284   - });
285   -
286   - });
287   -
288   - }
289   -
290   - }
291   -
292   -
293   - // метод, делающий число удобночитаемым (добавляет пробел справа через каждые 3 цифры)
294   - prettify(num) {
295   - const withoutSpace = num.replace(/[^\d]/g, ''); //убирает все символы;
296   - return withoutSpace.replace(/(?!^)(?=(?:\d{3})+(?:\.|$))/gm, ' '); //ставит пробелы;
297   - }
298   -
299   -
300   - // фильтры на главном экране
301   - controlFilters() {
302   -
303   - const heroFilters = document.querySelectorAll('.js_hero_filter');
304   - const heroSearchBtns = document.querySelectorAll('.js_hero_search_btn');
305   -
306   - if (heroFilters.length) {
307   -
308   - heroFilters.forEach(filter => {
309   -
310   - const heroFilterInput = filter.querySelector('.js_hero_filter_input');
311   - const heroFilterCurrent = filter.querySelector('.js_hero_filter_current');
312   - const heroFilterItems = filter.querySelectorAll('.hero-filter__item');
313   - const heroFilterFields = filter.querySelectorAll('.js_hero_filter_field');
314   - const heroFilterFrom = filter.querySelector('.js_hero_filter_from');
315   - const heroFilterTo = filter.querySelector('.js_hero_filter_to');
316   - const heroFilterReset = filter.querySelector('.js_hero_filter_reset');
317   -
318   - heroFilterCurrent.addEventListener('click', () => {
319   -
320   - if (filter.classList.contains('active')) {
321   -
322   - filter.classList.remove('active');
323   -
324   - heroSearchBtns.forEach(btn => {
325   - btn.disabled = false;
326   - });
327   -
328   - } else {
329   -
330   - heroFilters.forEach(filter => {
331   - filter.classList.remove('active');
332   - });
333   -
334   - filter.classList.add('active');
335   -
336   - heroSearchBtns.forEach(btn => {
337   - btn.disabled = true;
338   - });
339   -
340   - }
341   -
342   - });
343   -
344   - if (heroFilterItems.length) {
345   -
346   - heroFilterItems.forEach(item => {
347   -
348   - item.addEventListener('click', () => {
349   -
350   - heroFilterCurrent.textContent = item.textContent;
351   - heroFilterInput.value = item.dataset.val;
352   - filter.classList.remove('active');
353   -
354   - heroSearchBtns.forEach(btn => {
355   - btn.disabled = false;
356   - });
357   -
358   - });
359   -
360   - });
361   -
362   - }
363   -
364   - if (heroFilterFields.length) {
365   -
366   - const heroFilterMin = heroFilterFrom.dataset.min;
367   - const heroFilterMax = heroFilterTo.dataset.max;
368   -
369   - let heroFilterFromVal;
370   - let heroFilterToVal;
371   -
372   - heroFilterFields.forEach(field => {
373   -
374   - field.addEventListener('input', () => {
375   -
376   - field.value = this.prettify(field.value);
377   -
378   - heroFilterReset.classList.remove('active');
379   -
380   - heroFilterFields.forEach(field => {
381   -
382   - if (field.value != "") {
383   - heroFilterReset.classList.add('active');
384   - }
385   -
386   - });
387   -
388   - });
389   -
390   - });
391   -
392   - heroFilterFrom.addEventListener('change', () => {
393   -
394   - heroFilterFromVal = +heroFilterFrom.value.replace(/\s/g, '');
395   - heroFilterToVal = +heroFilterTo.value.replace(/\s/g, '');
396   -
397   - if (heroFilterToVal != '' && heroFilterFromVal > heroFilterToVal) {
398   -
399   - heroFilterFrom.value = heroFilterTo.value;
400   -
401   - } else if (heroFilterFromVal < +heroFilterMin) {
402   -
403   - heroFilterFrom.value = this.prettify(heroFilterMin);
404   -
405   - } else if (heroFilterFromVal > +heroFilterMax) {
406   -
407   - heroFilterFrom.value = this.prettify(heroFilterMax);
408   -
409   - }
410   -
411   - });
412   -
413   - heroFilterTo.addEventListener('change', () => {
414   -
415   - heroFilterFromVal = +heroFilterFrom.value.replace(/\s/g, '');
416   - heroFilterToVal = +heroFilterTo.value.replace(/\s/g, '');
417   -
418   - if (heroFilterFromVal != '' && heroFilterToVal < heroFilterFromVal) {
419   -
420   - heroFilterTo.value = heroFilterFrom.value;
421   -
422   - } else if (heroFilterToVal < +heroFilterMin) {
423   -
424   - heroFilterTo.value = this.prettify(heroFilterMax);
425   -
426   - } else if (heroFilterToVal > +heroFilterMax) {
427   -
428   - heroFilterTo.value = this.prettify(heroFilterMax);
429   -
430   - }
431   -
432   - });
433   -
434   - heroFilterReset.addEventListener('click', () => {
435   -
436   - heroFilterFields.forEach(field => {
437   - field.value = '';
438   - });
439   -
440   - heroFilterReset.classList.remove('active');
441   -
442   - });
443   - }
444   -
445   - });
446   -
447   - document.addEventListener('click', (e) => {
448   -
449   - if (!e.target.closest('.js_hero_filter_dropdown') && !e.target.closest('.js_hero_filter_current')) {
450   -
451   - heroFilters.forEach(filter => {
452   - filter.classList.remove('active');
453   - });
454   -
455   - heroSearchBtns.forEach(btn => {
456   - btn.disabled = false;
457   - });
458   -
459   - }
460   -
461   - });
462   -
463   - }
464   -
465   - }
466   -
467   -
468   - // открытие/закрытие типовых поп-апов
469   - controlPopups() {
470   -
471   - const popupShowBtns = document.querySelectorAll('[data-btn]');
472   - const popups = document.querySelectorAll('[data-popup]');
473   -
474   - if (popupShowBtns.length) {
475   -
476   - popupShowBtns.forEach(btn => {
477   -
478   - btn.addEventListener('click', (e) => {
479   -
480   - e.preventDefault();
481   -
482   - popups.forEach(popup => {
483   -
484   - popup.classList.remove('active'); // если какойто поп-ап открыт, то закрываем его;
485   - this.unfixBodyPosition();
486   -
487   - if (btn.dataset.btn == popup.dataset.popup) {
488   - popup.classList.add('active');
489   - this.fixBodyPosition();
490   - }
491   -
492   - });
493   -
494   -
495   - });
496   -
497   - });
498   -
499   - popups.forEach(popup => {
500   -
501   - const popupCloseBtns = popup.querySelectorAll('.js_popup_close');
502   -
503   - popupCloseBtns.forEach(btn => {
504   -
505   - btn.addEventListener('click', (e) => {
506   - e.preventDefault();
507   - popup.classList.remove('active');
508   - this.unfixBodyPosition();
509   - });
510   -
511   - });
512   -
513   - popup.addEventListener('click', (e) => {
514   -
515   - if (e.target == popup) {
516   -
517   - popup.classList.remove('active');
518   - this.unfixBodyPosition();
519   - }
520   -
521   - });
522   -
523   - });
524   -
525   - }
526   - }
527   -
528   -
529   - // открытие/закрытие поп-апа 'обратный звонок'
530   - controlContactUsPopup() {
531   -
532   - const contactUsBtn = document.querySelector('.js_btn_contact_us');
533   - const contactUsPopup = document.querySelector('.js_contact_us');
534   -
535   - if (contactUsPopup) {
536   -
537   - const contactUsPopupCloseBtns = contactUsPopup.querySelectorAll('.js_contact_us_close');
538   -
539   - contactUsBtn.addEventListener('click', (e) => {
540   -
541   - e.preventDefault();
542   -
543   - if (contactUsPopup.classList.contains('active')) {
544   - contactUsPopup.classList.remove('active');
545   - } else {
546   - contactUsPopup.classList.add('active');
547   - }
548   -
549   - });
550   -
551   - contactUsPopupCloseBtns.forEach(btn => {
552   - btn.addEventListener('click', () => {
553   - contactUsPopup.classList.remove('active');
554   - });
555   - });
556   -
557   -
558   - document.addEventListener('click', (e) => {
559   -
560   - if (!e.target.closest('.js_contact_us') && !e.target.closest('.js_btn_contact_us')) {
561   - contactUsPopup.classList.remove('active');
562   - }
563   -
564   - });
565   -
566   - }
567   -
568   - }
569   -
570   -
571   - // валидатор форм
572   - validateForm(input) {
573   -
574   - // функция добавления ошибки
575   - const createError = (text) => {
576   -
577   - input.classList.add('error');
578   - input.classList.remove('no-error');
579   -
580   - if (input.closest('label').querySelector('span.error')) {
581   - input.closest('label').querySelector('span.error').remove();
582   - input.closest('label').insertAdjacentHTML('beforeend', `<span class="error">${text}</span>`);
583   - } else {
584   - input.closest('label').insertAdjacentHTML('beforeend', `<span class="error">${text}</span>`);
585   - }
586   -
587   - }
588   -
589   - // функция удаления ошибки
590   - const removeError = () => {
591   -
592   - input.classList.remove('error');
593   - input.classList.add('no-error');
594   -
595   - if (input.closest('label').querySelector('span.error')) {
596   - input.closest('label').querySelector('span.error').remove();
597   - }
598   -
599   - }
600   -
601   - // проверяем на правильность заполнения поля 'Телефон'
602   - if (input.classList.contains('js_input_phone') && input.value == "") {
603   - createError('Заполните, пожалуйста, поле');
604   - } else if (input.classList.contains('js_input_phone') && input.value.search(this.patternPhone) == 0) {
605   - removeError();
606   - } else if (input.classList.contains('js_input_phone')) {
607   - createError('Укажите, пожалуйста, корректный телефон');
608   - }
609   -
610   - // проверяем правильность заполнения поля 'Электронная почта'
611   - if (input.classList.contains('js_input_email') && input.value == "") {
612   - createError('Заполните, пожалуйста, поле');
613   - } else if (input.classList.contains('js_input_email') && input.value.search(this.patternEmail) == 0) {
614   - removeError();
615   - } else if (input.classList.contains('js_input_email')) {
616   - createError('Укажите, пожалуйста, корректный e-mail');
617   - }
618   -
619   - }
620   -
621   -
622   - // отправка форм
623   - sendForm(formEl, success) {
624   -
625   - const form = document.querySelector(formEl);
626   -
627   - if (form) {
628   -
629   - form.addEventListener('submit', async (e) => {
630   -
631   - e.preventDefault();
632   -
633   - const formInputs = form.querySelectorAll('input');
634   - const formBtn = form.querySelector('.js_form_btn');
635   -
636   - formInputs.forEach(input => { // перебираем все инпуты в форме;
637   -
638   - this.validateForm(input);
639   -
640   - input.addEventListener('input', () => {
641   - this.validateForm(input);
642   - });
643   -
644   - });
645   -
646   - if (!form.querySelector('.error')) { //проверяем, чтоб все инпуты прошли валидацию (чтоб не было в форме ни одного элемента с класссом error);
647   -
648   - // сюда пишем команды, которые должны сработать после успешной валидации;
649   -
650   - console.log('validate');
651   - formBtn.classList.add('btn-animate');
652   - formBtn.disabled = true;
653   -
654   - const formData = new FormData(form);
655   -
656   - console.log(...formData);
657   -
658   - const response = await fetch(e.target.action, {
659   - method: e.target.method,
660   - body: formData
661   - });
662   -
663   - if (response.ok) {
664   -
665   - setTimeout(() => { // имитация отправки, когда отправка будет настроена, нужно достать всё из setTimeout() и удалить его;
666   -
667   - console.log('Отправлено');
668   - formBtn.classList.remove('btn-animate');
669   - formBtn.disabled = false;
670   - if (document.querySelector('[data-popup="feedback"]')) {
671   - document.querySelector('[data-popup="feedback"]').classList.remove('active');
672   - }
673   - if (document.querySelector('[data-popup="viewing"]')) {
674   - document.querySelector('[data-popup="viewing"]').classList.remove('active');
675   - }
676   - document.querySelector(success).classList.add('active');
677   - this.fixBodyPosition();
678   - form.reset();
679   -
680   - formInputs.forEach(input => {
681   - input.classList.remove('no-error');
682   - });
683   -
684   - }, 2000)
685   -
686   - } else {
687   - formBtn.classList.remove('btn-animate');
688   - formBtn.disabled = false;
689   - alert('Ошибка');
690   - }
691   -
692   - } else {
693   - console.log('no-validate');
694   - form.querySelector('.error').focus(); //фокус к полю с ошибкой;
695   - }
696   -
697   - });
698   -
699   - }
700   -
701   - }
702   -
703   - //отправка предложения по e-mail
704   - sendOffer() {
705   -
706   - const form = document.querySelector('.js_popup_sending_form');
707   -
708   - if (form) {
709   -
710   - form.addEventListener('submit', async (e) => {
711   -
712   - e.preventDefault();
713   -
714   - const formInputs = form.querySelectorAll('input');
715   - const formBtn = form.querySelector('.js_form_btn');
716   -
717   - formInputs.forEach(input => { // перебираем все инпуты в форме;
718   -
719   - this.validateForm(input);
720   -
721   - input.addEventListener('input', () => {
722   - this.validateForm(input);
723   - });
724   -
725   - });
726   -
727   - if (!form.querySelector('.error')) { //проверяем, чтоб все инпуты прошли валидацию (чтоб не было в форме ни одного элемента с класссом error);
728   -
729   - // сюда пишем команды, которые должны сработать после успешной валидации;
730   -
731   - console.log('validate');
732   - formBtn.classList.add('btn-animate');
733   - formBtn.disabled = true;
734   -
735   - const formData = new FormData(form);
736   -
737   - console.log(...formData);
738   -
739   - const response = await fetch(e.target.action, {
740   - method: e.target.method,
741   - body: formData
742   - });
743   -
744   - if (response.ok) {
745   -
746   - setTimeout(() => { // имитация отправки, когда отправка будет настроена, нужно достать всё из setTimeout() и удалить его;
747   -
748   - console.log('Отправлено');
749   - formBtn.classList.remove('btn-animate');
750   - formBtn.disabled = false;
751   - if (document.querySelector('[data-popup="sending"]')) {
752   - document.querySelector('[data-popup="sending"]').classList.remove('active');
753   - }
754   - this.fixBodyPosition();
755   - form.reset();
756   -
757   - formInputs.forEach(input => {
758   - input.classList.remove('no-error');
759   - });
760   -
761   - }, 2000)
762   -
763   - } else {
764   - formBtn.classList.remove('btn-animate');
765   - formBtn.disabled = false;
766   - alert('Ошибка');
767   - }
768   -
769   - } else {
770   - console.log('no-validate');
771   - form.querySelector('.error').focus(); //фокус к полю с ошибкой;
772   - }
773   -
774   - });
775   -
776   - }
777   -
778   - }
779   -
780   -
781   - // карта на странице 'ЖК'
782   - setComplexMap(id, coords, caption) {
783   -
784   - if (document.querySelector('#' + id)) {
785   -
786   - // Дождёмся загрузки API и готовности DOM.
787   - ymaps.ready(init);
788   -
789   - function init() {
790   - const map = new ymaps.Map(id, {
791   - // При инициализации карты обязательно нужно указать её центр и коэффициент масштабирования.
792   - center: coords,
793   - zoom: 16,
794   - controls: []
795   - });
796   -
797   - // Создаём макет содержимого.
798   - const MyIconContentLayout = ymaps.templateLayoutFactory.createClass(
799   - '<div style="color: #FFFFFF; font-weight: bold;">$[properties.iconContent]</div>'
800   - );
801   -
802   - // Создание макета содержимого хинта.
803   - // Макет создается через фабрику макетов с помощью текстового шаблона.
804   - const HintLayout = ymaps.templateLayoutFactory.createClass("<div class='my-hint'>" +
805   - "{{ properties.object }}" + "</div>", {
806   - // Определяем метод getShape, который
807   - // будет возвращать размеры макета хинта.
808   - // Это необходимо для того, чтобы хинт автоматически
809   - // сдвигал позицию при выходе за пределы карты.
810   - getShape: function () {
811   - let el = this.getElement(),
812   - result = null;
813   - if (el) {
814   - var firstChild = el.firstChild;
815   - result = new ymaps.shape.Rectangle(
816   - new ymaps.geometry.pixel.Rectangle([
817   - [0, 0],
818   - [firstChild.offsetWidth, firstChild.offsetHeight]
819   - ])
820   - );
821   - }
822   - return result;
823   - }
824   - }
825   - );
826   -
827   - // метка
828   - const placemark = new ymaps.Placemark(coords, {
829   - // hintContent: caption,
830   - // balloonContent: caption,
831   - iconContent: '1',
832   - // address: caption,
833   - object: caption
834   - }, {
835   - iconLayout: 'default#imageWithContent',
836   - iconImageHref: 'images/mark-complex.svg',
837   - iconImageSize: [52, 67],
838   - iconImageOffset: [-26, -67],
839   - iconContentOffset: [0, 17],
840   - iconContentLayout: MyIconContentLayout,
841   - hintLayout: HintLayout
842   - });
843   -
844   - map.geoObjects.add(placemark);
845   -
846   - }
847   -
848   - }
849   -
850   - }
851   -
852   -
853   - // фильтры и сортировка на странице 'каталог'
854   - setCatalogSorts() {
855   -
856   - const sortGroups = document.querySelectorAll('.js_sort_group');
857   -
858   - if (sortGroups.length) {
859   -
860   - sortGroups.forEach(group => {
861   -
862   - const sortGroupInput = group.querySelector('.js_sort_group_input');
863   - const sortGroupCurrent = group.querySelector('.js_sort_group_current');
864   - const sortGroupList = group.querySelector('.js_sort_group_list');
865   - const sortGroupItems = group.querySelectorAll('.js_sort_group_item');
866   -
867   - const sendRequest = () => {
868   -
869   - const spinner = document.querySelector('.spinner'); // спиннер;
870   -
871   - spinner.classList.add('active');
872   - document.body.classList.add('overlay');
873   - /*this.fixBodyPosition();
874   -
875   - fetch('test.json')
876   - .then(response => response.json())
877   - .then(data => {
878   -
879   - console.log()
880   -
881   - setTimeout(() => { //имитация ответа сервера
882   -
883   - spinner.classList.remove('active');
884   - document.body.classList.remove('overlay');
885   - this.unfixBodyPosition();
886   -
887   - }, 3000);
888   -
889   - })
890   - .catch(err => {
891   - console.log(err);
892   - });
893   -
894   - */
895   - spinner.classList.remove('active');
896   - document.body.classList.remove('overlay');
897   -
898   -
899   - };
900   -
901   - sortGroupCurrent.addEventListener('click', () => {
902   -
903   - if (group.classList.contains('active')) {
904   -
905   - group.classList.remove('active');
906   -
907   - } else {
908   -
909   - sortGroups.forEach(group => {
910   - group.classList.remove('active');
911   - });
912   -
913   - group.classList.add('active');
914   -
915   - }
916   -
917   - });
918   -
919   - sortGroupItems.forEach(item => {
920   -
921   - item.addEventListener('click', () => {
922   -
923   - sortGroupItems.forEach(item => {
924   - item.classList.remove('active');
925   - });
926   -
927   - item.classList.add('active');
928   - sortGroupCurrent.textContent = item.textContent;
929   - sortGroupInput.value = item.dataset.val;
930   - group.classList.remove('active');
931   -
932   - sendRequest();
933   -
934   - });
935   -
936   - });
937   -
938   - });
939   -
940   - document.addEventListener('click', (e) => {
941   -
942   - if (!e.target.closest('.js_sort_group_list') && !e.target.closest('.js_sort_group_current')) {
943   -
944   - sortGroups.forEach(group => {
945   - group.classList.remove('active');
946   - });
947   -
948   - }
949   -
950   - });
951   -
952   - }
953   -
954   - }
955   -
956   -
957   - // слайдер на странице жк и на странице предложения
958   - initIntroSlider() {
959   -
960   - let swiper3 = new Swiper('.intro__swiper', {
961   - navigation: {
962   - nextEl: '.swiper-button-next',
963   - prevEl: '.swiper-button-prev',
964   - },
965   - pagination: {
966   - el: '.swiper-pagination',
967   - clickable: true,
968   - },
969   - slidesPerView: 1.1,
970   - spaceBetween: 20,
971   - breakpoints: {
972   - 480: {
973   - slidesPerView: 1.5,
974   - },
975   - 640: {
976   - slidesPerView: 1.75,
977   - },
978   - 780: {
979   - slidesPerView: 2.15,
980   - },
981   - 1024: {
982   - slidesPerView: 3.5,
983   - },
984   - 1200: {
985   - slidesPerView: 1,
986   - }
987   - }
988   - });
989   -
990   - }
991   -
992   -
993   - // табы на странице предложения
994   - setTabs(tabs, items) {
995   -
996   - const offerSideTabs = document.querySelectorAll(tabs);
997   - const offerSideItems = document.querySelectorAll(items);
998   -
999   - if (offerSideTabs) {
1000   -
1001   - offerSideTabs.forEach(tab => {
1002   -
1003   - tab.addEventListener('click', () => {
1004   -
1005   - offerSideTabs.forEach(tab => {
1006   - tab.classList.remove('active');
1007   - });
1008   -
1009   - tab.classList.add('active');
1010   -
1011   - offerSideItems.forEach(item => {
1012   -
1013   - item.classList.remove('active', 'fade');
1014   -
1015   - if (tab.dataset.tab == item.dataset.item) {
1016   - item.classList.add('active', 'fade');
1017   - }
1018   -
1019   - });
1020   -
1021   - });
1022   -
1023   - });
1024   -
1025   - }
1026   -
1027   - }
1028   -
1029   -
1030   - // логика открытия нужного таба при открытии поп-апа с планами объекат и этажа на странице предложения
1031   - sontrolOfferSidePopup() {
1032   -
1033   - const offerSideItems = document.querySelectorAll('.js_offer_side_item');
1034   - const offerSidePopupTabs = document.querySelectorAll('.js_offer_side_popup_tab');
1035   - const offerSidePopupItems = document.querySelectorAll('.js_offer_side_popup_item');
1036   -
1037   - if (offerSideItems) {
1038   -
1039   - offerSideItems.forEach(item => {
1040   -
1041   - const offerSideItemBtn = item.querySelector('.js_offer_side_item_btn');
1042   -
1043   - offerSideItemBtn.addEventListener('click', (e) => {
1044   -
1045   - e.preventDefault();
1046   -
1047   - offerSidePopupTabs.forEach(tab => {
1048   -
1049   - tab.classList.remove('active');
1050   -
1051   - if (item.dataset.item == tab.dataset.tab) {
1052   - tab.classList.add('active');
1053   - }
1054   -
1055   - });
1056   -
1057   - offerSidePopupItems.forEach(el => {
1058   -
1059   - el.classList.remove('active', 'fade');
1060   -
1061   - if (item.dataset.item == el.dataset.item) {
1062   - el.classList.add('active', 'fade');
1063   - }
1064   -
1065   - });
1066   -
1067   - });
1068   -
1069   - });
1070   -
1071   - }
1072   -
1073   - }
1074   -
1075   -
1076   - // галлерея
1077   - setCustomGallery() {
1078   -
1079   - let swiper4 = new Swiper(".img-viewer__thumbs-swiper", {
1080   - slidesPerView: 3,
1081   - spaceBetween: 8,
1082   - // freeMode: true,
1083   - observer: true,
1084   - observeParents: true,
1085   - observeSlideChildren: true,
1086   - breakpoints: {
1087   - 640: {
1088   - spaceBetween: 10,
1089   - },
1090   - },
1091   - });
1092   -
1093   - let swiper5 = new Swiper(".img-viewer__slider .swiper", {
1094   - navigation: {
1095   - nextEl: ".img-viewer__slider .swiper-button-next",
1096   - prevEl: ".img-viewer__slider .swiper-button-prev",
1097   - },
1098   - slidesPerView: 1,
1099   - spaceBetween: 20,
1100   - thumbs: {
1101   - swiper: swiper4
1102   - },
1103   - observer: true,
1104   - observeParents: true,
1105   - observeSlideChildren: true,
1106   - });
1107   -
1108   - if (document.querySelector('.js_intro_item_btn')) {
1109   -
1110   - const imgViewer = document.querySelector('.js_img_viewer');
1111   - const imgViewerCloses = imgViewer.querySelectorAll('.js_img_viewer_close');
1112   - const imgViewerCaption = imgViewer.querySelector('.js_img_viewer_caption');
1113   -
1114   - const imgViewerSliderSwiper = imgViewer.querySelector('.js_img_viewer_slider_swiper');
1115   - const imgViewerSliderSwiperWrap = imgViewerSliderSwiper.querySelector('.js_img_viewer_slider_swiper .swiper-wrapper');
1116   -
1117   - const imgViewerThumbsSwiper = imgViewer.querySelector('.js_img_viewer_thumbs_swiper');
1118   - const imgViewerThumbsSwiperWrap = imgViewerThumbsSwiper.querySelector('.js_img_viewer_thumbs_swiper .swiper-wrapper');
1119   -
1120   - const introItemBtns = document.querySelectorAll('.js_intro_item_btn');
1121   -
1122   - introItemBtns.forEach((btn, i) => {
1123   -
1124   - btn.addEventListener('click', (e) => {
1125   -
1126   - e.preventDefault();
1127   -
1128   - imgViewer.classList.add('active');
1129   - this.fixBodyPosition();
1130   -
1131   - imgViewerSliderSwiperWrap.innerHTML = '';
1132   - imgViewerThumbsSwiperWrap.innerHTML = '';
1133   - imgViewerCaption.textContent = '';
1134   -
1135   -
1136   - introItemBtns.forEach(btn => {
1137   -
1138   - imgViewerSliderSwiperWrap.insertAdjacentHTML('beforeend', `
1139   - <div class="swiper-slide">
1140   - <img src="${btn.getAttribute('href')}" alt="">
1141   - </div>`
1142   - );
1143   -
1144   - imgViewerThumbsSwiperWrap.insertAdjacentHTML('beforeend', `
1145   - <div class="swiper-slide">
1146   - <img src="${btn.getAttribute('href')}" alt="">
1147   - </div>`
1148   - );
1149   -
1150   - });
1151   -
1152   - swiper4.update();
1153   - swiper5.update();
1154   - swiper5.slideTo(i);
1155   - imgViewerCaption.textContent = btn.dataset.caption;
1156   -
1157   - });
1158   -
1159   - });
1160   -
1161   - swiper5.on('slideChange', function () {
1162   - imgViewerCaption.textContent = introItemBtns[swiper5.realIndex].dataset.caption;
1163   - });
1164   -
1165   - imgViewerCloses.forEach(close => {
1166   -
1167   - close.addEventListener('click', () => {
1168   -
1169   - imgViewer.classList.remove('active');
1170   - this.unfixBodyPosition();
1171   -
1172   - });
1173   -
1174   - });
1175   -
1176   - }
1177   -
1178   - }
1179   -
1180   -
1181   - // куки
1182   - setCookies() {
1183   -
1184   - const cookies = document.querySelector('.js_cookies');
1185   - const cookiesBtn = document.querySelector('.js_cookies_confirm');
1186   - const cookiesTrigger = document.querySelector('.js_btn_cookies');
1187   -
1188   - if (cookiesTrigger) {
1189   -
1190   - cookiesTrigger.addEventListener('click', () => {
1191   - cookies.classList.add('active');
1192   - });
1193   -
1194   - };
1195   -
1196   - if (cookies) {
1197   -
1198   - cookiesBtn.addEventListener('click', () => {
1199   - cookies.classList.remove('active');
1200   - });
1201   -
1202   - };
1203   -
1204   - }
1205   -
1206   -
1207   - // карта на странице карт;
1208   - setGeneralMap() {
1209   -
1210   - if (document.querySelector('#general-map')) {
1211   -
1212   - ymaps.ready(init); // Дождёмся загрузки API и готовности DOM;
1213   -
1214   - function init() {
1215   -
1216   - const myMap = new ymaps.Map('general-map', { // Создание экземпляра карты и его привязка к контейнеру с заданным id;
1217   - center: [55.752933963675126, 37.52233749962665], // При инициализации карты обязательно нужно указать её центр и коэффициент масштабирования;
1218   - zoom: 10,
1219   - controls: [] // Скрываем элементы управления на карте;
1220   - });
1221   -
1222   - // Создаём макет содержимого.
1223   - const MyIconContentLayout = ymaps.templateLayoutFactory.createClass(
1224   - '<div style="color: #FFFFFF; font-weight: bold;">$[properties.iconContent]</div>'
1225   - );
1226   -
1227   - let collection = new ymaps.GeoObjectCollection(null, { // Создаём коллекцию, в которую будемпомещать метки (что-то типа массива);
1228   - // preset: 'islands#yellowIcon'
1229   - });
1230   -
1231   - let collectionCoords = [ // Создаём массив с координатами (координаты должны располагаться в том же порядке, что и адреса в списке на сайте);
1232   - [55.867783219108354, 37.392867499999916],
1233   - [55.728043075486504, 37.73937949999994],
1234   - [55.72624100423305, 37.476078499999964],
1235   - [55.80751105044832, 37.449622999999974],
1236   - [55.601783098948836, 37.36700499999998],
1237   - [55.86086502152225, 37.540348999999964],
1238   - [55.784961528728715, 37.56188599999996],
1239   - [55.63910010399773, 37.319407999999996],
1240   - [55.55819256767507, 37.55711549999994],
1241   - [55.79829252928473, 37.52063549999999],
1242   - ];
1243   -
1244   - for (let i = 0, l = collectionCoords.length; i < l; i++) { // C помощью цикла добавляем все метки в коллекцию;
1245   - collection.add(new ymaps.Placemark(collectionCoords[i]));
1246   - collection.get(i).properties.set('iconContent', `${i + 1}`); // Добавляем каждой метке порядковый номер, записываем его в свойство 'iconContent';
1247   - }
1248   -
1249   - myMap.geoObjects.add(collection); // Добавляем коллекцию с метками на карту;
1250   -
1251   - collection.options.set('iconLayout', 'default#imageWithContent'); // Необходимо указать данный тип макета;
1252   - collection.options.set('iconImageHref', 'images/mark-complex.svg'); // Своё изображение иконки метки;
1253   - collection.options.set('iconImageSize', [52, 67]); // Размеры метки;
1254   - collection.options.set('iconImageOffset', [-26, -67]); // Смещение левого верхнего угла иконки относительно её "ножки" (точки привязки);
1255   - collection.options.set('iconContentOffset', [0, 17]);
1256   - collection.options.set('iconContentLayout', MyIconContentLayout); // Смещение левого верхнего угла иконки относительно её "ножки" (точки привязки);
1257   -
1258   - const pageMapBar = document.querySelector('.js_page_map_bar');
1259   - const pageMapBarBtn = pageMapBar.querySelector('.js_page_map_bar_btn');
1260   - const pageMapBarList = pageMapBar.querySelector('.js_page_map_bar_list');
1261   - const pageMapBarCards = pageMapBar.querySelectorAll('.card-news');
1262   -
1263   - const showCard = (i) => {
1264   -
1265   - pageMapBarCards.forEach((card, k) => {
1266   -
1267   - card.classList.remove('active');
1268   -
1269   - if (i == k) {
1270   - card.classList.add('active');
1271   - }
1272   -
1273   - });
1274   -
1275   - };
1276   -
1277   - const hidecard = () => {
1278   -
1279   - pageMapBarCards.forEach(card => {
1280   - card.classList.remove('active');
1281   - });
1282   -
1283   - }
1284   -
1285   - let pageMapBarItems;
1286   -
1287   - pageMapBarBtn.addEventListener('click', () => {
1288   - pageMapBar.classList.toggle('active');
1289   - });
1290   -
1291   - pageMapBarList.addEventListener('click', (e) => {
1292   -
1293   - if (e.target.closest('.page-map-bar__item')) {
1294   -
1295   - pageMapBarItems = pageMapBarList.querySelectorAll('.page-map-bar__item');
1296   -
1297   - pageMapBarItems.forEach((item, i) => {
1298   -
1299   - if (e.target == item && e.target.classList.contains('active')) {
1300   -
1301   - item.classList.remove('active');
1302   -
1303   - hidecard();
1304   -
1305   - } else if (e.target == item) {
1306   -
1307   - pageMapBarItems.forEach(item => {
1308   - item.classList.remove('active');
1309   - });
1310   -
1311   - item.classList.add('active');
1312   -
1313   - let offsetCoords = collection.get(i).geometry.getCoordinates();
1314   -
1315   - offsetCoords = [
1316   - offsetCoords[0] - 0.0025,
1317   - offsetCoords[1]
1318   - ];
1319   -
1320   - myMap.setZoom(16);
1321   - // myMap.setCenter(collection.get(i).geometry.getCoordinates());
1322   - myMap.setCenter(offsetCoords);
1323   -
1324   - showCard(i);
1325   -
1326   - }
1327   -
1328   - });
1329   - }
1330   -
1331   - });
1332   -
1333   - collection.events.add('click', function (e) {
1334   -
1335   - for (let i = 0, l = collection.getLength(); i < l; i++) {
1336   -
1337   - if (e.get('target') == collection.get(i)) {
1338   -
1339   - pageMapBarItems = pageMapBarList.querySelectorAll('.page-map-bar__item');
1340   -
1341   - pageMapBarItems.forEach((item) => {
1342   - pageMapBar.classList.add('active');
1343   - item.classList.remove('active');
1344   - });
1345   -
1346   - pageMapBarItems[i].classList.add('active');
1347   -
1348   - showCard(i);
1349   -
1350   - }
1351   -
1352   - }
1353   -
1354   - });
1355   -
1356   - }
1357   -
1358   - }
1359   -
1360   - };
1361   -
1362   -
1363   - // аккордеон в футере
1364   - setFooterSpoilers() {
1365   -
1366   - const items = document.querySelectorAll('.js_footer_col');
1367   -
1368   - items.forEach(item => {
1369   -
1370   - const itemTitle = item.querySelector('.js_footer_caption');
1371   - const itemContent = item.querySelector('.js_footer_block');
1372   -
1373   - const blockToggle = (block, duration) => {
1374   -
1375   - if (window.getComputedStyle(block).display == "none" && !block.classList.contains('smooth')) {
1376   -
1377   - block.style.display = "block";
1378   -
1379   - const blockHeight = block.offsetHeight;
1380   -
1381   - block.style.height = 0;
1382   - block.style.overflow = "hidden";
1383   - block.style.transition = `height ${duration}ms ease`;
1384   - block.classList.add('smooth');
1385   - block.offsetHeight;
1386   - block.style.height = `${blockHeight}px`;
1387   -
1388   - setTimeout(() => {
1389   -
1390   - block.classList.remove('smooth');
1391   - block.style.height = '';
1392   - block.style.transition = '';
1393   - block.style.overflow = '';
1394   -
1395   - }, duration);
1396   -
1397   - } else if (!block.classList.contains('smooth')) {
1398   -
1399   - block.style.height = `${block.offsetHeight}px`;
1400   - block.offsetHeight;
1401   - block.style.height = 0;
1402   - block.style.overflow = "hidden";
1403   - block.style.transition = `height ${duration}ms ease`;
1404   - block.classList.add('smooth');
1405   -
1406   - setTimeout(() => {
1407   -
1408   - block.classList.remove('smooth');
1409   - block.style.display = "none";
1410   - block.style.height = '';
1411   - block.style.transition = '';
1412   - block.style.overflow = '';
1413   -
1414   - }, duration);
1415   -
1416   - }
1417   -
1418   - };
1419   -
1420   - itemTitle.addEventListener('click', (e) => {
1421   - itemTitle.classList.toggle('active');
1422   - blockToggle(itemContent, 300);
1423   - });
1424   -
1425   - });
1426   -
1427   - }
1428   -
1429   -
1430   - // слайдер с партнёрами;
1431   - initPartnerslSlider() {
1432   -
1433   - const slider = document.querySelector('.partners__swiper');
1434   -
1435   - if (slider) {
1436   -
1437   - let swiper6;
1438   -
1439   - const initSlider = () => {
1440   -
1441   - swiper6 = new Swiper(slider, {
1442   - // scrollbar: {
1443   - // el: '.swiper-scrollbar',
1444   - // draggable: true,
1445   - // },
1446   - slidesPerView: 0.275,
1447   - loop: true,
1448   - spaceBetween: 20,
1449   - freeMode: true,
1450   - allowTouchMove: true,
1451   - breakpoints: {
1452   - 480: {
1453   - slidesPerView: 0.4,
1454   - },
1455   - 640: {
1456   - slidesPerView: 0.65,
1457   - },
1458   - 780: {
1459   - slidesPerView: 0.65,
1460   - },
1461   - 1024: {
1462   - slidesPerView: 0.8,
1463   - },
1464   - 1200: {
1465   - slidesPerView: 1,
1466   - loop: false,
1467   - allowTouchMove: false,
1468   - }
1469   - }
1470   - });
1471   -
1472   - };
1473   -
1474   - initSlider();
1475   -
1476   - const updateSlider = () => {
1477   - swiper6.destroy();
1478   - initSlider();
1479   - }
1480   -
1481   - window.addEventListener('resize', () => {
1482   -
1483   - if (window.innerWidth <= 1200 && slider.dataset.mobile == 'false') {
1484   - slider.dataset.mobile = 'true';
1485   - updateSlider();
1486   - }
1487   -
1488   - if (window.innerWidth > 1200 && slider.dataset.mobile == 'true') {
1489   - slider.dataset.mobile = 'false';
1490   - updateSlider();
1491   - }
1492   -
1493   - });
1494   -
1495   - }
1496   -
1497   - }
1498   -
1499   -}
1500   -
1501   -
1502   -document.addEventListener('DOMContentLoaded', () => {
1503   -
1504   - const app = new App();
1505   - app.init();
1506   -
1507   -});
public/js/main_new.js
Changes suppressed. Click to show
... ... @@ -0,0 +1,1508 @@
  1 +// управляющий класс App с методом init(), в котором собраны все используемые методы с комментариями о том, что конкретно делает каждый метод
  2 +
  3 +class App {
  4 +
  5 + constructor() {
  6 + this.patternPhone = /^(\+7|7|8)?[\s\-]?\(?[489][0-9]{2}\)?[\s\-]?[0-9]{3}[\s\-]?[0-9]{2}[\s\-]?[0-9]{2}$/; // рег. выражение для поля 'телефон';
  7 + this.patternEmail = /^[a-zA-Z0-9._%+-\.]+@[a-z0-9.-]+\.[a-z]{2,}$/i; // рег. выражение для поля 'электронная почта';
  8 + }
  9 +
  10 + init() {
  11 +
  12 + console.log('init');
  13 +
  14 + this.stickyHeader(); // липкий хедер;
  15 + this.controlBurgerMenu(); // бургер-меню;
  16 + this.smoothScroll(); // плавный скролл к якорю (smooth scroll);
  17 + this.scrollUp(); // кнопка наверх;
  18 + this.addToFavorites(); // добавить в избранное (звёздочка);
  19 + this.initTypicalSlider(); // типовые слайдеры;
  20 + this.initPartnerslSlider(); // слайдер с партнёрами;
  21 + this.controlFilters(); // фильтры на главном экране;
  22 + this.controlPopups(); // открытие/закрытие поп-апов;
  23 + this.controlContactUsPopup(); // открытие/закрытие поп-апа 'обратный звонок';
  24 +
  25 + this.sendForm('.js_popup_feedback_form', '[data-popup="success"]'); // отправка формы в поп-апе обратной связи;
  26 + this.sendForm('.js_popup_viewing_form', '[data-popup="success"]'); // отправка формы в поп-апе 'записаться на просмотр';
  27 + this.sendForm('.js_footer_feedback_form', '[data-popup="success"]'); // отправка формы в футере;
  28 + this.sendForm('.js_contacts_form', '.js_contacts_success'); // отправка формы на странице контакты;
  29 + this.sendForm('.js_popup_sending_form_', '[data-popup="success"]');
  30 + //this.sendOffer(); //отправка предложения по e-mail;
  31 +
  32 + //this.setGeneralMap(); // карта на странице карт;
  33 + this.setComplexMap('complex-map', [55.726591050908745, 37.57244549999999], 'ЖК Садовые кварталы'); // карта на странице 'ЖК';
  34 + this.setComplexMap('offer-map', [55.70851106903402, 37.65864349999999], 'Аренда торгового помещения 321,6 м2'); // карта на странице 'Предложение';
  35 + this.setCatalogSorts(); // сортировка на странице 'каталог';
  36 + this.initIntroSlider(); // слайдер на странице жк и на странице предложения;
  37 + this.setTabs('.js_offer_side_tab', '.js_offer_side_item'); // табы с планами объекат и этажа на странице предложения;
  38 + this.setTabs('.js_offer_side_popup_tab', '.js_offer_side_popup_item'); // табы с планами объекат и этажа в поп-апе на странице предложения;
  39 + this.sontrolOfferSidePopup(); // логика открытия нужного таба при открытии поп-апа с планами объекат и этажа на странице предложения;
  40 + this.setCustomGallery(); // галлерея;
  41 + this.setCookies() // куки;
  42 + this.setFooterSpoilers() // аккордеон в футере;
  43 +
  44 + }
  45 +
  46 + // фиксация <body>
  47 + fixBodyPosition() {
  48 +
  49 + setTimeout(function () {
  50 + // ставим необходимую задержку, чтобы не было «конфликта» в случае, если функция фиксации вызывается сразу после расфиксации (расфиксация отменяет действия расфиксации из-за одновременного действия)
  51 +
  52 + if (!document.body.hasAttribute('data-body-scroll-fix')) {
  53 +
  54 + // получаем позицию прокрутки
  55 + let scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
  56 +
  57 + // ставим нужные стили
  58 + document.body.setAttribute('data-body-scroll-fix', scrollPosition); // Cтавим атрибут со значением прокрутки
  59 + document.body.style.overflow = 'hidden';
  60 + document.body.style.position = 'fixed';
  61 + document.body.style.top = '-' + scrollPosition + 'px';
  62 + document.body.style.left = '0';
  63 + document.body.style.width = '100%';
  64 +
  65 + if (window.innerWidth >= 1200) {
  66 + document.body.style.paddingRight = '8px';
  67 + }
  68 + }
  69 +
  70 + }, 15); // можно задержку ещё меньше, но работает хорошо именно с этим значением на всех устройствах и браузерах
  71 +
  72 + }
  73 +
  74 +
  75 + // расфиксация <body>
  76 + unfixBodyPosition() {
  77 +
  78 + if (document.body.hasAttribute('data-body-scroll-fix')) {
  79 +
  80 + // получаем позицию прокрутки из атрибута
  81 + let scrollPosition = document.body.getAttribute('data-body-scroll-fix');
  82 +
  83 + // удаляем атрибут
  84 + document.body.removeAttribute('data-body-scroll-fix');
  85 +
  86 + // удаляем ненужные стили
  87 + document.body.style.overflow = '';
  88 + document.body.style.position = '';
  89 + document.body.style.top = '';
  90 + document.body.style.left = '';
  91 + document.body.style.width = '';
  92 + document.body.style.paddingRight = '';
  93 +
  94 + // прокручиваем страницу на полученное из атрибута значение
  95 + window.scroll(0, scrollPosition);
  96 +
  97 + }
  98 +
  99 + }
  100 +
  101 +
  102 + // бургер-меню
  103 + controlBurgerMenu() {
  104 +
  105 + const headerBurger = document.querySelector('.js_header_burger');
  106 +
  107 + if (headerBurger) {
  108 +
  109 + const menu = document.querySelector('.js_menu');
  110 + const menuClose = menu.querySelector('.js_menu_close');
  111 +
  112 + headerBurger.addEventListener('click', () => {
  113 + menu.classList.add('active');
  114 + this.fixBodyPosition();
  115 + });
  116 +
  117 + menu.addEventListener('click', (e) => {
  118 +
  119 + if (e.target == menu) {
  120 + menu.classList.remove('active');
  121 + this.unfixBodyPosition();
  122 + }
  123 +
  124 + });
  125 +
  126 + menuClose.addEventListener('click', () => {
  127 + menu.classList.remove('active');
  128 + this.unfixBodyPosition();
  129 + });
  130 +
  131 + }
  132 +
  133 + }
  134 +
  135 +
  136 + // липкий хедер
  137 + stickyHeader() {
  138 +
  139 + const header = document.querySelector('.js_header');
  140 +
  141 + if (header) {
  142 +
  143 + window.addEventListener('scroll', () => {
  144 +
  145 + if (window.scrollY > 200) {
  146 + header.classList.add('fixed');
  147 + } else {
  148 + header.classList.remove('fixed');
  149 + }
  150 +
  151 + });
  152 +
  153 + };
  154 +
  155 + }
  156 +
  157 +
  158 + // плавный скролл к якорю (smooth scroll)
  159 + smoothScroll() {
  160 +
  161 + const smoothLinks = document.querySelectorAll('.js_smooth_link');
  162 +
  163 + if (smoothLinks.length) {
  164 +
  165 + smoothLinks.forEach(link => {
  166 +
  167 + link.addEventListener('click', function (e) {
  168 +
  169 + e.preventDefault();
  170 +
  171 + let href = this.getAttribute('href').substring(1);
  172 +
  173 + const scrollTarget = document.getElementById(href);
  174 +
  175 + // const topOffset = document.querySelector('.header').offsetHeight;
  176 + const topOffset = 0; // если не нужен отступ сверху
  177 + const elementPosition = scrollTarget.getBoundingClientRect().top;
  178 + const offsetPosition = elementPosition - topOffset;
  179 +
  180 + window.scrollBy({
  181 + top: offsetPosition,
  182 + behavior: 'smooth'
  183 + });
  184 +
  185 + });
  186 +
  187 + });
  188 +
  189 + }
  190 +
  191 + }
  192 +
  193 +
  194 + // кнопка наверх
  195 + scrollUp() {
  196 +
  197 + const toTopBtn = document.querySelector('.js_btn_up');
  198 +
  199 + if (toTopBtn) {
  200 +
  201 + toTopBtn.addEventListener('click', function () {
  202 +
  203 + window.scrollTo({
  204 + top: 0,
  205 + behavior: 'smooth'
  206 + });
  207 +
  208 + });
  209 +
  210 + }
  211 +
  212 + }
  213 +
  214 +
  215 + // добавить в избранное (звёздочка)
  216 + addToFavorites() {
  217 +
  218 + const cardFavorites = document.querySelectorAll('.js_card_favorites');
  219 +
  220 + if (cardFavorites.length) {
  221 +
  222 + cardFavorites.forEach(item => {
  223 +
  224 + item.addEventListener('click', (e) => {
  225 + e.preventDefault();
  226 + item.classList.toggle('active');
  227 + });
  228 +
  229 + });
  230 +
  231 + }
  232 +
  233 + }
  234 +
  235 +
  236 + // типовые слайдеры
  237 + initTypicalSlider() {
  238 +
  239 + const slidersWraps = document.querySelectorAll('.slider__wrap');
  240 +
  241 + if (slidersWraps.length) {
  242 +
  243 + slidersWraps.forEach(wrap => {
  244 +
  245 + const slider = wrap.querySelector('.swiper');
  246 + const prev = wrap.querySelector('.swiper-button-prev');
  247 + const next = wrap.querySelector('.swiper-button-next');
  248 + const pagination = wrap.querySelector('.swiper-pagination');
  249 +
  250 + let swiper1 = new Swiper(slider, {
  251 + navigation: {
  252 + nextEl: next,
  253 + prevEl: prev,
  254 + },
  255 + pagination: {
  256 + el: pagination,
  257 + clickable: true,
  258 + },
  259 + slidesPerView: 1,
  260 + spaceBetween: 20,
  261 + observer: true,
  262 + observeParents: true,
  263 + observeSlideChildren: true,
  264 + breakpoints: {
  265 + 480: {
  266 + slidesPerView: 1.5,
  267 + },
  268 + 640: {
  269 + slidesPerView: 2,
  270 + },
  271 + 780: {
  272 + slidesPerView: 2.5,
  273 + },
  274 + 920: {
  275 + slidesPerView: 3,
  276 + },
  277 + 1024: {
  278 + slidesPerView: 3.4
  279 + },
  280 + 1200: {
  281 + slidesPerView: 4,
  282 + }
  283 + }
  284 + });
  285 +
  286 + });
  287 +
  288 + }
  289 +
  290 + }
  291 +
  292 +
  293 + // метод, делающий число удобночитаемым (добавляет пробел справа через каждые 3 цифры)
  294 + prettify(num) {
  295 + const withoutSpace = num.replace(/[^\d]/g, ''); //убирает все символы;
  296 + return withoutSpace.replace(/(?!^)(?=(?:\d{3})+(?:\.|$))/gm, ' '); //ставит пробелы;
  297 + }
  298 +
  299 +
  300 + // фильтры на главном экране
  301 + controlFilters() {
  302 +
  303 + const heroFilters = document.querySelectorAll('.js_hero_filter');
  304 + const heroSearchBtns = document.querySelectorAll('.js_hero_search_btn');
  305 +
  306 + if (heroFilters.length) {
  307 +
  308 + heroFilters.forEach(filter => {
  309 +
  310 + const heroFilterInput = filter.querySelector('.js_hero_filter_input');
  311 + const heroFilterCurrent = filter.querySelector('.js_hero_filter_current');
  312 + const heroFilterItems = filter.querySelectorAll('.hero-filter__item');
  313 + const heroFilterFields = filter.querySelectorAll('.js_hero_filter_field');
  314 + const heroFilterFrom = filter.querySelector('.js_hero_filter_from');
  315 + const heroFilterTo = filter.querySelector('.js_hero_filter_to');
  316 + const heroFilterReset = filter.querySelector('.js_hero_filter_reset');
  317 +
  318 + heroFilterCurrent.addEventListener('click', () => {
  319 +
  320 + if (filter.classList.contains('active')) {
  321 +
  322 + filter.classList.remove('active');
  323 +
  324 + heroSearchBtns.forEach(btn => {
  325 + btn.disabled = false;
  326 + });
  327 +
  328 + } else {
  329 +
  330 + heroFilters.forEach(filter => {
  331 + filter.classList.remove('active');
  332 + });
  333 +
  334 + filter.classList.add('active');
  335 +
  336 + heroSearchBtns.forEach(btn => {
  337 + btn.disabled = true;
  338 + });
  339 +
  340 + }
  341 +
  342 + });
  343 +
  344 + if (heroFilterItems.length) {
  345 +
  346 + heroFilterItems.forEach(item => {
  347 +
  348 + item.addEventListener('click', () => {
  349 +
  350 + heroFilterCurrent.textContent = item.textContent;
  351 + heroFilterInput.value = item.dataset.val;
  352 + filter.classList.remove('active');
  353 +
  354 + heroSearchBtns.forEach(btn => {
  355 + btn.disabled = false;
  356 + });
  357 +
  358 + });
  359 +
  360 + });
  361 +
  362 + }
  363 +
  364 + if (heroFilterFields.length) {
  365 +
  366 + const heroFilterMin = heroFilterFrom.dataset.min;
  367 + const heroFilterMax = heroFilterTo.dataset.max;
  368 +
  369 + let heroFilterFromVal;
  370 + let heroFilterToVal;
  371 +
  372 + heroFilterFields.forEach(field => {
  373 +
  374 + field.addEventListener('input', () => {
  375 +
  376 + field.value = this.prettify(field.value);
  377 +
  378 + heroFilterReset.classList.remove('active');
  379 +
  380 + heroFilterFields.forEach(field => {
  381 +
  382 + if (field.value != "") {
  383 + heroFilterReset.classList.add('active');
  384 + }
  385 +
  386 + });
  387 +
  388 + });
  389 +
  390 + });
  391 +
  392 + heroFilterFrom.addEventListener('change', () => {
  393 +
  394 + heroFilterFromVal = +heroFilterFrom.value.replace(/\s/g, '');
  395 + heroFilterToVal = +heroFilterTo.value.replace(/\s/g, '');
  396 +
  397 + if (heroFilterToVal != '' && heroFilterFromVal > heroFilterToVal) {
  398 +
  399 + heroFilterFrom.value = heroFilterTo.value;
  400 +
  401 + } else if (heroFilterFromVal < +heroFilterMin) {
  402 +
  403 + heroFilterFrom.value = this.prettify(heroFilterMin);
  404 +
  405 + } else if (heroFilterFromVal > +heroFilterMax) {
  406 +
  407 + heroFilterFrom.value = this.prettify(heroFilterMax);
  408 +
  409 + }
  410 +
  411 + });
  412 +
  413 + heroFilterTo.addEventListener('change', () => {
  414 +
  415 + heroFilterFromVal = +heroFilterFrom.value.replace(/\s/g, '');
  416 + heroFilterToVal = +heroFilterTo.value.replace(/\s/g, '');
  417 +
  418 + if (heroFilterFromVal != '' && heroFilterToVal < heroFilterFromVal) {
  419 +
  420 + heroFilterTo.value = heroFilterFrom.value;
  421 +
  422 + } else if (heroFilterToVal < +heroFilterMin) {
  423 +
  424 + heroFilterTo.value = this.prettify(heroFilterMax);
  425 +
  426 + } else if (heroFilterToVal > +heroFilterMax) {
  427 +
  428 + heroFilterTo.value = this.prettify(heroFilterMax);
  429 +
  430 + }
  431 +
  432 + });
  433 +
  434 + heroFilterReset.addEventListener('click', () => {
  435 +
  436 + heroFilterFields.forEach(field => {
  437 + field.value = '';
  438 + });
  439 +
  440 + heroFilterReset.classList.remove('active');
  441 +
  442 + });
  443 + }
  444 +
  445 + });
  446 +
  447 + document.addEventListener('click', (e) => {
  448 +
  449 + if (!e.target.closest('.js_hero_filter_dropdown') && !e.target.closest('.js_hero_filter_current')) {
  450 +
  451 + heroFilters.forEach(filter => {
  452 + filter.classList.remove('active');
  453 + });
  454 +
  455 + heroSearchBtns.forEach(btn => {
  456 + btn.disabled = false;
  457 + });
  458 +
  459 + }
  460 +
  461 + });
  462 +
  463 + }
  464 +
  465 + }
  466 +
  467 +
  468 + // открытие/закрытие типовых поп-апов
  469 + controlPopups() {
  470 +
  471 + const popupShowBtns = document.querySelectorAll('[data-btn]');
  472 + const popups = document.querySelectorAll('[data-popup]');
  473 +
  474 + if (popupShowBtns.length) {
  475 +
  476 + popupShowBtns.forEach(btn => {
  477 +
  478 + btn.addEventListener('click', (e) => {
  479 +
  480 + e.preventDefault();
  481 +
  482 + popups.forEach(popup => {
  483 +
  484 + popup.classList.remove('active'); // если какойто поп-ап открыт, то закрываем его;
  485 + this.unfixBodyPosition();
  486 +
  487 + if (btn.dataset.btn == popup.dataset.popup) {
  488 + popup.classList.add('active');
  489 + this.fixBodyPosition();
  490 + }
  491 +
  492 + });
  493 +
  494 +
  495 + });
  496 +
  497 + });
  498 +
  499 + popups.forEach(popup => {
  500 +
  501 + const popupCloseBtns = popup.querySelectorAll('.js_popup_close');
  502 +
  503 + popupCloseBtns.forEach(btn => {
  504 +
  505 + btn.addEventListener('click', (e) => {
  506 + e.preventDefault();
  507 + popup.classList.remove('active');
  508 + this.unfixBodyPosition();
  509 + });
  510 +
  511 + });
  512 +
  513 + popup.addEventListener('click', (e) => {
  514 +
  515 + if (e.target == popup) {
  516 +
  517 + popup.classList.remove('active');
  518 + this.unfixBodyPosition();
  519 + }
  520 +
  521 + });
  522 +
  523 + });
  524 +
  525 + }
  526 + }
  527 +
  528 +
  529 + // открытие/закрытие поп-апа 'обратный звонок'
  530 + controlContactUsPopup() {
  531 +
  532 + const contactUsBtn = document.querySelector('.js_btn_contact_us');
  533 + const contactUsPopup = document.querySelector('.js_contact_us');
  534 +
  535 + if (contactUsPopup) {
  536 +
  537 + const contactUsPopupCloseBtns = contactUsPopup.querySelectorAll('.js_contact_us_close');
  538 +
  539 + contactUsBtn.addEventListener('click', (e) => {
  540 +
  541 + e.preventDefault();
  542 +
  543 + if (contactUsPopup.classList.contains('active')) {
  544 + contactUsPopup.classList.remove('active');
  545 + } else {
  546 + contactUsPopup.classList.add('active');
  547 + }
  548 +
  549 + });
  550 +
  551 + contactUsPopupCloseBtns.forEach(btn => {
  552 + btn.addEventListener('click', () => {
  553 + contactUsPopup.classList.remove('active');
  554 + });
  555 + });
  556 +
  557 +
  558 + document.addEventListener('click', (e) => {
  559 +
  560 + if (!e.target.closest('.js_contact_us') && !e.target.closest('.js_btn_contact_us')) {
  561 + contactUsPopup.classList.remove('active');
  562 + }
  563 +
  564 + });
  565 +
  566 + }
  567 +
  568 + }
  569 +
  570 +
  571 + // валидатор форм
  572 + validateForm(input) {
  573 +
  574 + // функция добавления ошибки
  575 + const createError = (text) => {
  576 +
  577 + input.classList.add('error');
  578 + input.classList.remove('no-error');
  579 +
  580 + if (input.closest('label').querySelector('span.error')) {
  581 + input.closest('label').querySelector('span.error').remove();
  582 + input.closest('label').insertAdjacentHTML('beforeend', `<span class="error">${text}</span>`);
  583 + } else {
  584 + input.closest('label').insertAdjacentHTML('beforeend', `<span class="error">${text}</span>`);
  585 + }
  586 +
  587 + }
  588 +
  589 + // функция удаления ошибки
  590 + const removeError = () => {
  591 +
  592 + input.classList.remove('error');
  593 + input.classList.add('no-error');
  594 +
  595 + if (input.closest('label').querySelector('span.error')) {
  596 + input.closest('label').querySelector('span.error').remove();
  597 + }
  598 +
  599 + }
  600 +
  601 + // проверяем на правильность заполнения поля 'Телефон'
  602 + if (input.classList.contains('js_input_phone') && input.value == "") {
  603 + createError('Заполните, пожалуйста, поле');
  604 + } else if (input.classList.contains('js_input_phone') && input.value.search(this.patternPhone) == 0) {
  605 + removeError();
  606 + } else if (input.classList.contains('js_input_phone')) {
  607 + createError('Укажите, пожалуйста, корректный телефон');
  608 + }
  609 +
  610 + // проверяем правильность заполнения поля 'Электронная почта'
  611 + if (input.classList.contains('js_input_email') && input.value == "") {
  612 + createError('Заполните, пожалуйста, поле');
  613 + } else if (input.classList.contains('js_input_email') && input.value.search(this.patternEmail) == 0) {
  614 + removeError();
  615 + } else if (input.classList.contains('js_input_email')) {
  616 + createError('Укажите, пожалуйста, корректный e-mail');
  617 + }
  618 +
  619 + }
  620 +
  621 +
  622 + // отправка форм
  623 + sendForm(formEl, success) {
  624 +
  625 + const form = document.querySelector(formEl);
  626 +
  627 + if (form) {
  628 +
  629 + form.addEventListener('submit', async (e) => {
  630 +
  631 + e.preventDefault();
  632 +
  633 + const formInputs = form.querySelectorAll('input');
  634 + const formBtn = form.querySelector('.js_form_btn');
  635 +
  636 + formInputs.forEach(input => { // перебираем все инпуты в форме;
  637 +
  638 + this.validateForm(input);
  639 +
  640 + input.addEventListener('input', () => {
  641 + this.validateForm(input);
  642 + });
  643 +
  644 + });
  645 +
  646 + if (!form.querySelector('.error')) { //проверяем, чтоб все инпуты прошли валидацию (чтоб не было в форме ни одного элемента с класссом error);
  647 +
  648 + // сюда пишем команды, которые должны сработать после успешной валидации;
  649 +
  650 + console.log('validate');
  651 + formBtn.classList.add('btn-animate');
  652 + formBtn.disabled = true;
  653 +
  654 + const formData = new FormData(form);
  655 +
  656 + console.log(...formData);
  657 +
  658 + const response = await fetch(e.target.action, {
  659 + method: e.target.method,
  660 + body: formData
  661 + });
  662 +
  663 + if (response.ok) {
  664 +
  665 + setTimeout(() => { // имитация отправки, когда отправка будет настроена, нужно достать всё из setTimeout() и удалить его;
  666 +
  667 + console.log('Отправлено');
  668 + formBtn.classList.remove('btn-animate');
  669 + formBtn.disabled = false;
  670 + if (document.querySelector('[data-popup="feedback"]')) {
  671 + document.querySelector('[data-popup="feedback"]').classList.remove('active');
  672 + }
  673 + if (document.querySelector('[data-popup="viewing"]')) {
  674 + document.querySelector('[data-popup="viewing"]').classList.remove('active');
  675 + }
  676 + document.querySelector(success).classList.add('active');
  677 + this.fixBodyPosition();
  678 + form.reset();
  679 +
  680 + formInputs.forEach(input => {
  681 + input.classList.remove('no-error');
  682 + });
  683 +
  684 + }, 2000)
  685 +
  686 + } else {
  687 + formBtn.classList.remove('btn-animate');
  688 + formBtn.disabled = false;
  689 + alert('Ошибка');
  690 + }
  691 +
  692 + } else {
  693 + console.log('no-validate');
  694 + form.querySelector('.error').focus(); //фокус к полю с ошибкой;
  695 + }
  696 +
  697 + });
  698 +
  699 + }
  700 +
  701 + }
  702 +
  703 + //отправка предложения по e-mail
  704 + sendOffer() {
  705 +
  706 + const form = document.querySelector('.js_popup_sending_form');
  707 +
  708 + if (form) {
  709 +
  710 + form.addEventListener('submit', async (e) => {
  711 +
  712 + e.preventDefault();
  713 +
  714 + const formInputs = form.querySelectorAll('input');
  715 + const formBtn = form.querySelector('.js_form_btn');
  716 +
  717 + formInputs.forEach(input => { // перебираем все инпуты в форме;
  718 +
  719 + this.validateForm(input);
  720 +
  721 + input.addEventListener('input', () => {
  722 + this.validateForm(input);
  723 + });
  724 +
  725 + });
  726 +
  727 + if (!form.querySelector('.error')) { //проверяем, чтоб все инпуты прошли валидацию (чтоб не было в форме ни одного элемента с класссом error);
  728 +
  729 + // сюда пишем команды, которые должны сработать после успешной валидации;
  730 +
  731 + console.log('validate');
  732 + formBtn.classList.add('btn-animate');
  733 + formBtn.disabled = true;
  734 +
  735 + const formData = new FormData(form);
  736 +
  737 + console.log(...formData);
  738 +
  739 + const response = await fetch(e.target.action, {
  740 + method: e.target.method,
  741 + body: formData
  742 + });
  743 +
  744 + if (response.ok) {
  745 +
  746 + setTimeout(() => { // имитация отправки, когда отправка будет настроена, нужно достать всё из setTimeout() и удалить его;
  747 +
  748 + console.log('Отправлено');
  749 + formBtn.classList.remove('btn-animate');
  750 + formBtn.disabled = false;
  751 + if (document.querySelector('[data-popup="sending"]')) {
  752 + document.querySelector('[data-popup="sending"]').classList.remove('active');
  753 + }
  754 + this.fixBodyPosition();
  755 + form.reset();
  756 +
  757 + formInputs.forEach(input => {
  758 + input.classList.remove('no-error');
  759 + });
  760 +
  761 + }, 2000)
  762 +
  763 + } else {
  764 + formBtn.classList.remove('btn-animate');
  765 + formBtn.disabled = false;
  766 + alert('Ошибка');
  767 + }
  768 +
  769 + } else {
  770 + console.log('no-validate');
  771 + form.querySelector('.error').focus(); //фокус к полю с ошибкой;
  772 + }
  773 +
  774 + });
  775 +
  776 + }
  777 +
  778 + }
  779 +
  780 +
  781 + // карта на странице 'ЖК'
  782 + setComplexMap(id, coords, caption) {
  783 +
  784 + if (document.querySelector('#' + id)) {
  785 +
  786 + // Дождёмся загрузки API и готовности DOM.
  787 + ymaps.ready(init);
  788 +
  789 + function init() {
  790 + const map = new ymaps.Map(id, {
  791 + // При инициализации карты обязательно нужно указать её центр и коэффициент масштабирования.
  792 + center: coords,
  793 + zoom: 16,
  794 + controls: []
  795 + });
  796 +
  797 + // Создаём макет содержимого.
  798 + const MyIconContentLayout = ymaps.templateLayoutFactory.createClass(
  799 + '<div style="color: #FFFFFF; font-weight: bold;">$[properties.iconContent]</div>'
  800 + );
  801 +
  802 + // Создание макета содержимого хинта.
  803 + // Макет создается через фабрику макетов с помощью текстового шаблона.
  804 + const HintLayout = ymaps.templateLayoutFactory.createClass("<div class='my-hint'>" +
  805 + "{{ properties.object }}" + "</div>", {
  806 + // Определяем метод getShape, который
  807 + // будет возвращать размеры макета хинта.
  808 + // Это необходимо для того, чтобы хинт автоматически
  809 + // сдвигал позицию при выходе за пределы карты.
  810 + getShape: function () {
  811 + let el = this.getElement(),
  812 + result = null;
  813 + if (el) {
  814 + var firstChild = el.firstChild;
  815 + result = new ymaps.shape.Rectangle(
  816 + new ymaps.geometry.pixel.Rectangle([
  817 + [0, 0],
  818 + [firstChild.offsetWidth, firstChild.offsetHeight]
  819 + ])
  820 + );
  821 + }
  822 + return result;
  823 + }
  824 + }
  825 + );
  826 +
  827 + // метка
  828 + const placemark = new ymaps.Placemark(coords, {
  829 + // hintContent: caption,
  830 + // balloonContent: caption,
  831 + iconContent: '1',
  832 + // address: caption,
  833 + object: caption
  834 + }, {
  835 + iconLayout: 'default#imageWithContent',
  836 + iconImageHref: 'images/mark-complex.svg',
  837 + iconImageSize: [52, 67],
  838 + iconImageOffset: [-26, -67],
  839 + iconContentOffset: [0, 17],
  840 + iconContentLayout: MyIconContentLayout,
  841 + hintLayout: HintLayout
  842 + });
  843 +
  844 + map.geoObjects.add(placemark);
  845 +
  846 + }
  847 +
  848 + }
  849 +
  850 + }
  851 +
  852 +
  853 + // фильтры и сортировка на странице 'каталог'
  854 + setCatalogSorts() {
  855 +
  856 + const sortGroups = document.querySelectorAll('.js_sort_group');
  857 +
  858 + if (sortGroups.length) {
  859 +
  860 + sortGroups.forEach(group => {
  861 +
  862 + const sortGroupInput = group.querySelector('.js_sort_group_input');
  863 + const sortGroupCurrent = group.querySelector('.js_sort_group_current');
  864 + const sortGroupList = group.querySelector('.js_sort_group_list');
  865 + const sortGroupItems = group.querySelectorAll('.js_sort_group_item');
  866 +
  867 + const sendRequest = () => {
  868 +
  869 + const spinner = document.querySelector('.spinner'); // спиннер;
  870 +
  871 + spinner.classList.add('active');
  872 + document.body.classList.add('overlay');
  873 + /*this.fixBodyPosition();
  874 +
  875 + fetch('test.json')
  876 + .then(response => response.json())
  877 + .then(data => {
  878 +
  879 + console.log()
  880 +
  881 + setTimeout(() => { //имитация ответа сервера
  882 +
  883 + spinner.classList.remove('active');
  884 + document.body.classList.remove('overlay');
  885 + this.unfixBodyPosition();
  886 +
  887 + }, 3000);
  888 +
  889 + })
  890 + .catch(err => {
  891 + console.log(err);
  892 + });
  893 +
  894 + */
  895 + spinner.classList.remove('active');
  896 + document.body.classList.remove('overlay');
  897 +
  898 +
  899 + };
  900 +
  901 + sortGroupCurrent.addEventListener('click', () => {
  902 +
  903 + if (group.classList.contains('active')) {
  904 +
  905 + group.classList.remove('active');
  906 +
  907 + } else {
  908 +
  909 + sortGroups.forEach(group => {
  910 + group.classList.remove('active');
  911 + });
  912 +
  913 + group.classList.add('active');
  914 +
  915 + }
  916 +
  917 + });
  918 +
  919 + sortGroupItems.forEach(item => {
  920 +
  921 + item.addEventListener('click', () => {
  922 +
  923 + sortGroupItems.forEach(item => {
  924 + item.classList.remove('active');
  925 + });
  926 +
  927 + item.classList.add('active');
  928 + sortGroupCurrent.textContent = item.textContent;
  929 + sortGroupInput.value = item.dataset.val;
  930 + group.classList.remove('active');
  931 +
  932 + sendRequest();
  933 +
  934 + });
  935 +
  936 + });
  937 +
  938 + });
  939 +
  940 + document.addEventListener('click', (e) => {
  941 +
  942 + if (!e.target.closest('.js_sort_group_list') && !e.target.closest('.js_sort_group_current')) {
  943 +
  944 + sortGroups.forEach(group => {
  945 + group.classList.remove('active');
  946 + });
  947 +
  948 + }
  949 +
  950 + });
  951 +
  952 + }
  953 +
  954 + }
  955 +
  956 +
  957 + // слайдер на странице жк и на странице предложения
  958 + initIntroSlider() {
  959 +
  960 + let swiper3 = new Swiper('.intro__swiper', {
  961 + navigation: {
  962 + nextEl: '.swiper-button-next',
  963 + prevEl: '.swiper-button-prev',
  964 + },
  965 + pagination: {
  966 + el: '.swiper-pagination',
  967 + clickable: true,
  968 + },
  969 + slidesPerView: 1.1,
  970 + spaceBetween: 20,
  971 + breakpoints: {
  972 + 480: {
  973 + slidesPerView: 1.5,
  974 + },
  975 + 640: {
  976 + slidesPerView: 1.75,
  977 + },
  978 + 780: {
  979 + slidesPerView: 2.15,
  980 + },
  981 + 1024: {
  982 + slidesPerView: 3.5,
  983 + },
  984 + 1200: {
  985 + slidesPerView: 1,
  986 + }
  987 + }
  988 + });
  989 +
  990 + }
  991 +
  992 +
  993 + // табы на странице предложения
  994 + setTabs(tabs, items) {
  995 +
  996 + const offerSideTabs = document.querySelectorAll(tabs);
  997 + const offerSideItems = document.querySelectorAll(items);
  998 +
  999 + if (offerSideTabs) {
  1000 +
  1001 + offerSideTabs.forEach(tab => {
  1002 +
  1003 + tab.addEventListener('click', () => {
  1004 +
  1005 + offerSideTabs.forEach(tab => {
  1006 + tab.classList.remove('active');
  1007 + });
  1008 +
  1009 + tab.classList.add('active');
  1010 +
  1011 + offerSideItems.forEach(item => {
  1012 +
  1013 + item.classList.remove('active', 'fade');
  1014 +
  1015 + if (tab.dataset.tab == item.dataset.item) {
  1016 + item.classList.add('active', 'fade');
  1017 + }
  1018 +
  1019 + });
  1020 +
  1021 + });
  1022 +
  1023 + });
  1024 +
  1025 + }
  1026 +
  1027 + }
  1028 +
  1029 +
  1030 + // логика открытия нужного таба при открытии поп-апа с планами объекат и этажа на странице предложения
  1031 + sontrolOfferSidePopup() {
  1032 +
  1033 + const offerSideItems = document.querySelectorAll('.js_offer_side_item');
  1034 + const offerSidePopupTabs = document.querySelectorAll('.js_offer_side_popup_tab');
  1035 + const offerSidePopupItems = document.querySelectorAll('.js_offer_side_popup_item');
  1036 +
  1037 + if (offerSideItems) {
  1038 +
  1039 + offerSideItems.forEach(item => {
  1040 +
  1041 + const offerSideItemBtn = item.querySelector('.js_offer_side_item_btn');
  1042 +
  1043 + offerSideItemBtn.addEventListener('click', (e) => {
  1044 +
  1045 + e.preventDefault();
  1046 +
  1047 + offerSidePopupTabs.forEach(tab => {
  1048 +
  1049 + tab.classList.remove('active');
  1050 +
  1051 + if (item.dataset.item == tab.dataset.tab) {
  1052 + tab.classList.add('active');
  1053 + }
  1054 +
  1055 + });
  1056 +
  1057 + offerSidePopupItems.forEach(el => {
  1058 +
  1059 + el.classList.remove('active', 'fade');
  1060 +
  1061 + if (item.dataset.item == el.dataset.item) {
  1062 + el.classList.add('active', 'fade');
  1063 + }
  1064 +
  1065 + });
  1066 +
  1067 + });
  1068 +
  1069 + });
  1070 +
  1071 + }
  1072 +
  1073 + }
  1074 +
  1075 +
  1076 + // галлерея
  1077 + setCustomGallery() {
  1078 +
  1079 + let swiper4 = new Swiper(".img-viewer__thumbs-swiper", {
  1080 + slidesPerView: 3,
  1081 + spaceBetween: 8,
  1082 + // freeMode: true,
  1083 + observer: true,
  1084 + observeParents: true,
  1085 + observeSlideChildren: true,
  1086 + breakpoints: {
  1087 + 640: {
  1088 + spaceBetween: 10,
  1089 + },
  1090 + },
  1091 + });
  1092 +
  1093 + let swiper5 = new Swiper(".img-viewer__slider .swiper", {
  1094 + navigation: {
  1095 + nextEl: ".img-viewer__slider .swiper-button-next",
  1096 + prevEl: ".img-viewer__slider .swiper-button-prev",
  1097 + },
  1098 + slidesPerView: 1,
  1099 + spaceBetween: 20,
  1100 + thumbs: {
  1101 + swiper: swiper4
  1102 + },
  1103 + observer: true,
  1104 + observeParents: true,
  1105 + observeSlideChildren: true,
  1106 + });
  1107 +
  1108 + if (document.querySelector('.js_intro_item_btn')) {
  1109 +
  1110 + const imgViewer = document.querySelector('.js_img_viewer');
  1111 + const imgViewerCloses = imgViewer.querySelectorAll('.js_img_viewer_close');
  1112 + const imgViewerCaption = imgViewer.querySelector('.js_img_viewer_caption');
  1113 +
  1114 + const imgViewerSliderSwiper = imgViewer.querySelector('.js_img_viewer_slider_swiper');
  1115 + const imgViewerSliderSwiperWrap = imgViewerSliderSwiper.querySelector('.js_img_viewer_slider_swiper .swiper-wrapper');
  1116 +
  1117 + const imgViewerThumbsSwiper = imgViewer.querySelector('.js_img_viewer_thumbs_swiper');
  1118 + const imgViewerThumbsSwiperWrap = imgViewerThumbsSwiper.querySelector('.js_img_viewer_thumbs_swiper .swiper-wrapper');
  1119 +
  1120 + const introItemBtns = document.querySelectorAll('.js_intro_item_btn');
  1121 +
  1122 + introItemBtns.forEach((btn, i) => {
  1123 +
  1124 + btn.addEventListener('click', (e) => {
  1125 +
  1126 + e.preventDefault();
  1127 +
  1128 + imgViewer.classList.add('active');
  1129 + this.fixBodyPosition();
  1130 +
  1131 + imgViewerSliderSwiperWrap.innerHTML = '';
  1132 + imgViewerThumbsSwiperWrap.innerHTML = '';
  1133 + imgViewerCaption.textContent = '';
  1134 +
  1135 +
  1136 + introItemBtns.forEach(btn => {
  1137 +
  1138 + imgViewerSliderSwiperWrap.insertAdjacentHTML('beforeend', `
  1139 + <div class="swiper-slide">
  1140 + <img src="${btn.getAttribute('href')}" alt="">
  1141 + </div>`
  1142 + );
  1143 +
  1144 + imgViewerThumbsSwiperWrap.insertAdjacentHTML('beforeend', `
  1145 + <div class="swiper-slide">
  1146 + <img src="${btn.getAttribute('href')}" alt="">
  1147 + </div>`
  1148 + );
  1149 +
  1150 + });
  1151 +
  1152 + swiper4.update();
  1153 + swiper5.update();
  1154 + swiper5.slideTo(i);
  1155 + imgViewerCaption.textContent = btn.dataset.caption;
  1156 +
  1157 + });
  1158 +
  1159 + });
  1160 +
  1161 + swiper5.on('slideChange', function () {
  1162 + imgViewerCaption.textContent = introItemBtns[swiper5.realIndex].dataset.caption;
  1163 + });
  1164 +
  1165 + imgViewerCloses.forEach(close => {
  1166 +
  1167 + close.addEventListener('click', () => {
  1168 +
  1169 + imgViewer.classList.remove('active');
  1170 + this.unfixBodyPosition();
  1171 +
  1172 + });
  1173 +
  1174 + });
  1175 +
  1176 + }
  1177 +
  1178 + }
  1179 +
  1180 +
  1181 + // куки
  1182 + setCookies() {
  1183 +
  1184 + const cookies = document.querySelector('.js_cookies');
  1185 + const cookiesBtn = document.querySelector('.js_cookies_confirm');
  1186 + const cookiesTrigger = document.querySelector('.js_btn_cookies');
  1187 +
  1188 + if (cookiesTrigger) {
  1189 +
  1190 + cookiesTrigger.addEventListener('click', () => {
  1191 + cookies.classList.add('active');
  1192 + });
  1193 +
  1194 + };
  1195 +
  1196 + if (cookies) {
  1197 +
  1198 + cookiesBtn.addEventListener('click', () => {
  1199 + cookies.classList.remove('active');
  1200 + });
  1201 +
  1202 + };
  1203 +
  1204 + }
  1205 +
  1206 +
  1207 + // карта на странице карт;
  1208 + /*
  1209 + //setGeneralMap() {
  1210 +
  1211 + if (document.querySelector('#general-map')) {
  1212 +
  1213 + ymaps.ready(init); // Дождёмся загрузки API и готовности DOM;
  1214 +
  1215 + function init() {
  1216 +
  1217 + const myMap = new ymaps.Map('general-map', { // Создание экземпляра карты и его привязка к контейнеру с заданным id;
  1218 + center: [55.752933963675126, 37.52233749962665], // При инициализации карты обязательно нужно указать её центр и коэффициент масштабирования;
  1219 + zoom: 10,
  1220 + controls: [] // Скрываем элементы управления на карте;
  1221 + });
  1222 +
  1223 + // Создаём макет содержимого.
  1224 + const MyIconContentLayout = ymaps.templateLayoutFactory.createClass(
  1225 + '<div style="color: #FFFFFF; font-weight: bold;">$[properties.iconContent]</div>'
  1226 + );
  1227 +
  1228 + let collection = new ymaps.GeoObjectCollection(null, { // Создаём коллекцию, в которую будемпомещать метки (что-то типа массива);
  1229 + // preset: 'islands#yellowIcon'
  1230 + });
  1231 +
  1232 + let collectionCoords = [ // Создаём массив с координатами (координаты должны располагаться в том же порядке, что и адреса в списке на сайте);
  1233 + [55.867783219108354, 37.392867499999916],
  1234 + [55.728043075486504, 37.73937949999994],
  1235 + [55.72624100423305, 37.476078499999964],
  1236 + [55.80751105044832, 37.449622999999974],
  1237 + [55.601783098948836, 37.36700499999998],
  1238 + [55.86086502152225, 37.540348999999964],
  1239 + [55.784961528728715, 37.56188599999996],
  1240 + [55.63910010399773, 37.319407999999996],
  1241 + [55.55819256767507, 37.55711549999994],
  1242 + [55.79829252928473, 37.52063549999999],
  1243 + ];
  1244 +
  1245 + for (let i = 0, l = collectionCoords.length; i < l; i++) { // C помощью цикла добавляем все метки в коллекцию;
  1246 + collection.add(new ymaps.Placemark(collectionCoords[i]));
  1247 + collection.get(i).properties.set('iconContent', `${i + 1}`); // Добавляем каждой метке порядковый номер, записываем его в свойство 'iconContent';
  1248 + }
  1249 +
  1250 + myMap.geoObjects.add(collection); // Добавляем коллекцию с метками на карту;
  1251 +
  1252 + collection.options.set('iconLayout', 'default#imageWithContent'); // Необходимо указать данный тип макета;
  1253 + collection.options.set('iconImageHref', 'images/mark-complex.svg'); // Своё изображение иконки метки;
  1254 + collection.options.set('iconImageSize', [52, 67]); // Размеры метки;
  1255 + collection.options.set('iconImageOffset', [-26, -67]); // Смещение левого верхнего угла иконки относительно её "ножки" (точки привязки);
  1256 + collection.options.set('iconContentOffset', [0, 17]);
  1257 + collection.options.set('iconContentLayout', MyIconContentLayout); // Смещение левого верхнего угла иконки относительно её "ножки" (точки привязки);
  1258 +
  1259 + const pageMapBar = document.querySelector('.js_page_map_bar');
  1260 + const pageMapBarBtn = pageMapBar.querySelector('.js_page_map_bar_btn');
  1261 + const pageMapBarList = pageMapBar.querySelector('.js_page_map_bar_list');
  1262 + const pageMapBarCards = pageMapBar.querySelectorAll('.card-news');
  1263 +
  1264 + const showCard = (i) => {
  1265 +
  1266 + pageMapBarCards.forEach((card, k) => {
  1267 +
  1268 + card.classList.remove('active');
  1269 +
  1270 + if (i == k) {
  1271 + card.classList.add('active');
  1272 + }
  1273 +
  1274 + });
  1275 +
  1276 + };
  1277 +
  1278 + const hidecard = () => {
  1279 +
  1280 + pageMapBarCards.forEach(card => {
  1281 + card.classList.remove('active');
  1282 + });
  1283 +
  1284 + }
  1285 +
  1286 + let pageMapBarItems;
  1287 +
  1288 + pageMapBarBtn.addEventListener('click', () => {
  1289 + pageMapBar.classList.toggle('active');
  1290 + });
  1291 +
  1292 + pageMapBarList.addEventListener('click', (e) => {
  1293 +
  1294 + if (e.target.closest('.page-map-bar__item')) {
  1295 +
  1296 + pageMapBarItems = pageMapBarList.querySelectorAll('.page-map-bar__item');
  1297 +
  1298 + pageMapBarItems.forEach((item, i) => {
  1299 +
  1300 + if (e.target == item && e.target.classList.contains('active')) {
  1301 +
  1302 + item.classList.remove('active');
  1303 +
  1304 + hidecard();
  1305 +
  1306 + } else if (e.target == item) {
  1307 +
  1308 + pageMapBarItems.forEach(item => {
  1309 + item.classList.remove('active');
  1310 + });
  1311 +
  1312 + item.classList.add('active');
  1313 +
  1314 + let offsetCoords = collection.get(i).geometry.getCoordinates();
  1315 +
  1316 + offsetCoords = [
  1317 + offsetCoords[0] - 0.0025,
  1318 + offsetCoords[1]
  1319 + ];
  1320 +
  1321 + myMap.setZoom(16);
  1322 + // myMap.setCenter(collection.get(i).geometry.getCoordinates());
  1323 + myMap.setCenter(offsetCoords);
  1324 +
  1325 + showCard(i);
  1326 +
  1327 + }
  1328 +
  1329 + });
  1330 + }
  1331 +
  1332 + });
  1333 +
  1334 + collection.events.add('click', function (e) {
  1335 +
  1336 + for (let i = 0, l = collection.getLength(); i < l; i++) {
  1337 +
  1338 + if (e.get('target') == collection.get(i)) {
  1339 +
  1340 + pageMapBarItems = pageMapBarList.querySelectorAll('.page-map-bar__item');
  1341 +
  1342 + pageMapBarItems.forEach((item) => {
  1343 + pageMapBar.classList.add('active');
  1344 + item.classList.remove('active');
  1345 + });
  1346 +
  1347 + pageMapBarItems[i].classList.add('active');
  1348 +
  1349 + showCard(i);
  1350 +
  1351 + }
  1352 +
  1353 + }
  1354 +
  1355 + });
  1356 +
  1357 + }
  1358 +
  1359 + }
  1360 +
  1361 + };*/
  1362 +
  1363 +
  1364 + // аккордеон в футере
  1365 + setFooterSpoilers() {
  1366 +
  1367 + const items = document.querySelectorAll('.js_footer_col');
  1368 +
  1369 + items.forEach(item => {
  1370 +
  1371 + const itemTitle = item.querySelector('.js_footer_caption');
  1372 + const itemContent = item.querySelector('.js_footer_block');
  1373 +
  1374 + const blockToggle = (block, duration) => {
  1375 +
  1376 + if (window.getComputedStyle(block).display == "none" && !block.classList.contains('smooth')) {
  1377 +
  1378 + block.style.display = "block";
  1379 +
  1380 + const blockHeight = block.offsetHeight;
  1381 +
  1382 + block.style.height = 0;
  1383 + block.style.overflow = "hidden";
  1384 + block.style.transition = `height ${duration}ms ease`;
  1385 + block.classList.add('smooth');
  1386 + block.offsetHeight;
  1387 + block.style.height = `${blockHeight}px`;
  1388 +
  1389 + setTimeout(() => {
  1390 +
  1391 + block.classList.remove('smooth');
  1392 + block.style.height = '';
  1393 + block.style.transition = '';
  1394 + block.style.overflow = '';
  1395 +
  1396 + }, duration);
  1397 +
  1398 + } else if (!block.classList.contains('smooth')) {
  1399 +
  1400 + block.style.height = `${block.offsetHeight}px`;
  1401 + block.offsetHeight;
  1402 + block.style.height = 0;
  1403 + block.style.overflow = "hidden";
  1404 + block.style.transition = `height ${duration}ms ease`;
  1405 + block.classList.add('smooth');
  1406 +
  1407 + setTimeout(() => {
  1408 +
  1409 + block.classList.remove('smooth');
  1410 + block.style.display = "none";
  1411 + block.style.height = '';
  1412 + block.style.transition = '';
  1413 + block.style.overflow = '';
  1414 +
  1415 + }, duration);
  1416 +
  1417 + }
  1418 +
  1419 + };
  1420 +
  1421 + itemTitle.addEventListener('click', (e) => {
  1422 + itemTitle.classList.toggle('active');
  1423 + blockToggle(itemContent, 300);
  1424 + });
  1425 +
  1426 + });
  1427 +
  1428 + }
  1429 +
  1430 +
  1431 + // слайдер с партнёрами;
  1432 + initPartnerslSlider() {
  1433 +
  1434 + const slider = document.querySelector('.partners__swiper');
  1435 +
  1436 + if (slider) {
  1437 +
  1438 + let swiper6;
  1439 +
  1440 + const initSlider = () => {
  1441 +
  1442 + swiper6 = new Swiper(slider, {
  1443 + // scrollbar: {
  1444 + // el: '.swiper-scrollbar',
  1445 + // draggable: true,
  1446 + // },
  1447 + slidesPerView: 0.275,
  1448 + loop: true,
  1449 + spaceBetween: 20,
  1450 + freeMode: true,
  1451 + allowTouchMove: true,
  1452 + breakpoints: {
  1453 + 480: {
  1454 + slidesPerView: 0.4,
  1455 + },
  1456 + 640: {
  1457 + slidesPerView: 0.65,
  1458 + },
  1459 + 780: {
  1460 + slidesPerView: 0.65,
  1461 + },
  1462 + 1024: {
  1463 + slidesPerView: 0.8,
  1464 + },
  1465 + 1200: {
  1466 + slidesPerView: 1,
  1467 + loop: false,
  1468 + allowTouchMove: false,
  1469 + }
  1470 + }
  1471 + });
  1472 +
  1473 + };
  1474 +
  1475 + initSlider();
  1476 +
  1477 + const updateSlider = () => {
  1478 + swiper6.destroy();
  1479 + initSlider();
  1480 + }
  1481 +
  1482 + window.addEventListener('resize', () => {
  1483 +
  1484 + if (window.innerWidth <= 1200 && slider.dataset.mobile == 'false') {
  1485 + slider.dataset.mobile = 'true';
  1486 + updateSlider();
  1487 + }
  1488 +
  1489 + if (window.innerWidth > 1200 && slider.dataset.mobile == 'true') {
  1490 + slider.dataset.mobile = 'false';
  1491 + updateSlider();
  1492 + }
  1493 +
  1494 + });
  1495 +
  1496 + }
  1497 +
  1498 + }
  1499 +
  1500 +}
  1501 +
  1502 +
  1503 +document.addEventListener('DOMContentLoaded', () => {
  1504 +
  1505 + const app = new App();
  1506 + app.init();
  1507 +
  1508 +});
public/pug/templates/scripts.pug
... ... @@ -2,4 +2,4 @@
2 2  
3 3 script(src="https://api-maps.yandex.ru/2.1/?lang=ru_RU")
4 4 script(src="js/swiper-bundle.min.js")
5   -script(src="js/main.js")
6 5 \ No newline at end of file
  6 +script(src="js/main_new.js")
resources/views/admin/houses/form.blade.php
... ... @@ -474,9 +474,13 @@
474 474 <input type="text" class="form-control_ txt" name="scheme_deal" placeholder="Схема сделки"
475 475 required maxlength="100" style="width: 80%" value="{{ old('scheme_deal') ?? $house->scheme_deal ?? '' }}"><br><br>
476 476  
477   -<label for="map_coord">Координаты дома: </label><br>
478   -<input type="text" class="form-control_ txt" name="map_coord" placeholder="Координаты дома"
479   - required maxlength="100" value="{{ old('map_coord') ?? $house->map_coord ?? '' }}"><br><br>
  477 +<label for="coord_x">Координаты дома X: </label><br>
  478 +<input type="text" class="form-control_ txt" name="coord_x" placeholder="Координаты дома X"
  479 + required maxlength="100" value="{{ old('coord_x') ?? $house->coord_x ?? '0' }}"><br><br>
  480 +
  481 +<label for="coord_y">Координаты дома Y: </label><br>
  482 +<input type="text" class="form-control_ txt" name="coord_y" placeholder="Координаты дома Y"
  483 + required maxlength="100" value="{{ old('coord_y') ?? $house->coord_y ?? '0' }}"><br><br>
480 484  
481 485 <label for="sos_obj">Состояние объекта: </label><br>
482 486 @error('sos_obj')
resources/views/index.blade.php
... ... @@ -256,7 +256,7 @@
256 256 </div><a class="card__btn btn btn--bordered" href="#">Подробнее об аренде</a>
257 257 </div>
258 258 </div>
259   - </div>-->
  259 + </div> --->
260 260 </div>
261 261 </div>
262 262 <div class="swiper-pagination"></div>
resources/views/layout/admin.blade.php
... ... @@ -457,7 +457,7 @@
457 457 </div>
458 458 <script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU"></script>
459 459 <script src="{{ asset('js/swiper-bundle.min.js') }}"></script>
460   -<script src="{{ asset('js/main.js') }}"></script>
  460 +<script src="{{ asset('js/main_new.js') }}"></script>
461 461 <script type="text/javascript" src="{{ asset('js/jquery.min.js') }}"></script>
462 462 <script type="text/javascript" src="{{ asset('js/jquery.cookie.js') }}"></script>
463 463 @yield('custom_js')
resources/views/layout/site.blade.php
... ... @@ -413,7 +413,7 @@
413 413 </div>
414 414 <script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU"></script>
415 415 <script src="{{ asset('js/swiper-bundle.min.js') }}"></script>
416   -<script src="{{ asset('js/main.js') }}"></script>
  416 +<script src="{{ asset('js/main_new.js') }}"></script>
417 417 <script type="text/javascript" src="{{ asset('js/jquery.min.js') }}"></script>
418 418 <script type="text/javascript" src="{{ asset('js/jquery.cookie.js') }}"></script>
419 419 @yield('custom_js')
resources/views/mapsobj.blade.php
... ... @@ -6,6 +6,168 @@
6 6  
7 7 @section('custom_js')
8 8 @include('js.filter_value')
  9 + <script>
  10 + function setGeneralMap() {
  11 +
  12 + if (document.querySelector('#general-map')) {
  13 +
  14 + ymaps.ready(init); // Дождёмся загрузки API и готовности DOM;
  15 +
  16 + function init() {
  17 +
  18 + const myMap = new ymaps.Map('general-map', { // Создание экземпляра карты и его привязка к контейнеру с заданным id;
  19 + center: [55.752933963675126, 37.52233749962665], // При инициализации карты обязательно нужно указать её центр и коэффициент масштабирования;
  20 + zoom: 10,
  21 + controls: [] // Скрываем элементы управления на карте;
  22 + });
  23 +
  24 + // Создаём макет содержимого.
  25 + const MyIconContentLayout = ymaps.templateLayoutFactory.createClass(
  26 + '<div style="color: #FFFFFF; font-weight: bold;">$[properties.iconContent]</div>'
  27 + );
  28 +
  29 + let collection = new ymaps.GeoObjectCollection(null, { // Создаём коллекцию, в которую будемпомещать метки (что-то типа массива);
  30 + // preset: 'islands#yellowIcon'
  31 + });
  32 +
  33 + let collectionCoords = [ // Создаём массив с координатами (координаты должны располагаться в том же порядке, что и адреса в списке на сайте);
  34 + @if ($areas->count())
  35 + @foreach ($areas as $area)
  36 + [{{ $area->coord_x }}, {{$area->coord_y}}],
  37 + @endforeach
  38 + @endif
  39 + // [55.867783219108354, 37.392867499999916],
  40 + // [55.728043075486504, 37.73937949999994],
  41 + // [55.72624100423305, 37.476078499999964],
  42 + // [55.80751105044832, 37.449622999999974],
  43 + // [55.601783098948836, 37.36700499999998],
  44 + // [55.86086502152225, 37.540348999999964],
  45 + // [55.784961528728715, 37.56188599999996],
  46 + // [55.63910010399773, 37.319407999999996],
  47 + // [55.55819256767507, 37.55711549999994],
  48 + // [55.79829252928473, 37.52063549999999],
  49 + ];
  50 +
  51 + for (let i = 0, l = collectionCoords.length; i < l; i++) { // C помощью цикла добавляем все метки в коллекцию;
  52 + collection.add(new ymaps.Placemark(collectionCoords[i]));
  53 + collection.get(i).properties.set('iconContent', `${i + 1}`); // Добавляем каждой метке порядковый номер, записываем его в свойство 'iconContent';
  54 + }
  55 +
  56 + myMap.geoObjects.add(collection); // Добавляем коллекцию с метками на карту;
  57 +
  58 + collection.options.set('iconLayout', 'default#imageWithContent'); // Необходимо указать данный тип макета;
  59 + collection.options.set('iconImageHref', 'images/mark-complex.svg'); // Своё изображение иконки метки;
  60 + collection.options.set('iconImageSize', [52, 67]); // Размеры метки;
  61 + collection.options.set('iconImageOffset', [-26, -67]); // Смещение левого верхнего угла иконки относительно её "ножки" (точки привязки);
  62 + collection.options.set('iconContentOffset', [0, 17]);
  63 + collection.options.set('iconContentLayout', MyIconContentLayout); // Смещение левого верхнего угла иконки относительно её "ножки" (точки привязки);
  64 +
  65 + const pageMapBar = document.querySelector('.js_page_map_bar');
  66 + const pageMapBarBtn = pageMapBar.querySelector('.js_page_map_bar_btn');
  67 + const pageMapBarList = pageMapBar.querySelector('.js_page_map_bar_list');
  68 + const pageMapBarCards = pageMapBar.querySelectorAll('.card-news');
  69 +
  70 + const showCard = (i) => {
  71 +
  72 + pageMapBarCards.forEach((card, k) => {
  73 +
  74 + card.classList.remove('active');
  75 +
  76 + if (i == k) {
  77 + card.classList.add('active');
  78 + }
  79 +
  80 + });
  81 +
  82 + };
  83 +
  84 + const hidecard = () => {
  85 +
  86 + pageMapBarCards.forEach(card => {
  87 + card.classList.remove('active');
  88 + });
  89 +
  90 + }
  91 +
  92 + let pageMapBarItems;
  93 +
  94 + pageMapBarBtn.addEventListener('click', () => {
  95 + pageMapBar.classList.toggle('active');
  96 + });
  97 +
  98 + pageMapBarList.addEventListener('click', (e) => {
  99 +
  100 + if (e.target.closest('.page-map-bar__item')) {
  101 +
  102 + pageMapBarItems = pageMapBarList.querySelectorAll('.page-map-bar__item');
  103 +
  104 + pageMapBarItems.forEach((item, i) => {
  105 +
  106 + if (e.target == item && e.target.classList.contains('active')) {
  107 +
  108 + item.classList.remove('active');
  109 +
  110 + hidecard();
  111 +
  112 + } else if (e.target == item) {
  113 +
  114 + pageMapBarItems.forEach(item => {
  115 + item.classList.remove('active');
  116 + });
  117 +
  118 + item.classList.add('active');
  119 +
  120 + let offsetCoords = collection.get(i).geometry.getCoordinates();
  121 +
  122 + offsetCoords = [
  123 + offsetCoords[0] - 0.0025,
  124 + offsetCoords[1]
  125 + ];
  126 +
  127 + myMap.setZoom(16);
  128 + // myMap.setCenter(collection.get(i).geometry.getCoordinates());
  129 + myMap.setCenter(offsetCoords);
  130 +
  131 + showCard(i);
  132 +
  133 + }
  134 +
  135 + });
  136 + }
  137 +
  138 + });
  139 +
  140 + collection.events.add('click', function (e) {
  141 +
  142 + for (let i = 0, l = collection.getLength(); i < l; i++) {
  143 +
  144 + if (e.get('target') == collection.get(i)) {
  145 +
  146 + pageMapBarItems = pageMapBarList.querySelectorAll('.page-map-bar__item');
  147 +
  148 + pageMapBarItems.forEach((item) => {
  149 + pageMapBar.classList.add('active');
  150 + item.classList.remove('active');
  151 + });
  152 +
  153 + pageMapBarItems[i].classList.add('active');
  154 +
  155 + showCard(i);
  156 +
  157 + }
  158 +
  159 + }
  160 +
  161 + });
  162 +
  163 + }
  164 +
  165 + }
  166 +
  167 + }
  168 +
  169 + setGeneralMap();
  170 + </script>
9 171 @endsection
10 172  
11 173 @section('content')
... ... @@ -24,7 +186,12 @@
24 186 </button>
25 187 </div>
26 188 <ul class="page-map-bar__list js_page_map_bar_list">
27   - <li class="page-map-bar__item">ЖК Большое Путилково</li>
  189 + @if ($areas->count())
  190 + @foreach ($areas as $area)
  191 + <li class="page-map-bar__item">{{ $area->name_area }}</li>
  192 + @endforeach
  193 + @endif
  194 + <!--<li class="page-map-bar__item">ЖК Большое Путилково</li>
28 195 <li class="page-map-bar__item">ЖК Среда</li>
29 196 <li class="page-map-bar__item">ЖК Квартал Триумфальный</li>
30 197 <li class="page-map-bar__item">ЖК Алые Паруса</li>
... ... @@ -33,9 +200,26 @@
33 200 <li class="page-map-bar__item">ЖК Царская Площадь</li>
34 201 <li class="page-map-bar__item">ЖК Переделкино Ближнее</li>
35 202 <li class="page-map-bar__item">ЖК Этолон Cити</li>
36   - <li class="page-map-bar__item">ЖК Триумф Палас</li>
  203 + <li class="page-map-bar__item">ЖК Триумф Палас</li>-->
37 204 </ul>
38   - <div class="card-news">
  205 + @if ($areas->count())
  206 + @foreach ($areas as $area)
  207 + <div class="card-news">
  208 + <div class="card-news__top"><img src="{{ asset(Storage::url($area->foto_main)) }}" alt="Превью по {{$area->name_area}}" loading="lazy">
  209 + <div class="card-news__date"><span>{{ $area->name_area }}</span><span></span></div>
  210 + </div>
  211 + <div class="card-news__cnt">
  212 + <p class="card-news__descr">{{ $area->description }}</p>
  213 + <a class="card-news__link" href="{{ route('complex', ['area' => $area->id]) }}">Подробнее
  214 + <svg width="17" height="12">
  215 + <use xlink:href="{{ asset('images/sprite.svg#card-news-link-arrow') }}"></use>
  216 + </svg>
  217 + </a>
  218 + </div>
  219 + </div>
  220 + @endforeach
  221 + @endif
  222 + <!--<div class="card-news">
39 223 <div class="card-news__top"><img src="images/card/card-img-5.jpg" alt="Превью к новости" loading="lazy">
40 224 <div class="card-news__date"><span>ЖК Большое Путилково</span><span></span></div>
41 225 </div>
... ... @@ -144,7 +328,7 @@
144 328 <use xlink:href="images/sprite.svg#card-news-link-arrow"></use>
145 329 </svg></a>
146 330 </div>
147   - </div>
  331 + </div>-->
148 332 </div>
149 333 </div>
150 334 </div>