PageSourceSearch

https://stresslessmoscow.ru/catalog/view/theme/default/assets/js/index.js?v1.19

js stresslessmoscow.ru collected 2026-10-04 03:32:09 UTC 46,103 bytes, 1,641 lines download raw bytes

1// Flash
2// class Flash {
3//     constructor(flash) {
4//         this.flash = flash
5//
6//         if (this.flash.classList.contains('active')) {
7//             this.flash.classList.remove('active')
8//         }
9//
10//         document.querySelectorAll('a').forEach(link => link.addEventListener('click', (e) => {
11//
12//
13//             this.flash.classList.add('active')
14//
15//             if (e.target.getAttribute('href')) {
16//                 e.preventDefault()
17//                 let link = e.target.getAttribute('href')
18//             } else if (e.target.closest('a').getAttribute('href')) {
19//                 e.preventDefault()
20//                 let link = e.target.closest('a').getAttribute('href')
21//             } else {
22//
23//             }
24//
25//             setTimeout(() => {
26//                 location.href = link
27//             }, 400)
28//         }))
29//     }
30//
31//     static init() {
32//         const flash = document.querySelector('.flash')
33//         new Flash(flash)
34//     }
35// }
36// document.body.onload = () => Flash.init()
37
38class Video {
39    constructor(video) {
40        this.video = video
41
42        this.poster = this.video.querySelector('.video-poster')
43        this.poster.addEventListener('click', this.play.bind(this))
44
45        this.content = this.video.querySelector('.video-content')
46    }
47
48    play() {
49        this.poster.style.display = 'none'
50
51        const src = this.content.dataset.src
52        const source = Functions.htmlToElement(src)
53
54        this.content.innerHTML = ''
55        this.content.append(source)
56
57        this.content.style.display = 'flex'
58    }
59
60    static init() {
61        const videos = document.querySelectorAll('.video')
62
63        if (videos) {
64            videos.forEach(video => new Video(video))
65        }
66    }
67}
68
69Video.init()
70
71// Карта в контактах
72class Map {
73
74    constructor(map) {
75        this.map = map
76
77        window.addEventListener('resize', this.setSize.bind(this))
78
79        this.setSize()
80    }
81
82    setSize() {
83
84        // const width = innerWidth - this.map.getBoundingClientRect().left
85        // this.map.style.width = width + 'px'
86
87    }
88
89    static init() {
90        const map = document.querySelector('.map-fixed')
91
92        if (map) {
93            new Map(map)
94        }
95    }
96}
97
98Map.init()
99
100// Контроллео модальных окон
101class Modal {
102
103    constructor(modal_type, opener = null, options = null) {
104
105        this.modal = document.querySelector('.modal-' + modal_type)
106        this.modal_type = modal_type
107
108        if (this.modal) {
109            this.opener = opener
110            this.subject = this.opener?.dataset?.subject || null
111            this.link = this.opener?.dataset?.link || null
112            this.is_open = false
113
114            this.open()
115
116            if (this.subject) {
117                if (this.modal.querySelector('[name=subject]')) this.modal.querySelector('[name=subject]').value = this.subject
118            } else {
119                if (this.modal.querySelector('[name=subject]')) this.modal.querySelector('[name=subject]').value = ''
120            }
121
122            if (this.link) {
123                if (this.modal.querySelector('[name=link]')) this.modal.querySelector('[name=link]').value = this.link
124            } else {
125                if (this.modal.querySelector('[name=link]')) this.modal.querySelector('[name=link]').value = ''
126            }
127
128            if (options) {
129                if (this.modal.querySelector('[name=options]')) this.modal.querySelector('[name=options]').value = options.join(' / ')
130            }
131
132            // Закрытие
133            this.closer = this.modal.querySelectorAll('.modal__close')
134            this.closer.forEach(button => {
135                button.addEventListener('click', e => {
136                    e.preventDefault()
137                    this.close()
138                })
139            })
140
141            // Отправка
142            if (this.modal.querySelector('.modal__form')) {
143                this.form = this.modal.querySelector('form')
144
145                if (this.form) {
146                    this.form.addEventListener('submit', this.submit.bind(this))
147                }
148            }
149        }
150    }
151
152    open() {
153        this.modal.removeAttribute('hidden')
154        this.modal.classList.add('open')
155        this.is_open = true
156
157        if (this.modal_type == 'constructor') {
158            const iframe_src = this.opener.dataset.iframe
159            const modal_content = this.modal.querySelector('.modal__content iframe')
160
161            modal_content.src = iframe_src
162        }
163    }
164
165    close() {
166        return new Promise((resolve, reject) => {
167            this.modal.classList.remove('open')
168
169            setTimeout(() => {
170                this.modal.setAttribute('hidden', true)
171                this.is_open = false
172                this.form.reset()
173
174                resolve()
175            }, 200)
176        })
177    }
178
179    async submit(e) {
180        e.preventDefault()
181
182        // await recaptha()
183
184        const url = this.form.action
185        const formData = new FormData(this.form)
186
187        // formData.append('recaptcha_response', window.grecaptcha.getResponse())
188
189        const submit_button = this.form.querySelector('button')
190        const temp_button_text = submit_button.innerText
191
192        submit_button.innerText = 'Подождите...'
193        submit_button.disabled = true
194
195        formData.append('type', this.modal_type)
196
197        let response = await fetch(url, {
198            method: 'POST',
199            body: formData
200        })
201
202        response = await response.json()
203
204        if (response.success) {
205            this.form.reset()
206            ym(86709843, 'reachGoal', 'formsend')
207            console.log('send')
208
209            if (response.redirect) {
210                let baseUrl = window.location.origin;
211                window.location.href = baseUrl + response.redirect;
212            } else {
213                this.getSuccess()
214            }
215        } else if (response.error) {
216            Alert.add(response.error, 'error')
217        }
218
219        submit_button.innerText = temp_button_text
220        submit_button.disabled = false
221
222    }
223
224    getSuccess() {
225        this.close()
226
227        new Modal('success')
228    }
229
230    static init() {
231        let openers = document.querySelectorAll('[data-modal]')
232
233        if (openers) {
234            openers.forEach(function (opener) {
235                opener.addEventListener('click', function (e) {
236                    e.preventDefault()
237
238                    const modal_type = opener.dataset.modal
239
240                    if (modal_type) {
241                        new Modal(modal_type, opener)
242                    }
243                })
244            })
245        }
246    }
247}
248
249Modal.init()
250
251class Coop {
252    constructor(coop) {
253        this.coop = coop
254
255        this.form = this.coop.querySelector('form')
256        this.form.addEventListener('submit', this.submit.bind(this))
257    }
258
259    async submit(e) {
260        e.preventDefault()
261
262        const url = this.form.action
263        const formData = new FormData(this.form)
264
265        let response = await fetch(url, {
266            method: 'POST',
267            body: formData
268        })
269
270        response = await response.json()
271
272        if (response.success) {
273            this.form.reset()
274            let baseUrl = window.location.origin;
275            let path = "/index.php?route=information/thanks";
276            window.location.href = baseUrl + path;
277            //new Modal('success', null)
278        } else if (response.error) {
279            Alert.add(response.error, 'error')
280        }
281    }
282
283    static init() {
284        const coop = document.querySelector('.for-designer__form')
285
286        if (coop) {
287            new Coop(coop)
288        }
289    }
290}
291
292Coop.init()
293
294// Товар
295class Product {
296    constructor(product) {
297        this.product = product
298
299        this.name = this.product.querySelector('.product-page__title').innerText
300
301        this.submit = this.product.querySelector('.product-page__get')
302        this.submit.addEventListener('click', this.checked.bind(this))
303
304        // this.rass = this.product.querySelector('.product-page__rass')
305        // this.rass.addEventListener('click', this.rassModal.bind(this))
306        //
307        // this.cred = this.product.querySelector('.product-page__cred')
308        // this.cred.addEventListener('click', this.credModal.bind(this))
309
310        this.options = this.product.querySelectorAll('.product-page__option')
311    }
312
313    checked() {
314        let valid = true
315
316        // this.options.forEach(option => {
317        //     if (option.querySelector('.select') && option.querySelector('.select input').value == '') {
318        //         valid = false
319        //     }
320        //
321        //     if ((option.querySelector('.product-page__colors-1') || option.querySelector('.product-page__colors-2')) && !option.querySelector('input:checked')) {
322        //         valid = false
323        //     }
324        // })
325
326        if (valid) {
327            this.openModal()
328        } else {
329            Alert.add('Выберите все значения', 'error')
330        }
331    }
332
333    openModal() {
334        let options = []
335
336        this.options.forEach(option => {
337            let title = option.querySelector('.product-page__label').innerText
338            let value = ''
339
340            if (option.querySelector('.select')) {
341                value = option.querySelector('.select input').value
342            }
343
344            if ((option.querySelector('.product-page__colors-1') || option.querySelector('.product-page__colors-2'))) {
345                value = option.querySelector('input:checked').value
346            }
347
348            options.push(`${title}: ${value}`)
349        })
350
351        new Modal('get-price', this.submit, options)
352    }
353
354    rassModal() {
355        let options = []
356
357        this.options.forEach(option => {
358            let title = option.querySelector('.product-page__label').innerText
359            let value = ''
360
361            if (option.querySelector('.select')) {
362                value = option.querySelector('.select input').value
363            }
364
365            if ((option.querySelector('.product-page__colors-1') || option.querySelector('.product-page__colors-2'))) {
366                value = option.querySelector('input:checked').value
367            }
368
369            options.push(`${title}: ${value}`)
370        })
371
372        new Modal('rass', this.submit, options)
373    }
374
375    credModal() {
376        let options = []
377
378        this.options.forEach(option => {
379            let title = option.querySelector('.product-page__label').innerText
380            let value = ''
381
382            if (option.querySelector('.select')) {
383                value = option.querySelector('.select input').value
384            }
385
386            if ((option.querySelector('.product-page__colors-1') || option.querySelector('.product-page__colors-2'))) {
387                value = option.querySelector('input:checked').value
388            }
389
390            options.push(`${title}: ${value}`)
391        })
392
393        new Modal('cred', this.submit, options)
394    }
395
396    static init() {
397        const product = document.querySelector('.product-page')
398
399        if (product) {
400            new Product(product)
401        }
402    }
403}
404
405Product.init()
406
407// Фильтры
408class Filter {
409    constructor(filter) {
410        this.filter = filter
411
412        this.filter_inputs = this.filter.querySelectorAll('input')
413        this.btnApply = this.filter.querySelector('[data-filter-apply]');
414        this.btnApply.addEventListener('click', this.change.bind(this));
415        // this.filter_inputs.forEach(filter => filter.addEventListener('change', this.change.bind(this)))
416
417        this.is_open = false
418
419        this.filter_toggler = this.filter.querySelector('.category__filter-toggler')
420        this.filter_toggler.addEventListener('click', this.open.bind(this))
421
422        if (this.filter) {
423            this.filter.addEventListener('click', (e) => {
424                const target = e.target;
425                if (target.closest('[data-toggler-parent]') || target.hasAttribute('data-toggler')) {
426                    const toggler = target.closest('[data-toggler-parent]');
427                    const parent = toggler.closest('[data-toggler-parent]');
428                    if (parent) {
429                        parent.classList.toggle('open');
430                    }
431                }
432            });
433        }
434    }
435
436    open() {
437        if (this.is_open) {
438            this.filter.classList.remove('open')
439        } else {
440            this.filter.classList.add('open')
441        }
442
443        this.is_open = !this.is_open
444    }
445
446    change(e) {
447        const url = new URL(location.href)
448        let search = url.search
449        search = url.search.split('&')
450
451        this.filter_inputs.forEach(input => {
452            let name = input.name
453            let values = []
454
455            this.filter.querySelectorAll(`[name="${name}"]`).forEach(item => {
456                if (item.checked) {
457                    values.push(item.value)
458                }
459            })
460
461            values = values.join('_')
462
463            if (values) {
464                let value = input.value
465                let in_params = false
466
467                for (var i = 0; i < search.length; i++) {
468                    let key = search[i].split('=')[0]
469                    if (name == key) {
470                        search[i] = name + '=' + values
471                        in_params = true
472                    }
473                }
474
475                if (!in_params) {
476                    search.push(name + '=' + values)
477                }
478
479            } else {
480                for (var i = 0; i < search.length; i++) {
481                    let key = search[i].split('=')[0]
482
483                    if (key[0] == '?')
484                        key = key.substring(1, key.length)
485
486                    if (name == key) {
487                        search.splice(i, 1)
488                    }
489                }
490            }
491
492        })
493
494        console.log(search)
495        console.log(search.join('&'))
496
497        location = url.origin + url.pathname + search.join('&')
498    }
499
500    static init() {
501        const filter = document.querySelector('.category__filters-list')
502
503        if (filter) {
504            new Filter(filter)
505        }
506    }
507}
508
509Filter.init()
510
511// Гамбургер
512class Menu {
513    constructor(menu) {
514        this.menu = menu
515        this.menu_open = document.querySelector('.header__menubtn')
516        this.menu_close = this.menu.querySelector('.menu__close')
517
518        this.menu_open.addEventListener('click', this.open.bind(this))
519        this.menu_close.addEventListener('click', this.close.bind(this))
520
521        this.togglers = this.menu.querySelectorAll('.menu__indicator')
522        this.togglers.forEach(toggler => toggler.addEventListener('click', this.toggle.bind(this)))
523    }
524
525    toggle(e) {
526        const parrent = e.target.closest('.menu__block')
527        parrent.classList.toggle('open')
528    }
529
530    open() {
531        this.menu.classList.add('open')
532    }
533
534    close() {
535        this.menu.classList.remove('open')
536    }
537
538    static init() {
539        const menu = document.querySelector('.menu')
540
541        if (menu) {
542            new Menu(menu)
543        }
544    }
545}
546
547Menu.init()
548
549// Маска телефона
550class Mask {
551
552    constructor(phone) {
553        this.phone = phone
554
555        if (!this.phone) return console.log('Что-то не так с селектором');
556
557        this.layout = '+7 (___) ___-__-__';
558        this.maskreg = this.getRegexp();
559
560        this.setListeners();
561    }
562
563    getRegexp() {
564        var str = this.layout.replace(/_/g, '\\d')
565        str = str.replace(/\(/g, '\\(')
566        str = str.replace(/\)/g, '\\)')
567        str = str.replace(/\+/g, '\\+')
568        str = str.replace(/\s/g, '\\s')
569
570        return str;
571    }
572
573    mask(e) {
574        var _this = e.target,
575            matrix = this.layout,
576            i = 0,
577            def = matrix.replace(/\D/g, ""),
578            val = _this.value.replace(/\D/g, "");
579
580        if (def.length >= val.length) val = def;
581
582        _this.value = matrix.replace(/./g, function (a) {
583            return /[_\d]/.test(a) && i < val.length ? val.charAt(i++) : i >= val.length ? "" : a
584        });
585
586        if (e.type == "blur") {
587            var regexp = new RegExp(this.maskreg);
588            if (!regexp.test(_this.value)) _this.value = "";
589        } else {
590            this.setCursorPosition(_this.value.length, _this);
591        }
592    }
593
594    setCursorPosition(pos, elem) {
595        elem.focus();
596        if (elem.setSelectionRange) elem.setSelectionRange(pos, pos);
597        else if (elem.createTextRange) {
598            var range = elem.createTextRange();
599            range.collapse(true);
600            range.moveEnd("character", pos);
601            range.moveStart("character", pos);
602            range.select()
603        }
604    }
605
606    setListeners() {
607        this.phone.addEventListener("input", this.mask.bind(this), false);
608        this.phone.addEventListener("focus", this.mask.bind(this), false);
609        this.phone.addEventListener("blur", this.mask.bind(this), false);
610    }
611
612    getElement(selector) {
613        if (selector === undefined) return false;
614        if (this.isElement(selector)) return selector;
615        if (typeof selector == 'string') {
616            var el = document.querySelector(selector);
617            if (this.isElement(el)) return el;
618        }
619        return false
620    }
621
622    isElement(element) {
623        return element instanceof Element || element instanceof HTMLDocument;
624    }
625
626    static init() {
627        const phones = document.querySelectorAll('[type=tel]:not(.no-mask)')
628
629        if (phones.length) {
630            phones.forEach(phone => new Mask(phone))
631        }
632    }
633}
634
635Mask.init()
636
637// Поиск
638class Search {
639    constructor(search) {
640        this.search = search
641        this.search.addEventListener('submit', this.to_search.bind(this))
642
643        this.search_input = this.search.querySelector('[name=search]')
644
645        if (document.querySelector('.search')) {
646            this.search_box = document.querySelector('.search')
647
648            this.search_open_button = document.querySelector('.header__search')
649            this.search_open_button.addEventListener('click', this.open.bind(this))
650
651            this.search_close_button = this.search_box.querySelector('.search__close')
652            this.search_close_button.addEventListener('click', this.close.bind(this))
653        }
654    }
655
656    open() {
657        this.search_box.classList.add('open')
658    }
659
660    close() {
661        this.search_box.classList.remove('open')
662    }
663
664    to_search(e) {
665        e.preventDefault()
666
667        if (this.search_input.value.length > 1) {
668            const url = '/index.php?route=product/search&search=' + this.search_input.value
669            location.href = url
670        } else {
671            Alert.add('Для поиска введите больше 1 символа', 'error')
672        }
673    }
674
675    static init() {
676        const searches = document.querySelectorAll('.search-form')
677
678        if (searches.length) {
679            searches.forEach(search => new Search(search))
680        }
681    }
682}
683
684Search.init()
685
686// Алерты
687const alerts = []
688
689class Alert {
690    constructor(message, type) {
691        this.message = message
692        this.type = type
693        this.alert = ''
694
695        this.create()
696    }
697
698    async create() {
699        await this.closeAll()
700
701        const alert = document.createElement('div')
702        alert.className = 'alert ' + this.type
703        alert.innerText = this.message
704
705        this.alert = alert
706
707        this.append()
708    }
709
710    async append() {
711        document.body.append(this.alert)
712
713        setTimeout(() => {
714            this.close()
715        }, 3000)
716    }
717
718    closeAll() {
719        return new Promise(async (resolve, reject) => {
720            if (alerts.length) {
721                for (let i = 0; i < alerts.length; i++) {
722                    await alerts[i].close()
723                    alerts.splice(i, 1)
724                }
725
726                resolve()
727            } else {
728                resolve()
729            }
730        })
731    }
732
733    close() {
734        return new Promise((resolve, reject) => {
735            if (this.alert) {
736                this.alert.classList.add('close')
737            }
738
739            setTimeout(() => {
740                resolve()
741            }, 300)
742        })
743    }
744
745    static add(message, type = 'success') {
746        alerts.push(new Alert(message, type))
747    }
748}
749
750// Слайдеры
751class Slider {
752    constructor(selector, options) {
753        this.main = document.querySelector(selector)
754
755        if (this.main) {
756            this.slider = this.main.querySelector('.splide')
757
758            if (this.slider) {
759                this.slider = new Splide(this.slider, options).mount();
760
761                this.prev_button = this.main.querySelector('.slider-navigation__arrow-prev') || this.main.querySelector('.slider__arrow-prev')
762                if (this.prev_button) this.prev_button.addEventListener('click', this.prev.bind(this))
763
764                this.next_button = this.main.querySelector('.slider-navigation__arrow-next') || this.main.querySelector('.slider__arrow-next')
765                if (this.next_button) this.next_button.addEventListener('click', this.next.bind(this))
766
767                this.createDotted()
768
769                switch (selector) {
770                    case '.functions':
771                        this.init_functions_slider()
772                        break;
773                }
774
775                return this.slider
776            }
777        }
778    }
779
780    prev() {
781        this.slider.go('-')
782    }
783
784    next() {
785        this.slider.go('+')
786    }
787
788    async createDotted() {
789        const dotted = this.main.querySelector('.slider-navigation__dotted') || this.main.querySelector('.dotted')
790
791        if (dotted) {
792            if (this.slider.length > 1) {
793                for (let i = 0; i < this.slider.length; i++) {
794                    let button = document.createElement('button')
795                    button.dataset.index = i
796
797                    if (i == 0) {
798                        button.className = 'active'
799                    }
800
801                    button.addEventListener('click', () => {
802                        this.slider.go(button.dataset.index)
803
804                        dotted.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
805                        dotted.querySelectorAll('button')[this.slider.index].classList.add('active')
806                    })
807
808                    await dotted.append(button)
809                }
810
811
812                this.slider.on('moved', (index) => {
813                    dotted.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
814                    dotted.querySelectorAll('button')[index].classList.add('active')
815                })
816            }
817        }
818    }
819
820    // Индивидуальные методы
821    init_functions_slider() {
822        this.slider.on('moved', (index) => {
823            const title = this.main.querySelectorAll('.functions__item:not(.splide__slide--clone)')[index].dataset.title
824            const text = this.main.querySelectorAll('.functions__item:not(.splide__slide--clone)')[index].dataset.desc
825
826            this.main.querySelector('.functions__name').innerText = title
827            this.main.querySelector('.functions__desc').innerText = text
828        })
829    }
830
831    static init() {
832        // Главный
833        // new Slider('.slider', {
834        //     type: 'loop',
835        //     arrows: false,
836        //     pagination: false,
837        //     autoplay: false,
838        //     // interval: 5000,
839        // })
840
841        // Вдохновение
842        new Slider('.worths', {
843            type: 'loop',
844            arrows: false,
845            pagination: false,
846            autoplay: true,
847            interval: 5000,
848            perPage: 1,
849            perMove: 1,
850            width: '700px',
851            gap: '50px',
852            breakpoints: {
853                1150: {
854                    width: '500px',
855                },
856                640: {
857                    width: '280px',
858                    gap: '10px',
859                }
860            }
861        })
862
863        // Галерея
864        new Slider('.gallery', {
865            type: 'slide',
866            arrows: false,
867            pagination: false,
868            autoplay: true,
869            interval: 5000,
870            perPage: 1,
871            perMove: 1,
872            gap: '20px',
873            trimSpace: true,
874            breakpoints: {
875                768: {
876                    perPage: 2,
877                },
878                480: {
879                    perPage: 1,
880                }
881            }
882        })
883
884        // Функции
885        new Slider('.functions', {
886            type: 'loop',
887            arrows: false,
888            pagination: false,
889            autoplay: true,
890            interval: 5000,
891            perPage: 1,
892            perMove: 1,
893            gap: '10px',
894        })
895
896        // Фото товара
897        new Slider('.product-page__images', {
898            type: 'slide',
899            arrows: false,
900            pagination: false,
901            autoplay: false,
902            perPage: 7,
903            perMove: 1,
904            gap: '10px',
905            trimSpace: false
906        })
907
908        // Галерея товара
909        new Slider('.product-gallery', {
910            type: 'slide',
911            arrows: false,
912            pagination: false,
913            autoplay: true,
914            interval: 5000,
915            perPage: 1,
916            perMove: 1,
917            gap: '20px',
918            trimSpace: true,
919            autoWidth: true,
920            breakpoints: {
921                768: {
922                    perPage: 2,
923                },
924                480: {
925                    perPage: 1,
926                }
927            }
928            // type: 'slide',
929            // arrows: false,
930            // pagination: false,
931            // autoplay: true,
932            // interval: 5000,
933            // perPage: 1,
934            // perMove: 1,
935            // gap: '20px',
936            // trimSpace: false,
937            // autoWidth: true,
938            // breakpoints: {
939            //     768: {
940            //         perPage: 2,
941            //     },
942            //     480: {
943            //         perPage: 1,
944            //     }
945            // }
946        })
947
948        // Галерея вдохновения
949        new Slider('.inspiration__slider', {
950            type: 'slide',
951            arrows: false,
952            pagination: false,
953            autoplay: true,
954            interval: 5000,
955            perPage: 3,
956            perMove: 1,
957            gap: '20px',
958            trimSpace: false,
959            breakpoints: {
960                768: {
961                    perPage: 2,
962                },
963                480: {
964                    perPage: 1,
965                }
966            }
967        })
968
969        // Подборка товаров вдохновения
970        new Slider('.related', {
971            type: 'loop',
972            arrows: false,
973            pagination: false,
974            autoplay: true,
975            interval: 5000,
976            perPage: 3,
977            perMove: 1,
978            trimSpace: true,
979            gap: '0px',
980            breakpoints: {
981                768: {
982                    perPage: 2,
983                },
984                480: {
985                    perPage: 1,
986                }
987            }
988        })
989
990        // Слайдер материалов 1
991        new Slider('.inspiration__slider-2-1', {
992            type: 'loop',
993            arrows: false,
994            pagination: false,
995            autoplay: true,
996            interval: 5000,
997            perPage: 2,
998            perMove: 1,
999            gap: '20px',
1000            breakpoints: {
1001                640: {
1002                    perPage: 1,
1003                }
1004            }
1005        })
1006
1007        // Слайдер материалов 2
1008        new Slider('.inspiration__slider-2-2', {
1009            type: 'loop',
1010            arrows: false,
1011            pagination: false,
1012            autoplay: true,
1013            interval: 5000,
1014            perPage: 2,
1015            perMove: 1,
1016            gap: '20px',
1017            breakpoints: {
1018                640: {
1019                    perPage: 1,
1020                }
1021            }
1022        })
1023
1024        // Больше о коже
1025        new Slider('.inspiration__more', {
1026            type: 'loop',
1027            arrows: false,
1028            pagination: false,
1029            autoplay: true,
1030            interval: 5000,
1031            perPage: 3,
1032            perMove: 1,
1033            gap: '20px',
1034            breakpoints: {
1035                996: {
1036                    perPage: 2,
1037                },
1038                480: {
1039                    perPage: 1,
1040                }
1041            }
1042        })
1043
1044        // Подборка товаров вдохновения
1045        new Slider('.other-news', {
1046            type: 'loop',
1047            arrows: false,
1048            pagination: false,
1049            autoplay: true,
1050            interval: 5000,
1051            perPage: 3,
1052            perMove: 1,
1053            gap: '20px',
1054            breakpoints: {
1055                768: {
1056                    perPage: 2,
1057                },
1058                480: {
1059                    perPage: 1,
1060                }
1061            }
1062        })
1063    }
1064}
1065
1066Slider.init()
1067
1068// Табы товара
1069class ProductDetails {
1070    constructor(details) {
1071        this.details = details
1072
1073        this.togglers = this.details.querySelectorAll('.product-detail__toggle')
1074        this.togglers.forEach(toggler => toggler.addEventListener('click', (e) => {
1075            this.togglers.forEach(toggler => toggler.classList.remove('active'))
1076            e.target.classList.add('active')
1077
1078            const need = e.target.dataset.tab
1079
1080            this.toggle(need)
1081        }))
1082
1083        this.tabs = this.details.querySelectorAll('.product-detail__tab')
1084
1085        if (this.togglers.length) {
1086            this.first_toggle = this.togglers[0]
1087            this.first_toggle.classList.add('active')
1088            this.first_tab = this.first_toggle.dataset.tab
1089            this.toggle(this.first_tab)
1090        }
1091    }
1092
1093    toggle(need) {
1094        this.tabs.forEach(tab => {
1095            tab.classList.remove('open')
1096            setTimeout(() => {
1097                tab.setAttribute('hidden', true)
1098            }, 200)
1099        })
1100
1101        setTimeout(() => {
1102            need = this.details.querySelector('#tab-' + need)
1103
1104            need.removeAttribute('hidden')
1105            need.classList.add('open')
1106        }, 200)
1107    }
1108
1109    static init() {
1110        const details = document.querySelector('.product-detail')
1111
1112        if (details) {
1113            new ProductDetails(details)
1114        }
1115    }
1116}
1117
1118ProductDetails.init()
1119
1120// Селекты
1121let selects = []
1122
1123class Select {
1124    constructor(select) {
1125        this.select = select
1126        this.is_open = false
1127        this.input = this.select.querySelector('input')
1128
1129        this.button = this.select.querySelector('.select__button')
1130        this.button.addEventListener('click', this.toggle.bind(this))
1131        this.button_text = this.button.querySelector('span')
1132
1133        this.list = this.select.querySelector('.select__body')
1134        if (this.list) {
1135            this.options = this.list.querySelectorAll('[data-value]')
1136            this.options.forEach(option => option.addEventListener('click', this.choise.bind(this)))
1137        }
1138    }
1139
1140    toggle() {
1141        if (this.is_open) {
1142            this.close()
1143        } else {
1144            this.open()
1145        }
1146
1147        this.is_open = !this.is_open
1148    }
1149
1150    choise(e) {
1151        const elem = e.target
1152        const value = elem.dataset.value
1153        const text = elem.innerText
1154
1155        this.input.value = value != '' ? value : ''
1156        this.button.innerText = value != '' ? text : 'Выбрать'
1157
1158        this.input.dispatchEvent(new Event('change'))
1159
1160        this.close()
1161    }
1162
1163    close() {
1164        this.select.classList.remove('open')
1165    }
1166
1167    open() {
1168        selects.forEach(select => select.close())
1169
1170        this.select.classList.add('open')
1171    }
1172
1173    static init() {
1174        const selected = document.querySelectorAll('.select')
1175
1176        if (selected.length) {
1177            selected.forEach(select => selects.push(new Select(select)))
1178        }
1179    }
1180}
1181
1182Select.init()
1183
1184// Фото товара
1185class ProductImages {
1186    constructor(product_images) {
1187        this.main = product_images
1188
1189        this.main_image = this.main.querySelector('.product-page__image img')
1190
1191        this.minimages = this.main.querySelectorAll('.product-page__minimage')
1192        this.minimages.forEach(image => image.addEventListener('click', this.changeImage.bind(this)))
1193    }
1194
1195    changeImage(e) {
1196        const src = e.target.dataset.big
1197        this.main_image.src = src
1198    }
1199
1200    static init() {
1201        const product_images = document.querySelector('.product-page__images')
1202
1203        if (product_images) {
1204            new ProductImages(product_images)
1205        }
1206    }
1207}
1208
1209ProductImages.init()
1210
1211// Рекомендуемые
1212class Recomended {
1213    constructor(recomended) {
1214        this.recomended = recomended
1215
1216        this.content = this.recomended.querySelector('.splide__list')
1217        this.slider = null
1218
1219        this.categories = this.recomended.querySelectorAll('.recomended__category')
1220        this.categories.forEach(category => category.addEventListener('click', this.getProducts.bind(this)))
1221
1222        setTimeout(this.getFirst.bind(this), 2000)
1223    }
1224
1225    getFirst() {
1226        this.categories[0].dispatchEvent(new Event('click'))
1227    }
1228
1229    async getProducts(e) {
1230        const category_id = e.target.dataset.categoryId
1231
1232        this.categories.forEach(category => category.classList.remove('active'))
1233        e.target.classList.add('active')
1234
1235        const recomended_data = new FormData()
1236        recomended_data.append('category_id', category_id)
1237
1238        let response = await fetch('/index.php?route=product/recomended/getProducts', {
1239            method: 'POST',
1240            body: recomended_data
1241        })
1242
1243        response = await response.json()
1244
1245        if (response.products) {
1246            await this.refresh()
1247
1248            for (let product in response.products) {
1249                const product_card = await Functions.htmlToElement(response.products[product])
1250                await this.content.append(product_card)
1251            }
1252
1253            // Галерея рекомендуемых
1254            this.slider = new Slider('.recomended', {
1255                type: 'loop',
1256                arrows: false,
1257                pagination: false,
1258                autoplay: true,
1259                interval: 5000,
1260                perPage: 3,
1261                perMove: 1,
1262                gap: '0px',
1263                breakpoints: {
1264                    768: {
1265                        perPage: 2,
1266                    },
1267                    480: {
1268                        perPage: 1,
1269                    }
1270                }
1271            })
1272
1273        }
1274    }
1275
1276    refresh() {
1277        return new Promise((resolve, reject) => {
1278            this.content.innerHTML = ''
1279
1280            if (this.slider) {
1281                this.slider.refresh()
1282            }
1283
1284            resolve()
1285        })
1286    }
1287
1288    static init() {
1289        const recomended = document.querySelector('.recomended')
1290
1291        if (recomended) {
1292            new Recomended(recomended)
1293        }
1294    }
1295}
1296
1297Recomended.init()
1298
1299// Слайдер в карточке товара
1300let product_card_sliders = []
1301
1302class ProductCardSlider {
1303    constructor(product) {
1304        this.product = product
1305        this.id = this.product.dataset.id
1306
1307        this.createSlider()
1308    }
1309
1310    createSlider() {
1311        const class_id = '.product-card-' + this.id
1312
1313        new Slider(class_id, {
1314            type: 'loop',
1315            arrows: false,
1316            pagination: false,
1317            perPage: 1,
1318            perMove: 1,
1319            autoplay: false
1320        })
1321    }
1322
1323    static init() {
1324        const products = document.querySelectorAll('.product-card')
1325
1326        if (products) {
1327            products.forEach(product => product_card_sliders.push(new ProductCardSlider(product)))
1328        }
1329    }
1330}
1331
1332ProductCardSlider.init()
1333
1334// Раскрывашки
1335class Toggler {
1336    constructor(toggler) {
1337        this.toggler = toggler
1338        this.parent = this.toggler.closest('.toggle-parrent')
1339        this.toggle = this.parent.querySelector('.toggle')
1340        this.is_open = false
1341
1342        this.toggler.addEventListener('click', this.toggleChange.bind(this))
1343    }
1344
1345    toggleChange() {
1346        if (this.is_open) {
1347            this.toggle.classList.remove('open')
1348            this.toggler.innerText = 'Развернуть'
1349        } else {
1350            this.toggle.classList.add('open')
1351            this.toggler.innerText = 'Свернуть'
1352        }
1353
1354        this.is_open = !this.is_open
1355    }
1356
1357    static init() {
1358        const togglers = document.querySelectorAll('.toggler')
1359
1360        if (togglers.length) {
1361            togglers.forEach(item => new Toggler(item))
1362        }
1363    }
1364}
1365
1366Toggler.init()
1367
1368// Lightbox
1369class Lightbox {
1370    constructor() {
1371        this.lightbox = document.querySelector('.lightbox')
1372
1373        if (this.lightbox) {
1374            this.content = this.lightbox.querySelector('.lightbox__content')
1375            this.content.addEventListener('click', this.close.bind(this))
1376
1377            this.lightbox_elements = []
1378            this.open_handler = this.open.bind(this)
1379            this.set_listeners()
1380        }
1381    }
1382
1383    set_listeners() {
1384        if (this.lightbox_elements.length) {
1385            this.lightbox_elements.forEach(elem => elem.removeEventListener('click', this.open_handler))
1386        }
1387
1388        this.lightbox_elements = document.querySelectorAll('[lightbox]')
1389        this.lightbox_elements.forEach(elem => elem.addEventListener('click', this.open_handler))
1390    }
1391
1392    open(e) {
1393        const elem = e.target
1394
1395        console.log(elem)
1396
1397        this.content.innerHTML = ''
1398
1399        if (elem.dataset.lightboxImg) {
1400            const img = document.createElement('img')
1401            img.src = elem.dataset.lightboxImg
1402
1403            this.content.append(img)
1404        }
1405
1406        this.lightbox.removeAttribute('hidden')
1407        this.lightbox.classList.add('open')
1408
1409    }
1410
1411    close() {
1412        this.lightbox.classList.remove('open')
1413
1414        setTimeout(() => {
1415            this.lightbox.setAttribute('hidden', true)
1416            this.content.innerHTML = ''
1417        }, 200)
1418    }
1419
1420    static init() {
1421        const lightbox = document.querySelector('.lightbox')
1422
1423        if (lightbox) {
1424            new Lightbox(lightbox)
1425        }
1426    }
1427}
1428
1429let lightbox = new Lightbox()
1430
1431// Анимации
1432
1433let observer = new IntersectionObserver(start_animation, {threshold: 0.5})
1434document.querySelectorAll('.animation-element').forEach(element => observer.observe(element))
1435
1436function start_animation(elements, intersectionRatio) {
1437    elements.forEach(async element => {
1438        let _element = element.target
1439        // console.log(_element)
1440        if (_element.classList.contains('animation-element') && element.intersectionRatio > 0.1) {
1441            if (_element.dataset.delay) {
1442                await setTimeout(() => {
1443                    _element.classList.remove('animation-element')
1444                    _element.classList.add('start')
1445                }, _element.dataset.delay * 1000)
1446            } else {
1447                _element.classList.remove('animation-element')
1448                _element.classList.add('start')
1449            }
1450
1451            if (_element.classList.contains('animation-element-list')) {
1452                _element.querySelectorAll('.animation-element-item').forEach((item, i) => {
1453                    setTimeout(() => {
1454                        item.classList.add('start')
1455                    }, i * 400)
1456                })
1457            }
1458        }
1459    })
1460}
1461
1462// Функции
1463class Functions {
1464    static htmlToElement(html) {
1465        const template = document.createElement('template');
1466
1467        html = html.trim();
1468
1469        template.innerHTML = html;
1470
1471        return template.content.firstChild;
1472    }
1473}
1474
1475// Выделение активных пунктов меню
1476const current_url = [location.href, location.pathname, location.pathname.replace('/', '')]
1477current_url.forEach(function (item) {
1478    document.querySelectorAll('[href="' + item + '"]').forEach(item => item.classList.add('active'))
1479})
1480
1481// Recaptha
1482// function recaptha() {
1483//     return new Promise(function (resolve, reject) {
1484//         grecaptcha.ready(function () {
1485//             grecaptcha.execute('6Lc4f7coAAAAAOEOBp2L9Ol5wEzCGsbksx7Btjh8', {action: 'contact'}).then(function (token) {
1486//                 document.querySelectorAll('[name=recaptcha_response]').forEach(elem => elem.value = token)
1487//                 resolve()
1488//             });
1489//         });
1490//     })
1491// }
1492
1493// grecaptcha.ready(function () {
1494//     document.querySelectorAll('.modal__form-form').forEach(form => {
1495//         let recaptcha_elem = document.createElement('div')
1496//         recaptcha_elem.className = 'g-recaptcha'
1497//         recaptcha_elem.dataset.sitekey = '6LeWi7coAAAAAPLSSN8A_qZrNCS5Q9vEbxT2Fw74'
1498//
1499//         form.append(recaptcha_elem)
1500//
1501//         grecaptcha.render(recaptcha_elem)
1502//     })
1503//
1504// })
1505
1506// window.onloadRecaptcha = function () {
1507//     document.querySelectorAll('.grecap').forEach(elem => {
1508//         grecaptcha.render(elem, {
1509//             'sitekey': '6LcBZekUAAAAABN1SVylHf7xmgHtfY7Z0KZHD0em'
1510//         })
1511//     })
1512// }
1513
1514const header = document.querySelector('.header');
1515let isAnimating = false;
1516
1517let currentPath = window.location.pathname;
1518let currentSearch = window.location.search;
1519
1520document.addEventListener('scroll', (e) => {
1521
1522    let scrolledDistance = window.scrollY;
1523    if (scrolledDistance > 650) {
1524        if (!header.classList.contains('scrolled')) {
1525            header.classList.add('scrolled');
1526            if (currentPath !== '/' && (currentPath !== '/index.php' || currentSearch !== '?route=common/home')) {
1527                header.classList.add('notMain');
1528            }
1529            header.animate(
1530                [{top: '-150px'}, {top: '0'},],
1531                {duration: 500, iterations: 1,}
1532            );
1533        }
1534    } else if (scrolledDistance < 650 && !isAnimating) {
1535        if (header.classList.contains('scrolled')) {
1536            isAnimating = true;
1537            let answer = header.animate(
1538                [{top: '0'}, {top: '-150px'},],
1539                {duration: 300, iterations: 1,}
1540            );
1541            answer.addEventListener('finish', function () {
1542                header.classList.remove('scrolled');
1543                header.classList.remove('notMain');
1544                isAnimating = false;
1545            });
1546        }
1547    }
1548});
1549
1550class Range {
1551    constructor(range) {
1552        this.range = range
1553
1554        // Сохраняем обертку где будет рендерится слайдер
1555        this.slider_container = this.range.querySelector('.noui-slider')
1556        this.slider = null
1557
1558        // Берем минимальное и максимальное из data аттрибутов
1559        this.min = parseInt(this.range.dataset.min)
1560        this.max = parseInt(this.range.dataset.max)
1561
1562        // Берем начальное и конечное значение из data аттрибутов, если нет - берем начальное и конечное
1563        this.start = this.range.dataset.start ? parseInt(this.range.dataset.start) : this.min
1564        this.end = this.range.dataset.end ? parseInt(this.range.dataset.end) : this.max
1565
1566        // Поля ввода которые будут связаны с слайдером
1567        this.input_from = this.range.querySelector('[data-from]')
1568        this.input_to = this.range.querySelector('[data-to]')
1569
1570        // Хендлер который будет устанавливать значение в слайдер из инпутов
1571        this.set_value_to_slider_handler = this.setValueToSlider.bind(this)
1572
1573        this.create()
1574    }
1575
1576    create() {
1577        // Инициализируем слайдер
1578        this.slider = noUiSlider.create(this.slider_container, {
1579            // Начальные значения
1580            start: [
1581                this.start,
1582                this.end
1583            ],
1584            // Минимальное и максимальное значения
1585            range: {
1586                min: this.min,
1587                max: this.max
1588            },
1589            connect: true,
1590        });
1591
1592        // Событие когда мы изменили значения в слайдере
1593        this.slider.on('update', (values, handle) => {
1594            // Устанавливаем минимальное и максимальное в инпуты
1595            switch (handle) {
1596                case 0:
1597                    this.input_from.value = parseInt(values[0])
1598                    this.min = parseInt(values[0])
1599                    break;
1600                case 1:
1601                    this.input_to.value = parseInt(values[1])
1602                    this.max = parseInt(values[1])
1603                    break;
1604            }
1605        });
1606
1607        // Вешаем события на инпуты, при изменении будем устанавливать значение в слайдер
1608        this.input_from.addEventListener('change', this.set_value_to_slider_handler)
1609        this.input_to.addEventListener('change', this.set_value_to_slider_handler)
1610    }
1611
1612    setValueToSlider() {
1613        this.slider.set([
1614            this.input_from.value,
1615            this.input_to.value
1616        ])
1617    }
1618
1619    static init() {
1620        const ranges = document.querySelectorAll('[data-range]')
1621
1622        if (ranges.length) {
1623            ranges.forEach(range => new Range(range))
1624        }
1625    }
1626}
1627
1628Range.init()
1629window.addEventListener('load', function () {
1630    let url = new URL(location.href)
1631    let search = url.search
1632    console.log(search)
1633    if (search.includes('option_')) {
1634        let catalog = document.querySelector('.category__block')
1635        if (catalog) {
1636            catalog.scrollIntoView({block: "start", behavior: "smooth"});
1637
1638        }
1639    }
1640
1641})

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.