PageSourceSearch

https://art-fresco.ru/local/templates/fresco/js/catalog-modern.js

js art-fresco.ru collected 2026-10-08 02:14:09 UTC 90,054 bytes, 2,420 lines download raw bytes

1(function () {
2    'use strict';
3
4    var YM_COUNTER_ID = 87874798;
5    var MOBILE_FILTER_MEDIA = '(max-width: 900px)';
6    var basketStateRequestId = 0;
7    var basketStateRefreshTimer = null;
8    var basketStateEventBound = false;
9    var BASKET_LOADING_MIN_MS = 650;
10    var mobileFilterState = {
11        open: false,
12        scrollY: 0,
13        trigger: null,
14        snapshot: null,
15        baseHistoryState: null,
16        closePending: false
17    };
18
19    function reachGoal(goal, params) {
20        if (!goal) {
21            return;
22        }
23
24        if (typeof window.afReachYandexGoal === 'function') {
25            window.afReachYandexGoal(goal, params);
26            return;
27        }
28
29        if (typeof window.ym === 'function') {
30            window.ym(YM_COUNTER_ID, 'reachGoal', goal, params || {});
31        }
32    }
33
34
35    document.addEventListener('DOMContentLoaded', function () {
36        normalizeFilterLayout();
37        initMobileCatalogFilter();
38        syncMobileFilterUi();
39        initShortCartPanel();
40        initVisualRequestModal();
41        initLegacyGalleryHandlers();
42        initLegacyDetailGalleryControls();
43        initGalleryThumbs();
44        initPopularShowcaseSliders();
45        syncStickyPanel();
46        initFastSizes();
47        initDetailBasketState();
48    });
49
50    document.addEventListener('af:catalog-ajax-updated', function () {
51        normalizeFilterLayout();
52        syncMobileFilterUi();
53    });
54
55    document.addEventListener('click', function (event) {
56        /*
57         * data-ym-goal обрабатывается глобальным analytics-goals.js.
58         */
59
60        var filterToggle = event.target.closest('[data-af-filter-toggle]');
61        if (filterToggle) {
62            event.preventDefault();
63            openMobileCatalogFilter(filterToggle);
64            return;
65        }
66
67        var filterClose = event.target.closest('[data-af-filter-close]');
68        if (filterClose) {
69            event.preventDefault();
70            requestCloseMobileCatalogFilter();
71            return;
72        }
73
74        var filterMore = event.target.closest('[data-af-filter-more]');
75        if (filterMore) {
76            event.preventDefault();
77            expandMobileFilterValues(filterMore);
78            return;
79        }
80
81        var filterChip = event.target.closest('[data-af-filter-chip]');
82        if (filterChip) {
83            event.preventDefault();
84            removeMobileFilterChip(filterChip);
85            return;
86        }
87
88        var fastButton = event.target.closest('[data-af-fast-size]');
89        if (fastButton && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) {
90            event.preventDefault();
91            chooseFastSize(fastButton);
92            return;
93        }
94        var customSize = event.target.closest('[data-af-fast-custom]');
95        if (customSize) {
96            event.preventDefault();
97            var customGroup = document.querySelector('[data-af-fast-sizes]');
98            if (customGroup) {
99                customGroup.setAttribute('data-selected', '');
100                commitFastState(false);
101                document.querySelector('[data-af-width]').focus();
102                scheduleCurrentProductBasketStateRefresh();
103            }
104            return;
105        }
106
107        var factureCurrent = event.target.closest('[data-af-facture-current]');
108        if (factureCurrent) {
109            var select = factureCurrent.closest('[data-af-facture-select]');
110            if (select) {
111                var isOpen = select.classList.toggle('is-open');
112                factureCurrent.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
113            }
114        }
115
116        var factureOption = event.target.closest('.af-facture-option');
117        if (factureOption) {
118            selectFacture(factureOption);
119        }
120
121        var customBasket = event.target.closest('[data-af-add-custom-basket]');
122        if (customBasket) {
123            event.preventDefault();
124            addCustomBasket(customBasket);
125        }
126
127        var readyBasket = event.target.closest('[data-ready-add-to-basket]');
128        if (readyBasket) {
129            event.preventDefault();
130            addReadyBasket(readyBasket);
131        }
132
133        var shortAdd = event.target.closest('[data-af-short-add]');
134        if (shortAdd) {
135            event.preventDefault();
136            addCustomBasket(shortAdd);
137        }
138
139        var openVisual = event.target.closest('[data-af-open-visual], #openModalVisual');
140        if (openVisual) {
141            event.preventDefault();
142            openVisualRequestModal();
143        }
144
145        var closeVisual = event.target.closest('[data-af-close-visual], #closeModalVisuale');
146        if (closeVisual) {
147            event.preventDefault();
148            closeVisualRequestModal();
149        }
150
151        if (event.target && event.target.id === 'modalVisulate') {
152            closeVisualRequestModal();
153        }
154
155        var galleryThumb = event.target.closest('[data-af-gallery-thumb]');
156        if (galleryThumb) {
157            switchGalleryImage(galleryThumb);
158        }
159
160        if (!event.target.closest('[data-af-facture-select]')) {
161            closeFactureSelects();
162        }
163    });
164
165    document.addEventListener('keydown', function (event) {
166        if (!mobileFilterState.open) {
167            return;
168        }
169
170        if (event.key === 'Escape') {
171            event.preventDefault();
172            requestCloseMobileCatalogFilter();
173            return;
174        }
175
176        if (event.key === 'Tab') {
177            trapMobileFilterFocus(event);
178        }
179    });
180
181    document.addEventListener('input', function (event) {
182        var filterSearch = event.target.closest('[data-af-filter-search]');
183        if (filterSearch) {
184            filterMobileFilterValues(filterSearch);
185            return;
186        }
187
188        var phoneField = event.target.closest('[data-af-phone-mask]');
189        if (phoneField) {
190            phoneField.value = formatPhone(phoneField.value);
191            toggleFastBuyButton(phoneField.closest('form'));
192        }
193
194        var fastBuyField = event.target.closest('.fast_buy_form input');
195        if (fastBuyField) {
196            toggleFastBuyButton(fastBuyField.closest('form'));
197        }
198
199        var field = event.target.closest('[data-af-width], [data-af-height]');
200        if (!field) {
201            return;
202        }
203        var calc = field.closest('[data-af-price-calculator]');
204        if (calc) {
205            reconcileFastDimensions(calc);
206            updatePrice(calc, true);
207            commitFastState(true);
208            syncStickyPanel();
209            scheduleCurrentProductBasketStateRefresh();
210        }
211    });
212
213    document.addEventListener('submit', function (event) {
214        var form = event.target.closest('.fast_buy_form');
215        if (!form) {
216            return;
217        }
218
219        event.preventDefault();
220        submitFastBuy(form);
221    });
222
223
224    function initPopularShowcaseSliders() {
225        document.querySelectorAll('[data-af-popular-slider]').forEach(function (slider) {
226            var track = slider.querySelector('.af-products-grid');
227            var prev = slider.querySelector('[data-af-popular-prev]');
228            var next = slider.querySelector('[data-af-popular-next]');
229            if (!track || !prev || !next) {
230                return;
231            }
232
233            var getStep = function () {
234                var card = track.querySelector('.af-product-card');
235                if (!card) {
236                    return Math.max(280, Math.round(track.clientWidth * 0.8));
237                }
238                var gap = parseInt(window.getComputedStyle(track).columnGap || window.getComputedStyle(track).gap || '18', 10);
239                if (isNaN(gap)) {
240                    gap = 18;
241                }
242                return card.getBoundingClientRect().width + gap;
243            };
244
245            var updateState = function () {
246                var maxScroll = track.scrollWidth - track.clientWidth - 2;
247                var hasOverflow = maxScroll > 0;
248                prev.disabled = !hasOverflow || track.scrollLeft <= 2;
249                next.disabled = !hasOverflow || track.scrollLeft >= maxScroll;
250                slider.classList.toggle('af-popular-products--no-arrows', !hasOverflow);
251            };
252
253            prev.addEventListener('click', function () {
254                track.scrollBy({ left: -getStep(), behavior: 'smooth' });
255            });
256
257            next.addEventListener('click', function () {
258                track.scrollBy({ left: getStep(), behavior: 'smooth' });
259            });
260
261            track.addEventListener('scroll', updateState, { passive: true });
262            window.addEventListener('resize', updateState);
263            updateState();
264            setTimeout(updateState, 300);
265        });
266    }
267
268    function closeFactureSelects() {
269        document.querySelectorAll('[data-af-facture-select].is-open').forEach(function (select) {
270            select.classList.remove('is-open');
271            var current = select.querySelector('[data-af-facture-current]');
272            if (current) {
273                current.setAttribute('aria-expanded', 'false');
274            }
275        });
276    }
277
278    function selectFacture(option) {
279        var root = option.closest('[data-product-id]') || document;
280        var select = option.closest('[data-af-facture-select]');
281        var calc = root.querySelector('[data-af-price-calculator]');
282        var options = select ? select.querySelectorAll('.af-facture-option') : [];
283        var picture = option.getAttribute('data-picture') || '';
284        var name = option.getAttribute('data-name') || '';
285        var printPrice = option.getAttribute('data-print-price') || '';
286
287        options.forEach(function (btn) { btn.classList.remove('is-active'); });
288        option.classList.add('is-active');
289
290        if (select) {
291            var nameNode = select.querySelector('[data-af-facture-current-name]');
292            var priceNode = select.querySelector('[data-af-facture-current-price]');
293            var pictureNode = select.querySelector('[data-af-facture-current-picture]');
294
295            if (nameNode) { nameNode.textContent = name; }
296            if (priceNode) { priceNode.textContent = printPrice; }
297            if (pictureNode) {
298                pictureNode.innerHTML = picture
299                    ? '<img src="' + escapeHtml(picture) + '" alt="' + escapeHtml(name) + '">'
300                    : '<span>Ф</span>';
301            }
302        }
303
304        if (calc) {
305            calc.setAttribute('data-price-per-meter', option.getAttribute('data-price') || '0');
306            calc.setAttribute('data-facture-id', option.getAttribute('data-facture-id') || '');
307            updatePrice(calc);
308        }
309
310        var stickyFacture = document.getElementById('sticky__facture');
311        if (stickyFacture) { stickyFacture.textContent = name; }
312        syncStickyPanel();
313        scheduleCurrentProductBasketStateRefresh();
314
315        commitFastState(false);
316        closeFactureSelects();
317    }
318
319    var restoringFastState = false;
320    var fastTyping = false;
321    var fastBaseSchema = null;
322    var fastBaseMeta = null;
323    var fastCanonicalBase = '';
324
325    function getSelectedFastSize() {
326        var group = document.querySelector('[data-af-fast-sizes]');
327        return group ? (group.getAttribute('data-selected') || '') : '';
328    }
329
330    function fastSnapshot() {
331        var context = getCurrentProductBasketContext();
332        return context ? Object.assign({}, context) : null;
333    }
334
335    function fastBasePath() {
336        var group = document.querySelector('[data-af-fast-sizes]');
337        if (!group) { return ''; }
338        var prefix = window.location.pathname.split('/catalog/')[0];
339        return prefix + group.getAttribute('data-base-url');
340    }
341
342    function fastOption(id) {
343        return Array.prototype.find.call(document.querySelectorAll('.af-facture-option'), function (option) {
344            return option.getAttribute('data-facture-id') === String(id);
345        });
346    }
347
348    function renderFastState() {
349        var selected = getSelectedFastSize();
350        document.querySelectorAll('[data-af-fast-size]').forEach(function (button) {
351            var active = button.getAttribute('data-af-fast-size') === selected;
352            button.classList.toggle('is-active', active);
353            button.setAttribute('aria-pressed', active ? 'true' : 'false');
354        });
355        var custom = document.querySelector('[data-af-fast-custom]');
356        if (custom) { custom.setAttribute('aria-pressed', selected ? 'false' : 'true'); }
357    }
358
359    function reconcileFastDimensions(calc) {
360        var group = document.querySelector('[data-af-fast-sizes]');
361        if (!group) { return; }
362        var selected = getSelectedFastSize();
363        var dimensions = selected.split('_');
364        if (String(calc.querySelector('[data-af-width]').value) !== dimensions[0] ||
365            String(calc.querySelector('[data-af-height]').value) !== dimensions[1]) {
366            group.setAttribute('data-selected', '');
367        }
368    }
369
370    function syncFastMetadata(path, snapshot) {
371        if (!fastBaseSchema || !snapshot) { return; }
372        var schema = JSON.parse(JSON.stringify(fastBaseSchema));
373        var catalogPath = path.substring(path.indexOf('/catalog/'));
374        var variantSuffix = catalogPath.match(/\/([0-9]+_[0-9]+\/[^/]+)\/?$/);
375        var canonicalPath = fastCanonicalBase
376            ? fastCanonicalBase + (variantSuffix ? '/' + variantSuffix[1] + '/' : '')
377            : catalogPath;
378        var url = 'https://art-fresco.ru' + canonicalPath;
379        var option = fastOption(snapshot.factureId);
380        var title = fastBaseMeta ? fastBaseMeta.name : schema.name;
381        var metaDescription = fastBaseMeta ? fastBaseMeta.description : (schema.description || '');
382        if (snapshot.fastSize && option) {
383            var sizeLabel = snapshot.width + ' × ' + snapshot.height + ' см';
384            var factureName = option.getAttribute('data-name');
385            title += ' — ' + sizeLabel + ', ' + factureName;
386            metaDescription = 'Фотообои «' + (fastBaseMeta ? fastBaseMeta.name : fastBaseSchema.name)
387                + '», размер полотна ' + sizeLabel + ', фактура «' + factureName
388                + '». Макет подготовлен к печати. Изготовление на следующий рабочий день. ArtFresco.';
389            schema.name = title;
390            schema.description = metaDescription;
391            schema.url = url;
392            schema['@id'] = url + '#product';
393            schema.sku = 'af-' + snapshot.productId + '-' + snapshot.fastSize + '-' + snapshot.factureId;
394            schema.size = sizeLabel;
395            schema.material = factureName;
396            var calc = document.querySelector('[data-af-price-calculator]');
397            var area = Math.max(Number(calc.getAttribute('data-min-area') || '1'), Number(snapshot.width) * Number(snapshot.height) / 10000);
398            if (calc.getAttribute('data-area-rounding') === 'ceil') { area = Math.ceil(area); }
399            area = Math.round((area + Number.EPSILON) * 100) / 100;
400            schema.offers = { '@type': 'Offer', '@id': url + '#offer', url: url,
401                price: Math.round(area * Number(option.getAttribute('data-price')) * 100) / 100,
402                priceCurrency: 'RUB', itemCondition: 'https://schema.org/NewCondition' };
403        }
404        var metaTitle = title;
405        if (snapshot.fastSize && fastBaseMeta && fastBaseMeta.code &&
406            metaTitle.toLowerCase().indexOf(String(fastBaseMeta.code).toLowerCase()) === -1) {
407            var productName = fastBaseMeta.name;
408            metaTitle = productName + ' ' + fastBaseMeta.code + title.substring(productName.length);
409        }
410        document.title = !snapshot.fastSize && fastBaseMeta ? fastBaseMeta.title : metaTitle + ' | ArtFresco';
411        var description = document.querySelector('meta[name="description"]');
412        if (!description) {
413            description = document.createElement('meta');
414            description.name = 'description';
415            document.head.appendChild(description);
416        }
417        description.content = metaDescription;
418        var ogDescription = document.querySelector('meta[property="og:description"]');
419        if (ogDescription) { ogDescription.content = metaDescription; }
420        var ogUrl = document.querySelector('meta[property="og:url"]');
421        if (ogUrl) { ogUrl.content = url; }
422        var ogTitle = document.querySelector('meta[property="og:title"]');
423        if (ogTitle) { ogTitle.content = document.title; }
424        var canonical = document.querySelector('link[rel="canonical"]');
425        if (canonical) { canonical.href = url; }
426        var node = document.querySelector('[data-af-product-schema]');
427        if (node) { node.textContent = JSON.stringify(schema); }
428    }
429
430    function commitFastState(fromInput) {
431        var group = document.querySelector('[data-af-fast-sizes]');
432        if (!group || restoringFastState) { return; }
433        var snapshot = fastSnapshot();
434        var option = fastOption(snapshot.factureId);
435        var slug = option ? option.getAttribute('data-facture-code') : '';
436        // A material without a public slug remains available for an individual order.
437        if (snapshot.fastSize && !slug) {
438            group.setAttribute('data-selected', '');
439            snapshot.fastSize = '';
440        }
441        var path = fastBasePath();
442        if (snapshot.fastSize) { path += snapshot.fastSize + '/' + slug + '/'; }
443        renderFastState();
444        var next = Object.assign({}, window.history.state || {}, { afFast: snapshot });
445        if (path !== window.location.pathname || window.location.search) {
446            window.history.pushState(next, '', path);
447        } else if ((fromInput && !fastTyping) || (!fromInput && JSON.stringify((window.history.state || {}).afFast) !== JSON.stringify(snapshot))) {
448            window.history.pushState(next, '', path);
449        } else {
450            window.history.replaceState(next, '', path);
451        }
452        fastTyping = !!fromInput;
453        syncFastMetadata(path, snapshot);
454    }
455
456    function chooseFastSize(button) {
457        var group = document.querySelector('[data-af-fast-sizes]');
458        var option = fastOption(button.getAttribute('data-default-facture'));
459        if (!group || !option) { window.location.assign(button.href); return; }
460        group.setAttribute('data-selected', button.getAttribute('data-af-fast-size'));
461        document.querySelector('[data-af-width]').value = button.getAttribute('data-width');
462        document.querySelector('[data-af-height]').value = button.getAttribute('data-height');
463        selectFacture(option); // Shared price/sticky/basket refresh, then history.
464        reachGoal('catalog_prepared_size_select', { size: getSelectedFastSize(), facture_id: option.getAttribute('data-facture-id') });
465    }
466
467    function initFastSizes() {
468        var group = document.querySelector('[data-af-fast-sizes]');
469        if (!group) { return; }
470        var canonicalNode = document.querySelector('link[rel="canonical"]');
471        if (canonicalNode) {
472            try {
473                var canonicalMatch = new URL(canonicalNode.href, window.location.href).pathname.match(
474                    /^(\/catalog\/[^/]+\/[^/]+)(?:\/[0-9]+_[0-9]+\/[^/]+)?\/?$/
475                );
476                if (canonicalMatch) { fastCanonicalBase = canonicalMatch[1]; }
477            } catch (ignore) {}
478        }
479        var baseSchemaNode = document.querySelector('[data-af-base-schema]');
480        if (baseSchemaNode) {
481            try { fastBaseSchema = JSON.parse(baseSchemaNode.textContent); } catch (ignore) {}
482        }
483        var baseMetaNode = document.querySelector('[data-af-base-meta]');
484        if (baseMetaNode) {
485            try { fastBaseMeta = JSON.parse(baseMetaNode.textContent); } catch (ignore) {}
486        }
487        window.history.replaceState(Object.assign({}, window.history.state || {}, { afFast: fastSnapshot() }), '', window.location.href);
488        renderFastState();
489        group.addEventListener('keydown', function (event) {
490            var button = event.target.closest('[data-af-fast-size]');
491            if (button && event.key === ' ') { event.preventDefault(); chooseFastSize(button); }
492        });
493        window.addEventListener('popstate', function (event) {
494            var snapshot = event.state && event.state.afFast;
495            var current = getCurrentProductBasketContext();
496            if (!snapshot || !current || snapshot.productId !== current.productId) { return; }
497            var option = fastOption(snapshot.factureId);
498            if (!option) { window.location.reload(); return; }
499            restoringFastState = true;
500            group.setAttribute('data-selected', snapshot.fastSize || '');
501            document.querySelector('[data-af-width]').value = snapshot.width;
502            document.querySelector('[data-af-height]').value = snapshot.height;
503            selectFacture(option);
504            restoringFastState = false;
505            fastTyping = false;
506            renderFastState();
507            syncFastMetadata(window.location.pathname, snapshot);
508        });
509    }
510
511    function updatePrice(calc, userInitiated) {
512        var width = parseInt((calc.querySelector('[data-af-width]') || {}).value || '100', 10);
513        var height = parseInt((calc.querySelector('[data-af-height]') || {}).value || '100', 10);
514        var pricePerMeter = parseFloat(calc.getAttribute('data-price-per-meter') || '0');
515        var area = Math.max(parseFloat(calc.getAttribute('data-min-area') || '1'), (width / 100) * (height / 100));
516        if (calc.getAttribute('data-area-rounding') === 'ceil') { area = Math.ceil(area); }
517        area = Math.round((area + Number.EPSILON) * 100) / 100;
518        var price = Math.round(area * pricePerMeter);
519        var priceNode = calc.querySelector('[data-af-price]');
520        if (priceNode) {
521            priceNode.textContent = formatPrice(price) + ' ₽';
522        }
523        var shortPrice = document.querySelector('[data-af-short-price], #sticky__price');
524        if (shortPrice) {
525            shortPrice.textContent = formatPrice(price) + ' ₽';
526        }
527        var stickySize = document.getElementById('sticky__size');
528        if (stickySize) { stickySize.textContent = width + ' x ' + height + ' см'; }
529
530        if (userInitiated === true) {
531            var product = calc.closest('[data-product-id]');
532            document.dispatchEvent(new CustomEvent('af:price-recalculated', {
533                detail: {
534                    product_id: product ? String(product.getAttribute('data-product-id') || '') : '',
535                    width: width,
536                    height: height,
537                    price: price
538                }
539            }));
540        }
541    }
542
543	function getBitrixSessid() {
544		if (
545			window.BX &&
546			typeof window.BX.bitrix_sessid === 'function'
547		) {
548			var bxSessid = window.BX.bitrix_sessid();
549
550			if (bxSessid) {
551				return bxSessid;
552			}
553		}
554
555		if (
556			window.BX &&
557			typeof window.BX.message === 'function'
558		) {
559			var messageSessid = window.BX.message('bitrix_sessid');
560
561			if (messageSessid) {
562				return messageSessid;
563			}
564		}
565
566		var input = document.querySelector(
567			'input[name="sessid"], input[name="bitrix_sessid"]'
568		);
569
570		if (input && input.value) {
571			return input.value;
572		}
573
574		return '';
575	}
576
577	function appendBitrixSessid(data) {
578		var sessid = getBitrixSessid();
579
580		if (sessid) {
581			data.set('sessid', sessid);
582		}
583	}
584
585    function setBasketButtonText(button, text) {
586        if (!button) { return; }
587        var textNode = button.querySelector('.buy_button_text');
588        if (textNode) {
589            textNode.textContent = text;
590        } else {
591            button.textContent = text;
592        }
593    }
594
595    function finishBasketLoading(startedAt, callback) {
596        var elapsed = Math.max(0, Date.now() - startedAt);
597        var delay = Math.max(0, BASKET_LOADING_MIN_MS - elapsed);
598
599        if (delay === 0) {
600            callback();
601            return;
602        }
603
604        window.setTimeout(callback, delay);
605    }
606
607    function getBasketButtons(productId) {
608        productId = String(productId || '').replace(/[^0-9]/g, '');
609        if (!productId) { return []; }
610
611        var selectors = [
612            '[data-af-add-custom-basket][data-product-id="' + productId + '"]',
613            '[data-af-short-add][data-product-id="' + productId + '"]',
614            '[data-ready-add-to-basket][data-product-id="' + productId + '"]'
615        ];
616
617        return Array.prototype.slice.call(document.querySelectorAll(selectors.join(',')));
618    }
619
620    function rememberBasketAddGoal(button) {
621        if (!button || button.hasAttribute('data-af-basket-add-goal')) { return; }
622        var goal = button.getAttribute('data-ym-goal') || '';
623        button.setAttribute('data-af-basket-add-goal', goal);
624    }
625
626    function restoreBasketAddGoal(button) {
627        if (!button) { return; }
628        var goal = button.getAttribute('data-af-basket-add-goal');
629        if (goal) {
630            button.setAttribute('data-ym-goal', goal);
631        }
632    }
633
634    function setBasketButtonState(button, state) {
635        if (!button) { return; }
636        rememberBasketAddGoal(button);
637
638        button.classList.remove('is-added', 'is-loading', 'is-checking');
639        button.removeAttribute('aria-busy');
640        button.setAttribute('data-af-basket-state', state);
641
642        var spinner = button.querySelector('.buy_button_spinner');
643        if (spinner) {
644            spinner.style.display = state === 'loading' ? '' : 'none';
645        }
646
647        if (state === 'in-cart') {
648            button.classList.add('is-added');
649            button.disabled = false;
650            button.removeAttribute('data-ym-goal');
651            setBasketButtonText(button, 'В корзине');
652            return;
653        }
654
655        if (state === 'loading') {
656            button.classList.add('is-loading');
657            button.disabled = true;
658            button.setAttribute('aria-busy', 'true');
659            button.removeAttribute('data-ym-goal');
660            setBasketButtonText(button, 'Добавляем...');
661            return;
662        }
663
664        if (state === 'checking') {
665            button.classList.add('is-checking');
666            button.disabled = true;
667            button.setAttribute('aria-busy', 'true');
668            button.removeAttribute('data-ym-goal');
669            setBasketButtonText(button, 'Проверяем...');
670            return;
671        }
672
673        button.disabled = false;
674        restoreBasketAddGoal(button);
675        setBasketButtonText(button, 'В корзину');
676    }
677
678    function setBasketButtonsState(productId, state) {
679        getBasketButtons(productId).forEach(function (button) {
680            setBasketButtonState(button, state);
681        });
682    }
683
684    function syncAddedBasketButtons(productId) {
685        setBasketButtonsState(productId, 'in-cart');
686    }
687
688    function isBasketButtonInCart(button) {
689        return !!button && button.getAttribute('data-af-basket-state') === 'in-cart';
690    }
691
692    function getBasketUrl(button) {
693        var url = button ? (button.getAttribute('data-cart-url') || '') : '';
694        if (url) { return url; }
695
696        var basketLink = document.querySelector('a[href*="/personal/cart/"]');
697        return basketLink ? basketLink.getAttribute('href') : '/personal/cart/';
698    }
699
700    function openBasket(button) {
701        window.location.href = getBasketUrl(button);
702    }
703
704    function getCurrentProductBasketContext() {
705        var product = document.querySelector('.af-product-detail[data-product-id], .af-product-detail');
706        if (!product) { return null; }
707
708        var productId = product.getAttribute('data-product-id') || '';
709        if (!productId) {
710            var anyButton = product.querySelector('[data-product-id][data-af-add-custom-basket], [data-product-id][data-af-short-add], [data-product-id][data-ready-add-to-basket]');
711            productId = anyButton ? (anyButton.getAttribute('data-product-id') || '') : '';
712        }
713        productId = String(productId || '').replace(/[^0-9]/g, '');
714        if (!productId) { return null; }
715
716        var readyButton = product.querySelector('[data-ready-add-to-basket][data-product-id="' + productId + '"]');
717        if (readyButton) {
718            return {
719                productId: productId,
720                ready: true,
721                factureId: '',
722                width: '',
723                height: ''
724            };
725        }
726
727        var calc = product.querySelector('[data-af-price-calculator]') || document.querySelector('[data-af-price-calculator]');
728        if (!calc) { return null; }
729        var widthField = calc.querySelector('[data-af-width]');
730        var heightField = calc.querySelector('[data-af-height]');
731
732        return {
733            productId: productId,
734            ready: false,
735            factureId: String(calc.getAttribute('data-facture-id') || ''),
736            width: widthField ? String(widthField.value || '') : '',
737            height: heightField ? String(heightField.value || '') : '',
738            fastSize: getSelectedFastSize()
739        };
740    }
741
742    function appendBasketContext(data, context) {
743        data.append('product_id', context.productId);
744        if (!context.ready) {
745            data.append('facture_id', context.factureId || '');
746            data.append('width', context.width || '');
747            data.append('height', context.height || '');
748            data.append('fast_size', context.fastSize || '');
749        }
750    }
751
752    function refreshCurrentProductBasketState(showChecking) {
753        var context = getCurrentProductBasketContext();
754        if (!context) { return Promise.resolve(); }
755
756        var requestId = ++basketStateRequestId;
757        if (showChecking !== false) {
758            setBasketButtonsState(context.productId, 'checking');
759        }
760
761        var data = new FormData();
762        data.append('action', 'state');
763        appendBasketContext(data, context);
764        appendBitrixSessid(data);
765
766        return fetch('/local/ajax/catalog_add_to_basket.php', {
767            method: 'POST',
768            body: data,
769            credentials: 'same-origin'
770        })
771            .then(function (res) { return res.json(); })
772            .then(function (json) {
773                if (requestId !== basketStateRequestId) { return; }
774                if (!json.success) {
775                    setBasketButtonsState(context.productId, 'idle');
776                    return;
777                }
778                setBasketButtonsState(context.productId, json.in_basket ? 'in-cart' : 'idle');
779                updateHeaderBasketFallback(json);
780            })
781            .catch(function () {
782                if (requestId === basketStateRequestId) {
783                    setBasketButtonsState(context.productId, 'idle');
784                }
785            });
786    }
787
788    function scheduleCurrentProductBasketStateRefresh(showChecking) {
789        var context = getCurrentProductBasketContext();
790        if (!context) { return; }
791        ++basketStateRequestId; // Invalidate old responses before the debounce delay.
792        if (showChecking !== false) { setBasketButtonsState(context.productId, 'checking'); }
793        if (basketStateRefreshTimer) {
794            clearTimeout(basketStateRefreshTimer);
795        }
796        basketStateRefreshTimer = setTimeout(function () {
797            basketStateRefreshTimer = null;
798            refreshCurrentProductBasketState(showChecking !== false);
799        }, 180);
800    }
801
802    function initDetailBasketState() {
803        var context = getCurrentProductBasketContext();
804        if (!context) { return; }
805
806        refreshCurrentProductBasketState(true);
807
808        if (!basketStateEventBound && window.BX && typeof BX.addCustomEvent === 'function') {
809            basketStateEventBound = true;
810            BX.addCustomEvent(window, 'OnBasketChange', function () {
811                // Basket line/header still refreshes through the shared Bitrix event,
812                // but the product button must not flash through "Проверяем..."
813                // immediately after a successful add/remove operation.
814                scheduleCurrentProductBasketStateRefresh(false);
815            });
816        }
817    }
818
819    function updateHeaderBasketFallback(json) {
820        var count = parseInt((json && json.basket_count), 10);
821        if (isNaN(count) || count < 0) { return; }
822
823        document.querySelectorAll('a[href*="/personal/cart/"]').forEach(function (link) {
824            var icon = link.querySelector('img[src*="Card.svg"]');
825            if (!icon) { return; }
826
827            var counter = link.querySelector('.catalog-cart-counter-menu');
828            if (count < 1) {
829                if (counter) { counter.textContent = ''; counter.style.display = 'none'; }
830                return;
831            }
832
833            if (!counter) {
834                counter = document.createElement('span');
835                counter.className = 'catalog-cart-counter-menu catalog-cart-counter-menu_custom';
836                counter.style.background = '#7D0808';
837                counter.style.top = '14px';
838                link.appendChild(counter);
839            }
840            counter.style.display = '';
841            counter.textContent = String(count);
842        });
843    }
844
845    function notifyBasketChanged(json) {
846        if (window.BX && typeof BX.onCustomEvent === 'function') {
847            BX.onCustomEvent('OnBasketChange');
848        }
849        updateHeaderBasketFallback(json || {});
850    }
851
852    function addCustomBasket(button) {
853        if (isBasketButtonInCart(button)) {
854            openBasket(button);
855            return;
856        }
857        if (button.disabled) { return; }
858
859        var product = button.closest('.af-product-detail') || document.querySelector('.af-product-detail');
860        var calc = product ? product.querySelector('[data-af-price-calculator]') : document.querySelector('[data-af-price-calculator]');
861        if (!calc) {
862            window.afFormMessage.at(button.closest('.af-product-detail, .af-product-card') || button.parentElement, "error")('Не удалось найти блок расчёта размера');
863            return;
864        }
865
866        var widthField = calc.querySelector('[data-af-width]');
867        var heightField = calc.querySelector('[data-af-height]');
868        var activeFacture = (product || document).querySelector('.af-facture-option.is-active, .af-facture-item.is-active');
869        var productId = button.getAttribute('data-product-id') || (product && product.getAttribute('data-product-id')) || '';
870
871        if (!productId) {
872            window.afFormMessage.at(button.closest('.af-product-detail, .af-product-card') || button.parentElement, "error")('Не удалось определить товар');
873            return;
874        }
875
876        var data = new FormData();
877        data.append('product_id', productId);
878        data.append('facture_id', calc.getAttribute('data-facture-id') || (activeFacture ? activeFacture.getAttribute('data-facture-id') : '') || '');
879        data.append('width', widthField ? (widthField.value || '100') : '100');
880        data.append('height', heightField ? (heightField.value || '100') : '100');
881        data.append('fast_size', getSelectedFastSize());
882        var addedContextKey = JSON.stringify(getCurrentProductBasketContext());
883        appendBitrixSessid(data);
884
885        var loadingStartedAt = Date.now();
886        ++basketStateRequestId;
887        if (basketStateRefreshTimer) { clearTimeout(basketStateRefreshTimer); basketStateRefreshTimer = null; }
888        setBasketButtonsState(productId, 'loading');
889        fetch('/local/ajax/catalog_add_to_basket.php', { method: 'POST', body: data, credentials: 'same-origin' })
890            .then(function (res) { return res.json(); })
891            .then(function (json) {
892                if (!json.success) {
893                    setBasketButtonsState(productId, 'idle');
894                    window.afFormMessage.at(button.closest('.af-product-detail, .af-product-card') || button.parentElement, "error")(json.message || 'Не удалось добавить товар в корзину');
895                    return;
896                }
897
898                if (json.added === true) {
899                    reachGoal('catalog_custom_add_to_cart_success');
900                    reachGoal('ecom_add_to_cart', {
901                        product_id: String(productId),
902                        product_type: data.get('fast_size') ? 'prepared_wallpaper' : 'custom_wallpaper',
903                        size: data.get('fast_size') || '',
904                        facture_id: data.get('facture_id') || ''
905                    });
906                }
907
908                finishBasketLoading(loadingStartedAt, function () {
909                    if (addedContextKey === JSON.stringify(getCurrentProductBasketContext())) {
910                        syncAddedBasketButtons(productId);
911                    } else {
912                        scheduleCurrentProductBasketStateRefresh();
913                    }
914                    if (json.added === true) {
915                        notifyBasketChanged(json);
916                    } else {
917                        updateHeaderBasketFallback(json);
918                    }
919                });
920            })
921            .catch(function () {
922                setBasketButtonsState(productId, 'idle');
923                window.afFormMessage.at(button.closest('.af-product-detail, .af-product-card') || button.parentElement, "error")('Ошибка добавления в корзин
923у');
924                scheduleCurrentProductBasketStateRefresh();
925            });
926    }
927
928
929    function toggleFastBuyButton(form) {
930        if (!form) {
931            return;
932        }
933        var phone = form.querySelector('input[name="phone"], input[name="value"]');
934        var button = form.querySelector('.fast_buy_button');
935        if (!phone || !button || button.classList.contains('success')) {
936            return;
937        }
938        var digits = phone.value.replace(/\D/g, '');
939        button.disabled = digits.length < 10;
940    }
941
942    function submitFastBuy(form) {
943        var button =
944            form.querySelector('.fast_buy_button');
945
946        var phone =
947            form.querySelector(
948                'input[name="phone"], input[name="value"]'
949            );
950
951        var name =
952            form.querySelector('input[name="name"]');
953
954        var url =
955            form.querySelector('input[name="url"]');
956
957        if (
958            !phone
959            || phone.value.replace(/\D/g, '').length < 10
960        ) {
961            return;
962        }
963
964        var attribution =
965            window.afMetrikaAttribution
966                ? window.afMetrikaAttribution.get()
967                : {
968                    clientId: '',
969                    yclid: '',
970                    token: ''
971                };
972
973        var data = new FormData();
974
975        data.append('value', phone.value);
976        data.append(
977            'name',
978            name ? name.value : ''
979        );
980        data.append(
981            'url',
982            url ? url.value : window.location.href
983        );
984        data.append(
985            'page_url',
986            window.location.href
987        );
988        data.append(
989            'ym_client_id',
990            attribution.clientId || ''
991        );
992        data.append(
993            'yclid',
994            attribution.yclid || ''
995        );
996        data.append(
997            'attribution_token',
998            attribution.token || ''
999        );
1000
1001        if (button) {
1002            button.disabled = true;
1003            button.textContent = 'Отправляем...';
1004        }
1005
1006        var persistPromise = Promise.resolve();
1007
1008        if (
1009            window.afMetrikaAttribution
1010            && typeof window.afMetrikaAttribution.persist === 'function'
1011        ) {
1012            persistPromise =
1013                window.afMetrikaAttribution
1014                    .persist('site_form')
1015                    .catch(function (error) {
1016                        console.warn(
1017                            '[ArtFresco] catalog fast buy attribution persist failed',
1018                            error
1019                        );
1020                    });
1021        }
1022
1023        persistPromise
1024            .then(function () {
1025                return window.afFormGuard.fetch(
1026                    '/local/templates/fresco/ajax/fast_buy.php',
1027                    {
1028                        method: 'POST',
1029                        body: data,
1030                        credentials: 'same-origin'
1031                    }
1032                );
1033            })
1034            .then(function (res) {
1035                return res.json();
1036            })
1037            .then(function (json) {
1038                if (!json || json.STATUS !== true) {
1039                    throw new Error(
1040                        json && json.MESSAGE
1041                            ? json.MESSAGE
1042                            : 'bad-response'
1043                    );
1044                }
1045
1046                /*
1047                 * Stage 11.1:
1048                 * FastBuy — единственная каноническая цель
1049                 * успешного быстрого заказа.
1050                 */
1051                reachGoal('FastBuy');
1052
1053                renderFastBuyPromo(
1054                    form,
1055                    json.PROMO_CODE || 'WALL2205',
1056                    json.PROMO_VALID_UNTIL || ''
1057                );
1058            })
1059            .catch(function (error) {
1060                console.error(
1061                    '[ArtFresco] fast buy failed',
1062                    error
1063                );
1064
1065                if (button) {
1066                    button.textContent =
1067                        'Попробовать ещё раз';
1068                    button.disabled = false;
1069                }
1070
1071                window.afFormMessage.at(form, "error")(
1072                    'Не удалось отправить быстрый заказ. '
1073                    + 'Попробуйте ещё раз или свяжитесь с нами по телефону.'
1074                );
1075            });
1076    }
1077
1078    function renderFastBuyPromo(
1079        form,
1080        promoCode,
1081        validUntil
1082    ) {
1083        if (!form) {
1084            return;
1085        }
1086
1087        var code = String(
1088            promoCode || 'WALL2205'
1089        );
1090
1091        form.innerHTML = '';
1092
1093        var box = document.createElement('div');
1094        box.className =
1095            'af-fast-buy-promo-success';
1096
1097        box.style.padding = '18px';
1098        box.style.border =
1099            '1px solid #dfd3ca';
1100        box.style.borderRadius = '14px';
1101        box.style.background = '#fff';
1102
1103        var title = document.createElement('div');
1104        title.textContent = 'Заявка отправлена';
1105        title.style.fontSize = '18px';
1106        title.style.fontWeight = '700';
1107        title.style.marginBottom = '8px';
1108
1109        var text = document.createElement('div');
1110        text.textContent =
1111            'Спасибо! Ваш промокод '
1112            + 'на специальное предложение:';
1113        text.style.fontSize = '14px';
1114        text.style.lineHeight = '1.45';
1115        text.style.marginBottom = '14px';
1116
1117        var row = document.createElement('div');
1118        row.style.display = 'flex';
1119        row.style.alignItems = 'center';
1120        row.style.gap = '10px';
1121        row.style.flexWrap = 'wrap';
1122        row.style.padding = '12px';
1123        row.style.border =
1124            '1px dashed #9b1111';
1125        row.style.borderRadius = '10px';
1126        row.style.background = '#faf7f4';
1127
1128        var strong =
1129            document.createElement('strong');
1130        strong.textContent = code;
1131        strong.style.fontSize = '22px';
1132        strong.style.letterSpacing = '1px';
1133
1134        var copy =
1135            document.createElement('button');
1136        copy.type = 'button';
1137        copy.textContent = 'Скопировать';
1138        copy.style.marginLeft = 'auto';
1139        copy.style.padding = '9px 14px';
1140        copy.style.border = '0';
1141        copy.style.borderRadius = '8px';
1142        copy.style.background = '#8f0909';
1143        copy.style.color = '#fff';
1144        copy.style.fontWeight = '600';
1145        copy.style.cursor = 'pointer';
1146
1147        copy.addEventListener(
1148            'click',
1149            function () {
1150                copyPromoCode(
1151                    code,
1152                    copy
1153                );
1154            }
1155        );
1156
1157        row.appendChild(strong);
1158        row.appendChild(copy);
1159
1160        box.appendChild(title);
1161        box.appendChild(text);
1162        box.appendChild(row);
1163
1164        if (validUntil) {
1165            var until =
1166                document.createElement('div');
1167
1168            until.textContent =
1169                'Действует до '
1170                + String(validUntil)
1171                + ' г.';
1172
1173            until.style.marginTop = '10px';
1174            until.style.fontSize = '13px';
1175            until.style.color = '#777';
1176
1177            box.appendChild(until);
1178        }
1179
1180        form.appendChild(box);
1181        form.classList.add('is-success');
1182    }
1183
1184    function copyPromoCode(code, button) {
1185        function done() {
1186            var oldText = button.textContent;
1187
1188            button.textContent =
1189                '✓ Скопировано';
1190
1191            setTimeout(
1192                function () {
1193                    button.textContent =
1194                        oldText;
1195                },
1196                1800
1197            );
1198        }
1199
1200        function fallback() {
1201            var area =
1202                document.createElement(
1203                    'textarea'
1204                );
1205
1206            area.value = code;
1207            area.setAttribute(
1208                'readonly',
1209                'readonly'
1210            );
1211            area.style.position = 'fixed';
1212            area.style.opacity = '0';
1213
1214            document.body.appendChild(area);
1215            area.select();
1216
1217            try {
1218                document.execCommand('copy');
1219                done();
1220            } catch (e) {
1221                window.afFormMessage.at(null, "status")('Промокод: ' + code);
1222            }
1223
1224            area.remove();
1225        }
1226
1227        if (
1228            navigator.clipboard
1229            && typeof navigator.clipboard.writeText
1230                === 'function'
1231        ) {
1232            navigator.clipboard
1233                .writeText(code)
1234                .then(done)
1235                .catch(fallback);
1236        } else {
1237            fallback();
1238        }
1239    }
1240
1241
1242
1243    function initLegacyGalleryHandlers() {
1244        // fix62: новая карточка использует старые глобаÐ
1244»ÑŒÐ½Ñ‹Ðµ функции галереи.
1245        // Переключаем главное изображение напрямую через #main_fresca_image img.
1246        window.selectImageSlider = function (image) {
1247            image = normalizeGalleryThumb(image);
1248            if (!image) { return false; }
1249
1250            var gallery = getGalleryFromElement(image) || document.querySelector('[data-af-legacy-gallery]') || document;
1251            var mainFrescaImage = document.getElementById('main_fresca_image');
1252            var mainFrescoIframe = document.getElementById('main_fresco_iframe');
1253            var mainImage = mainFrescaImage ? (mainFrescaImage.querySelector('[data-af-main-image]') || mainFrescaImage.querySelector('img')) : null;
1254
1255            if (!mainFrescaImage || !mainImage) { return false; }
1256
1257            getGalleryThumbs(gallery).forEach(function (item) {
1258                item.classList.remove('select', 'is-active');
1259            });
1260            image.classList.add('select', 'is-active');
1261            syncLegacySwiperClasses(gallery, image);
1262
1263            var currentMedia = getThumbMediaElement(image);
1264            var iframeSrc = getThumbIframeSrc(image, currentMedia);
1265            var imageSrc = getThumbImageSrc(image, currentMedia);
1266
1267            if (iframeSrc && mainFrescoIframe) {
1268                mainFrescaImage.style.display = 'none';
1269                var iframe = mainFrescoIframe.querySelector('iframe');
1270                if (iframe) {
1271                    iframe.setAttribute('src', iframeSrc);
1272                }
1273                mainFrescoIframe.style.display = 'block';
1274                scrollActiveThumbIntoView(gallery, image);
1275                return false;
1276            }
1277
1278            if (!imageSrc) {
1279                scrollActiveThumbIntoView(gallery, image);
1280                return false;
1281            }
1282
1283            mainFrescaImage.style.display = 'block';
1284            if (mainFrescoIframe) {
1285                mainFrescoIframe.style.display = 'none';
1286            }
1287            mainFrescaImage.classList.add('element_image_wrap');
1288            mainFrescaImage.classList.remove('img_wrap');
1289
1290            mainImage.classList.remove('hide');
1291            mainImage.setAttribute('src', imageSrc);
1292            mainImage.setAttribute('alt', image.getAttribute('data-alt') || mainImage.getAttribute('alt') || '');
1293
1294            var editorImage = document.getElementById('image');
1295            if (editorImage) {
1296                editorImage.setAttribute('src', imageSrc);
1297            }
1298
1299            scrollActiveThumbIntoView(gallery, image);
1300            return false;
1301        };
1302
1303        window.nextSelectImage = function (elem) {
1304            var gallery = getGalleryFromElement(elem) || document.querySelector('[data-af-legacy-gallery]') || document;
1305            var thumbs = getGalleryThumbs(gallery);
1306            if (!thumbs.length) { return false; }
1307            var currentIndex = getActiveThumbIndex(thumbs);
1308            var nextIndex = currentIndex >= 0 ? (currentIndex + 1) % thumbs.length : 0;
1309            return window.selectImageSlider(thumbs[nextIndex]);
1310        };
1311
1312        window.prevSelectImage = function (elem) {
1313            var gallery = getGalleryFromElement(elem) || document.querySelector('[data-af-legacy-gallery]') || document;
1314            var thumbs = getGalleryThumbs(gallery);
1315            if (!thumbs.length) { return false; }
1316            var currentIndex = getActiveThumbIndex(thumbs);
1317            var prevIndex = currentIndex >= 0 ? (currentIndex - 1 + thumbs.length) % thumbs.length : thumbs.length - 1;
1318            return window.selectImageSlider(thumbs[prevIndex]);
1319        };
1320    }
1321
1322    function initLegacyDetailGalleryControls() {
1323        document.querySelectorAll('[data-af-legacy-gallery]').forEach(function (gallery) {
1324            var thumbs = getGalleryThumbs(gallery);
1325            var prev = gallery.querySelector('[data-af-gallery-prev], .product__swiper-button-p_container');
1326            var next = gallery.querySelector('[data-af-gallery-next], .product__swiper-button-n_container');
1327            var slider = gallery.querySelector('[data-af-gallery-slider], .product__swiper, .swiper_custom_mobile');
1328
1329            gallery.classList.toggle('af-product-detail__gallery--single', thumbs.length <= 1);
1330            if (prev) { prev.style.display = thumbs.length > 1 ? '' : 'none'; }
1331            if (next) { next.style.display = thumbs.length > 1 ? '' : 'none'; }
1332
1333            if (slider) {
1334                initDragScroll(slider);
1335                initWheelHorizontalScroll(slider);
1336            }
1337
1338            // fix61: не глушим inline onclick старой галереи.
1339            // Превью в шаблоне вызывает selectImageSlider(this), как в старой версии.
1340            // JS ниже нужен только как резерв, если inline-обработчик не сработал.
1341            gallery.querySelectorAll('[data-af-gallery-thumb], .img_wrap').forEach(function (thumb) {
1342                if (thumb.getAttribute('data-af-gallery-click-ready') === 'Y') { return; }
1343                thumb.setAttribute('data-af-gallery-click-ready', 'Y');
1344                thumb.addEventListener('click', function (event) {
1345                    if (slider && slider.getAttribute('data-af-gallery-dragging') === 'Y') {
1346                        return;
1347                    }
1348
1349                    if (typeof window.selectImageSlider === 'function') {
1350                        event.preventDefault();
1351                        window.selectImageSlider(thumb);
1352                    }
1353                }, false);
1354            });
1355        });
1356    }
1357
1358    function normalizeGalleryThumb(element) {
1359        if (!element) { return null; }
1360        if (element.matches && element.matches('.img_wrap')) { return element; }
1361        if (element.closest) { return element.closest('.img_wrap'); }
1362        return null;
1363    }
1364
1365    function getThumbMediaElement(thumb) {
1366        if (!thumb) { return null; }
1367        return thumb.querySelector('img, iframe, picture, video') || thumb.firstElementChild || null;
1368    }
1369
1370    function getThumbImageSrc(thumb, media) {
1371        if (!thumb) { return ''; }
1372
1373        var src = thumb.getAttribute('data-src') ||
1374            thumb.getAttribute('data-lazy-src') ||
1375            thumb.getAttribute('data-original') ||
1376            '';
1377
1378        if (!src && media) {
1379            src = media.getAttribute('data-src') ||
1380                media.getAttribute('data-lazy-src') ||
1381                media.getAttribute('data-original') ||
1382                media.getAttribute('src') ||
1383                '';
1384        }
1385
1386        if (!src && media && media.tagName && media.tagName.toUpperCase() === 'PICTURE') {
1387            var pictureImage = media.querySelector('img');
1388            if (pictureImage) {
1389                src = pictureImage.getAttribute('data-src') ||
1390                    pictureImage.getAttribute('data-lazy-src') ||
1391                    pictureImage.getAttribute('src') ||
1392                    '';
1393            }
1394        }
1395
1396        if (!src) {
1397            var nestedImage = thumb.querySelector('img');
1398            if (nestedImage) {
1399                src = nestedImage.getAttribute('data-src') ||
1400                    nestedImage.getAttribute('data-lazy-src') ||
1401                    nestedImage.getAttribute('src') ||
1402                    '';
1403            }
1404        }
1405
1406        return src || '';
1407    }
1408
1409    function getThumbIframeSrc(thumb, media) {
1410        if (!thumb) { return ''; }
1411        var iframe = media && media.tagName && media.tagName.toUpperCase() === 'IFRAME'
1412            ? media
1413            : thumb.querySelector('iframe');
1414        return iframe ? (iframe.getAttribute('data-src') || iframe.getAttribute('src') || '') : '';
1415    }
1416
1417    function getGalleryFromElement(element) {
1418        if (!element || !element.closest) { return null; }
1419        return element.closest('[data-af-legacy-gallery]') || element.closest('.af-product-detail__gallery');
1420    }
1421
1422    function getGalleryThumbs(gallery) {
1423        var root = gallery || document;
1424        var thumbs = Array.prototype.slice.call(root.querySelectorAll('[data-af-gallery-thumb], [data-af-gallery-slider] .img_wrap, .product__swiper .img_wrap, .swiper_custom_mobile .img_wrap'));
1425        return thumbs.filter(function (thumb, index, list) {
1426            return !!thumb.firstElementChild &&
1427                thumb.id !== 'main_fresca_image' &&
1428                list.indexOf(thumb) === index;
1429        });
1430    }
1431
1432    function getActiveThumbIndex(thumbs) {
1433        return thumbs.findIndex(function (thumb) {
1434            return thumb.classList.contains('select') || thumb.classList.contains('is-active');
1435        });
1436    }
1437
1438    function scrollActiveThumbIntoView(gallery, thumb) {
1439        var slider = gallery ? gallery.querySelector('[data-af-gallery-slider], .product__swiper, .swiper_custom_mobile') : null;
1440        if (!slider || !thumb || !thumb.closest) { return; }
1441
1442        var slide = thumb.closest('.swiper-slide') || thumb;
1443        if (typeof slide.scrollIntoView === 'function') {
1444            slide.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
1445        }
1446    }
1447
1448    function initDragScroll(slider) {
1449        if (slider.getAttribute('data-af-drag-scroll-ready') === 'Y') { return; }
1450        slider.setAttribute('data-af-drag-scroll-ready', 'Y');
1451
1452        var isDown = false;
1453        var isDragged = false;
1454        var startX = 0;
1455        var scrollLeft = 0;
1456        var startThumb = null;
1457
1458        slider.setAttribute('data-af-gallery-dragging', 'N');
1459
1460        slider.addEventListener('dragstart', function (event) {
1461            event.preventDefault();
1462        });
1463
1464        slider.addEventListener('pointerdown', function (event) {
1465            if (event.button !== undefined && event.button !== 0) { return; }
1466            isDown = true;
1467            isDragged = false;
1468            startX = event.clientX;
1469            scrollLeft = slider.scrollLeft;
1470            startThumb = event.target && event.target.closest ? event.target.closest('.img_wrap') : null;
1471            slider.classList.add('is-dragging');
1472            // Не даём браузеру начать drag картинки внутри превью. Клик обрабатываем на pointerup.
1473            event.preventDefault();
1474            if (slider.setPointerCapture && event.pointerId !== undefined) {
1475                try { slider.setPointerCapture(event.pointerId); } catch (e) {}
1476            }
1477        });
1478
1479        slider.addEventListener('pointermove', function (event) {
1480            if (!isDown) { return; }
1481            var dx = event.clientX - startX;
1482            if (Math.abs(dx) > 6) {
1483                isDragged = true;
1484                slider.setAttribute('data-af-gallery-dragging', 'Y');
1485                slider.setAttribute('data-af-gallery-last-drag-at', String(Date.now()));
1486            }
1487            slider.scrollLeft = scrollLeft - dx;
1488        });
1489
1490        var finishDrag = function (event, shouldSelect) {
1491            if (!isDown) { return; }
1492            var wasDragged = isDragged;
1493            var selectedThumb = startThumb;
1494            isDown = false;
1495            startThumb = null;
1496            slider.classList.remove('is-dragging');
1497
1498            if (shouldSelect && !wasDragged && selectedThumb && typeof window.selectImageSlider === 'function') {
1499                if (event) {
1500                    event.preventDefault();
1501                    event.stopPropagation();
1502                }
1503                window.selectImageSlider(selectedThumb);
1504            }
1505
1506            window.setTimeout(function () {
1507                isDragged = false;
1508                slider.setAttribute('data-af-gallery-dragging', 'N');
1509            }, 80);
1510        };
1511
1512        slider.addEventListener('pointerup', function (event) {
1513            finishDrag(event, true);
1514        });
1515        slider.addEventListener('pointercancel', function (event) {
1516            finishDrag(event, false);
1517        });
1518        slider.addEventListener('pointerleave', function (event) {
1519            if (isDown) { finishDrag(event, false); }
1520        });
1521
1522        slider.addEventListener('click', function (event) {
1523            if (isDragged) {
1524                event.preventDefault();
1525                event.stopPropagation();
1526            }
1527        }, true);
1528    }
1529
1530    function initWheelHorizontalScroll(slider) {
1531        if (slider.getAttribute('data-af-wheel-scroll-ready') === 'Y') { return; }
1532        slider.setAttribute('data-af-wheel-scroll-ready', 'Y');
1533
1534        slider.addEventListener('wheel', function (event) {
1535            if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) { return; }
1536            if (slider.scrollWidth <= slider.clientWidth) { return; }
1537            event.preventDefault();
1538            slider.scrollLeft += event.deltaY;
1539        }, { passive: false });
1540    }
1541
1542    function syncLegacySwiperClasses(gallery, activeThumb) {
1543        var slides = Array.prototype.slice.call(gallery.querySelectorAll('.swiper-slide'));
1544        if (!slides.length) { return; }
1545
1546        slides.forEach(function (slide) {
1547            slide.classList.remove('swiper-slide-active', 'swiper-slide-next', 'swiper-slide-prev');
1548        });
1549
1550        var activeSlide = activeThumb.closest('.swiper-slide');
1551        var activeIndex = slides.indexOf(activeSlide);
1552        if (activeIndex < 0) { return; }
1553
1554        slides[activeIndex].classList.add('swiper-slide-active');
1555        slides[(activeIndex + 1) % slides.length].classList.add('swiper-slide-next');
1556        slides[(activeIndex - 1 + slides.length) % slides.length].classList.add('swiper-slide-prev');
1557    }
1558
1559    function initGalleryThumbs() {
1560        document.querySelectorAll('[data-af-gallery-thumb].is-active, [data-af-gallery-thumb].select').forEach(function (thumb) {
1561            if (typeof window.selectImageSlider === 'function') {
1562                window.selectImageSlider(thumb);
1563                return;
1564            }
1565
1566            var src = thumb.getAttribute('data-src');
1567            var main = thumb.closest('.af-product-detail__gallery')?.querySelector('[data-af-main-image]');
1568            if (src && main && !main.getAttribute('src')) {
1569                main.setAttribute('src', src);
1570            }
1571        });
1572    }
1573
1574    function switchGalleryImage(button) {
1575        if (typeof window.selectImageSlider === 'function') {
1576            window.selectImageSlider(button);
1577            return;
1578        }
1579
1580        var gallery = button.closest('.af-product-detail__gallery');
1581        if (!gallery) { return; }
1582        var main = gallery.querySelector('[data-af-main-image]');
1583        var src = button.getAttribute('data-src');
1584        if (!main || !src) { return; }
1585        main.setAttribute('src', src);
1586        var alt = button.getAttribute('data-alt') || main.getAttribute('alt') || '';
1587        main.setAttribute('alt', alt);
1588        gallery.querySelectorAll('[data-af-gallery-thumb]').forEach(function (item) {
1589            item.classList.remove('is-active');
1590        });
1591        button.classList.add('is-active');
1592    }
1593
1594    function syncLegacySelfLayoutSize() {
1595        var widthInput = document.getElementById('width_holst') || document.querySelector('[data-af-width]');
1596        var heightInput = document.getElementById('height_holst') || document.querySelector('[data-af-height]');
1597        var calc = widthInput ? widthInput.closest('[data-af-price-calculator]') : document.querySelector('[data-af-price-calculator]');
1598
1599        if (calc) {
1600            updatePrice(calc);
1601        }
1602
1603        if (widthInput) {
1604            widthInput.dispatchEvent(new Event('input', { bubbles: true }));
1605        }
1606        if (heightInput) {
1607            heightInput.dispatchEvent(new Event('input', { bubbles: true }));
1608        }
1609
1610        syncStickyPanel();
1611    }
1612
1613    if (typeof window.setSize !== 'function') {
1614        window.setSize = function (callback) {
1615            syncLegacySelfLayoutSize();
1616            if (typeof callback === 'function') {
1617                callback();
1618            }
1619        };
1620    }
1621
1622    function addReadyBasket(button) {
1623        if (isBasketButtonInCart(button)) {
1624            openBasket(button);
1625            return;
1626        }
1627        if (button.disabled) { return; }
1628
1629        var productId = String(button.getAttribute('data-product-id') || '');
1630        if (!productId) {
1631            window.afFormMessage.at(button.closest('.af-product-detail, .af-product-card') || button.parentElement, "error")('Не удалось определить товар');
1632            return;
1633        }
1634
1635        var data = new FormData();
1636        data.append('product_id', productId);
1637        appendBitrixSessid(data);
1638        var loadingStartedAt = Date.now();
1639        setBasketButtonsState(productId, 'loading');
1640
1641        fetch('/local/ajax/catalog_add_to_basket.php', { method: 'POST', body: data, credentials: 'same-origin' })
1642            .then(function (res) { return res.json(); })
1643            .then(function (json) {
1644                if (!json.success) {
1645                    setBasketButtonsState(productId, 'idle');
1646                    window.afFormMessage.at(button.closest('.af-product-detail, .af-product-card') || button.parentElement, "error")(json.message || 'Не удалось добавить товар в корзин
1646у');
1647                    return;
1648                }
1649
1650                if (json.added === true) {
1651                    reachGoal('catalog_ready_add_to_cart_success');
1652                    reachGoal('ecom_add_to_cart', {
1653                        product_id: productId,
1654                        product_type: 'ready_product'
1655                    });
1656                }
1657
1658                finishBasketLoading(loadingStartedAt, function () {
1659                    syncAddedBasketButtons(productId);
1660                    if (json.added === true) {
1661                        notifyBasketChanged(json);
1662                    } else {
1663                        updateHeaderBasketFallback(json);
1664                    }
1665                });
1666            })
1667            .catch(function () {
1668                setBasketButtonsState(productId, 'idle');
1669                window.afFormMessage.at(button.closest('.af-product-detail, .af-product-card') || button.parentElement, "error")('Ошибка добавления в корзину');
1670                scheduleCurrentProductBasketStateRefresh();
1671            });
1672    }
1673
1674    function initVisualRequestModal() {
1675        var modal = document.getElementById('modalVisulate');
1676        if (!modal) { return; }
1677        document.querySelectorAll('.visual_form').forEach(function (form) {
1678            if (form.dataset.afBound === 'Y') { return; }
1679            form.dataset.afBound = 'Y';
1680            form.addEventListener('submit', function (event) {
1681                event.preventDefault();
1682
1683                var attribution =
1684                    window.afMetrikaAttribution
1685                        ? window.afMetrikaAttribution.get()
1686                        : {
1687                            clientId: '',
1688                            yclid: '',
1689                            token: ''
1690                        };
1691
1692                var data = new FormData(form);
1693
1694                data.set(
1695                    'ym_client_id',
1696                    attribution.clientId || ''
1697                );
1698                data.set(
1699                    'yclid',
1700                    attribution.yclid || ''
1701                );
1702                data.set(
1703                    'attribution_token',
1704                    attribution.token || ''
1705                );
1706                data.set(
1707                    'page_url',
1708                    window.location.href
1709                );
1710
1711                var persistPromise =
1712                    Promise.resolve();
1713
1714                if (
1715                    window.afMetrikaAttribution
1716                    && typeof window.afMetrikaAttribution.persist === 'function'
1717                ) {
1718                    persistPromise =
1719                        window.afMetrikaAttribution
1720                            .persist('site_form')
1721                            .catch(function (error) {
1722                                console.warn(
1723                                    '[ArtFresco] visual form attribution persist failed',
1724                                    error
1725                                );
1726                            });
1727                }
1728
1729                persistPromise
1730                    .then(function () {
1731                        return window.afFormGuard.fetch(
1732                            '/local/templates/fresco/ajax/send_visual_request.php',
1733                            {
1734                                method: 'POST',
1735                                body: data,
1736                                credentials: 'same-origin'
1737                            }
1738                        );
1739                    })
1740                    .then(function (res) {
1741                        if (!res.ok) {
1742                            throw new Error(
1743                                'bad-response'
1744                            );
1745                        }
1746
1747                        return res.json();
1748                    })
1749                    .then(function (json) {
1750                        if (
1751                            !json
1752                            || json.STATUS !== true
1753                        ) {
1754                            throw new Error(
1755                                json && json.MESSAGE
1756                                    ? json.MESSAGE
1757                                    : 'bad-response'
1758                            );
1759                        }
1760
1761                        window.afFormMessage.at(null, "status")(
1762                            'Форма успешно отправлена!'
1763                        );
1764                        closeVisualRequestModal();
1765                        form.reset();
1766                    })
1767                    .catch(function (error) {
1768                        console.error(
1769                            '[ArtFresco] visual form failed',
1770                            error
1771                        );
1772                        window.afFormMessage.at(form, "error")('Ошибка отправки');
1773                    });
1774            });
1775        });
1776    }
1777
1778    function openVisualRequestModal() {
1779        var modal = document.getElementById('modalVisulate');
1780        if (modal) {
1781            modal.style.display = 'flex';
1782            modal.setAttribute('aria-hidden', 'false');
1783        }
1784    }
1785
1786    function closeVisualRequestModal() {
1787        var modal = document.getElementById('modalVisulate');
1788        if (modal) {
1789            modal.style.display = 'none';
1790            modal.setAttribute('aria-hidden', 'true');
1791        }
1792    }
1793
1794    function syncStickyPanel() {
1795        var calc = document.querySelector('[data-af-price-calculator]');
1796        if (!calc) { return; }
1797        updatePrice(calc);
1798    }
1799
1800    function isMobileFilterViewport() {
1801        if (window.matchMedia) {
1802            return window.matchMedia(MOBILE_FILTER_MEDIA).matches;
1803        }
1804
1805        return (window.innerWidth || document.documentElement.clientWidth) <= 900;
1806    }
1807
1808    function getMobileFilterPanel() {
1809        return document.getElementById('afCatalogFilterPanel');
1810    }
1811
1812    function getMobileFilterForm() {
1813        var panel = getMobileFilterPanel();
1814        return panel ? panel.querySelector('form.af-smart-filter[data-af-sef-filter="Y"]') : null;
1815    }
1816
1817    function createMobileFilterSnapshot(form) {
1818        var applyLabel = form.querySelector('[data-af-filter-appl
1818y-label]');
1819
1820        return {
1821            values: Array.prototype.map.call(
1822                form.querySelectorAll('input[type="checkbox"], input[type="radio"]'),
1823                function (input) {
1824                    return {
1825                        input: input,
1826                        checked: input.checked
1827                    };
1828                }
1829            ),
1830            applyText: applyLabel ? applyLabel.textContent : '',
1831            resultCount: form.getAttribute('data-af-result-count') || ''
1832        };
1833    }
1834
1835    function restoreMobileFilterSnapshot() {
1836        var snapshot = mobileFilterState.snapshot;
1837        var form = getMobileFilterForm();
1838
1839        if (!snapshot || !form) {
1840            return;
1841        }
1842
1843        snapshot.values.forEach(function (item) {
1844            if (item.input && item.input.isConnected) {
1845                item.input.checked = !!item.checked;
1846            }
1847        });
1848
1849        var applyLabel = form.querySelector('[data-af-filter-apply-label]');
1850        if (applyLabel && snapshot.applyText) {
1851            applyLabel.textContent = snapshot.applyText;
1852        }
1853
1854        if (snapshot.resultCount !== '') {
1855            form.setAttribute('data-af-result-count', snapshot.resultCount);
1856        }
1857    }
1858
1859    function resetMobileFilterSearchState(form) {
1860        if (!form) {
1861            return;
1862        }
1863
1864        form.querySelectorAll('[data-af-filter-search]').forEach(function (input) {
1865            input.value = '';
1866        });
1867
1868        form.querySelectorAll('[data-af-filter-value][hidden]').forEach(function (value) {
1869            value.hidden = false;
1870        });
1871
1872        form.querySelectorAll('[data-af-filter-group].is-af-searching').forEach(function (group) {
1873            group.classList.remove('is-af-searching');
1874        });
1875
1876        form.querySelectorAll('[data-af-filter-search-empty]').forEach(function (empty) {
1877            empty.hidden = true;
1878        });
1879    }
1880
1881    function prepareMobileFilterGroups(form) {
1882        if (!form) {
1883            return;
1884        }
1885
1886        form.querySelectorAll('[data-af-filter-group]').forEach(function (group) {
1887            var hasChecked = !!group.querySelector('input[type="checkbox"]:checked, input[type="radio"]:checked');
1888            group.open = hasChecked;
1889            group.classList.remove('is-af-values-expanded', 'is-af-searching');
1890
1891            var more = group.querySelector('[data-af-filter-more]');
1892            if (more) {
1893                more.hidden = false;
1894            }
1895        });
1896    }
1897
1898    function syncMobileFilterUi() {
1899        var form = getMobileFilterForm();
1900        var toggle = document.querySelector('[data-af-filter-toggle]');
1901
1902        if (!form) {
1903            return;
1904        }
1905
1906        var checked = Array.prototype.slice.call(
1907            form.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked')
1908        );
1909
1910        form.querySelectorAll('[data-af-filter-value]').forEach(function (valueNode) {
1911            var input = valueNode.querySelector('input[type="checkbox"], input[type="radio"]');
1912            valueNode.classList.toggle('is-checked', !!(input && input.checked));
1913        });
1914
1915        form.querySelectorAll('[data-af-filter-group]').forEach(function (group) {
1916            var count = group.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked').length;
1917            var countNode = group.querySelector('[data-af-filter-group-selected-count]');
1918
1919            if (countNode) {
1920                countNode.textContent = count ? String(count) : '';
1921                countNode.hidden = count === 0;
1922            }
1923        });
1924
1925        var activeCountNodes = form.querySelectorAll('[data-af-mobile-filter-active-count]');
1926        activeCountNodes.forEach(function (node) {
1927            node.textContent = checked.length ? '· ' + checked.length : '';
1928            node.hidden = checked.length === 0;
1929        });
1930
1931        if (toggle) {
1932            var toggleCount = toggle.querySelector('[data-af-filter-toggle-count]');
1933            toggle.setAttribute('aria-label', checked.length ? 'Фильтры, выбрано: ' + checked.length : 'Фильтры');
1934
1935            if (toggleCount) {
1936                toggleCount.textContent = checked.length ? String(checked.length) : '';
1937                toggleCount.hidden = checked.length === 0;
1938            }
1939        }
1940
1941        var zeroState = form.querySelector('[data-af-filter-zero]');
1942        var resultCount = parseInt(form.getAttribute('data-af-result-count') || '', 
194210);
1943        if (zeroState && !isNaN(resultCount)) {
1944            zeroState.hidden = resultCount !== 0;
1945        }
1946
1947        syncMobileFilterSelectedChips(form, checked);
1948    }
1949
1950    function syncMobileFilterSelectedChips(form, checked) {
1951        var section = form.querySelector('[data-af-filter-selected]');
1952        var chips = form.querySelector('[data-af-filter-selected-chips]');
1953
1954        if (!section || !chips) {
1955            return;
1956        }
1957
1958        chips.innerHTML = '';
1959
1960        checked.forEach(function (input) {
1961            var valueNode = input.closest('[data-af-filter-value]');
1962            var textNode = valueNode ? valueNode.querySelector('.af-filter-value__text') : null;
1963            var text = textNode ? textNode.textContent.trim() : '';
1964
1965            if (!text) {
1966                return;
1967            }
1968
1969            var chip = document.createElement('button');
1970            chip.type = 'button';
1971            chip.className = 'af-mobile-filter__chip';
1972            chip.setAttribute('data-af-filter-chip', 'Y');
1973            chip.setAttribute('aria-label', 'Убрать фильтр «' + text + '»');
1974            chip.textContent = text;
1975            chip.afFilterInput = input;
1976
1977            var close = document.createElement('span');
1978            close.setAttribute('aria-hidden', 'true');
1979            close.textContent = '×';
1980            chip.appendChild(close);
1981            chips.appendChild(chip);
1982        });
1983
1984        section.hidden = chips.children.length === 0;
1985    }
1986
1987    function filterMobileFilterValues(input) {
1988        var group = input.closest('[data-af-filter-group]');
1989        if (!group) {
1990            return;
1991        }
1992
1993        var query = String(input.value || '').trim().toLocaleLowerCase('ru-RU');
1994        var matched = 0;
1995
1996        group.classList.toggle('is-af-searching', query !== '');
1997
1998        group.querySelectorAll('[data-af-filter-value]').forEach(function (valueNode) {
1999            var textNode = valueNode.querySelector('.af-filter-value__text');
2000            var text = textNode ? textNode.textContent.toLocaleLowerCase('ru-RU') : '';
2001            var visible = !query || text.indexOf(query) !== -1;
2002
2003            valueNode.hidden = !visible;
2004            if (visible) {
2005                matched += 1;
2006            }
2007        });
2008
2009        var empty = group.querySelector('[data-af-filter-search-empty]');
2010        if (empty) {
2011            empty.hidden = query === '' || matched > 0;
2012        }
2013    }
2014
2015    function expandMobileFilterValues(button) {
2016        var group = button.closest('[data-af-filter-group]');
2017        if (!group) {
2018            return;
2019        }
2020
2021        group.classList.add('is-af-values-expanded');
2022        button.hidden = true;
2023    }
2024
2025    function removeMobileFilterChip(chip) {
2026        var input = chip.afFilterInput;
2027        if (!input || !input.isConnected) {
2028            return;
2029        }
2030
2031        input.checked = false;
2032        input.dispatchEvent(new Event('change', { bubbles: true }));
2033        syncMobileFilterUi();
2034    }
2035
2036    function focusWithoutScroll(element) {
2037        if (!element || typeof element.focus !== 'function') {
2038            return;
2039        }
2040
2041        try {
2042            element.focus({ preventScroll: true });
2043        } catch (ignore) {
2044            element.focus();
2045        }
2046    }
2047
2048    function lockMobileFilterBody(panel) {
2049        mobileFilterState.scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
2050        document.body.style.setProperty('--af-filter-lock-top', '-' + mobileFilterState.scrollY + 'px');
2051        document.body.classList.add('af-filter-open');
2052
2053        panel.setAttribute('role', 'dialog');
2054        panel.setAttribute('aria-modal', 'true');
2055        panel.setAttribute('aria-label', 'Фильтры каталога');
2056        panel.setAttribute('aria-hidden', 'false');
2057    }
2058
2059    function unlockMobileFilterBody(panel) {
2060        document.body.classList.remove('af-filter-open');
2061        document.body.style.removeProperty('--af-filter-lock-top');
2062
2063        if (panel) {
2064            panel.removeAttribute('role');
2065            panel.removeAttribute('aria-modal');
2066            panel.removeAttribute('aria-label');
2067            if (isMobileFilterViewport()) {
2068                panel.setAttribute('aria-hidden', 'true');
2069            } else {
2070                panel.removeAttribute('aria-hidden');
2071            }
2072        }
2073
2074        window.scrollTo(0, mobileFilterState.scrollY || 0);
2075    }
2076
2077    function openMobileCatalogFilter(trigger, options) {
2078        options = options || {};
2079
2080        if (!isMobileFilterViewport() || mobileFilterState.open) {
2081            return false;
2082        }
2083
2084        var panel = getMobileFilterPanel();
2085        var form = getMobileFilterForm();
2086
2087        if (!panel || !form || panel.hasAttribute('hidden')) {
2088            return false;
2089        }
2090
2091        mobileFilterState.open = true;
2092        mobileFilterState.closePending = false;
2093        mobileFilterState.trigger = trigger || document.querySelector('[data-af-filter-toggle]');
2094        mobileFilterState.snapshot = createMobileFilterSnapshot(form);
2095        mobileFilterState.baseHistoryState = window.history.state;
2096
2097        resetMobileFilterSearchState(form);
2098        prepareMobileFilterGroups(form);
2099        syncMobileFilterUi();
2100        lockMobileFilterBody(panel);
2101
2102        var toggle = document.querySelector('[data-af-filter-toggle]');
2103        if (toggle) {
2104            toggle.setAttribute('aria-expanded', 'true');
2105        }
2106
2107        if (options.pushHistory !== false && window.history && window.history.pushState) {
2108            var nextState = Object.assign({}, window.history.state || {}, { afFilterOverlay: true });
2109            window.history.pushState(nextState, '', window.location.href);
2110        }
2111
2112        document.dispatchEvent(new CustomEvent('af:mobile-filter-opened'));
2113
2114        window.requestAnimationFrame(function () {
2115            var close = panel.querySelector('[data-af-filter-close]');
2116            if (close) {
2117                focusWithoutScroll(close);
2118            }
2119        });
2120
2121        return true;
2122    }
2123
2124    function closeMobileCatalogFilter(options) {
2125        options = options || {};
2126
2127        if (!mobileFilterState.open) {
2128            return false;
2129        }
2130
2131        var panel = getMobileFilterPanel();
2132        var trigger = mobileFilterState.trigger;
2133
2134        if (options.discard !== false) {
2135            restoreMobileFilterSnapshot();
2136        }
2137
2138        resetMobileFilterSearchState(getMobileFilterForm());
2139        syncMobileFilterUi();
2140        unlockMobileFilterBody(panel);
2141
2142        mobileFilterState.open = false;
2143        mobileFilterState.closePending = false;
2144        mobileFilterState.snapshot = null;
2145
2146        var toggle = document.querySelector('[data-af-filter-toggle]');
2147        if (toggle) {
2148            toggle.setAttribute('aria-expanded', 'false');
2149        }
2150
2151        document.dispatchEvent(new CustomEvent('af:mobile-filter-closed', {
2152            detail: { discard: options.discard !== false }
2153        }));
2154
2155        if (options.restoreFocus !== false) {
2156            if (!trigger || !trigger.isConnected) {
2157                trigger = document.querySelector('[data-af-filter-toggle]');
2158            }
2159
2160            if (trigger) {
2161                window.requestAnimationFrame(function () {
2162                    focusWithoutScroll(trigger);
2163                });
2164            }
2165        }
2166
2167        mobileFilterState.trigger = null;
2168        return true;
2169    }
2170
2171    function requestCloseMobileCatalogFilter() {
2172        if (!mobileFilterState.open || mobileFilterState.closePending) {
2173            return;
2174        }
2175
2176        if (window.history && window.history.state && window.history.state.afFilterOverlay) {
2177            mobileFilterState.closePending = true;
2178            window.history.back();
2179            return;
2180        }
2181
2182        closeMobileCatalogFilter({ discard: true });
2183    }
2184
2185    function commitAndCloseMobileCatalogFilter() {
2186        closeMobileCatalogFilter({ discard: false });
2187    }
2188
2189    function abandonMobileCatalogFilterForNavigation() {
2190        if (!mobileFilterState.open) {
2191            return;
2192        }
2193
2194        if (window.history && window.history.replaceState && window.history.state && window.history.state.afFilterOverlay) {
2195            window.history.replaceState(
2196                mobileFilterState.baseHistoryState || {},
2197                '',
2198                window.location.href
2199            );
2200        }
2201
2202        closeMobileCatalogFilter({ discard: false, restoreFocus: false });
2203    }
2204
2205    function handleMobileCatalogFilterPopState(event) {
2206        if (mobileFilterState.open) {
2207            closeMobileCatalogFilter({ discard: true });
2208            return true;
2209        }
2210
2211        if (
2212            isMobileFilterViewport() &&
2213            event &&
2214            event.state &&
2215            event.state.afFilterOverlay
2216        ) {
2217            return openMobileCatalogFilter(
2218                document.querySelector('[data-af-filter-toggle]'),
2219                { pushHistory: false }
2220            );
2221        }
2222
2223        return false;
2224    }
2225
2226    function trapMobileFilterFocus(event) {
2227        var panel = getMobileFilterPanel();
2228        if (!panel) {
2229            return;
2230        }
2231
2232        var focusable = Array.prototype.filter.call(
2233            panel.querySelectorAll(
2234                'button:not([disabled]), a[href], summary, input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
2235            ),
2236            function (element) {
2237                return !element.hidden && element.getClientRects().length > 0;
2238            }
2239        );
2240
2241        if (!focusable.length) {
2242            return;
2243        }
2244
2245        var first = focusable[0];
2246        var last = focusable[focusable.length - 1];
2247
2248        if (event.shiftKey && document.activeElement === first) {
2249            event.preventDefault();
2250            last.focus();
2251        } else if (!event.shiftKey && document.activeElement === last) {
2252            event.preventDefault();
2253            first.focus();
2254        }
2255    }
2256
2257    function initMobileCatalogFilter() {
2258        var panel = getMobileFilterPanel();
2259        if (panel && isMobileFilterViewport() && !mobileFilterState.open) {
2260            panel.setAttribute('aria-hidden', 'true');
2261        }
2262
2263        window.addEventListener('resize', function () {
2264            var currentPanel = getMobileFilterPanel();
2265
2266            if (!isMobileFilterViewport()) {
2267                if (mobileFilterState.open) {
2268                    requestCloseMobileCatalogFilter();
2269                } else if (currentPanel) {
2270                    currentPanel.removeAttribute('aria-hidden');
2271                }
2272            } else if (currentPanel && !mobileFilterState.open) {
2273                currentPanel.setAttribute('aria-hidden', 'true');
2274            }
2275        });
2276    }
2277
2278    window.afCatalogMobileFilter = {
2279        isOpen: function () {
2280            return mobileFilterState.open;
2281        },
2282        sync: syncMobileFilterUi,
2283        handlePopState: handleMobileCatalogFilterPopState,
2284        commitAndClose: commitAndCloseMobileCatalogFilter,
2285        abandonForNavigation: abandonMobileCatalogFilterForNavigation
2286    };
2287
2288    function normalizeFilterLayout() {
2289        var layout = document.querySelector('.af-section-layout');
2290        var filter = document.querySelector('.af-section-filter');
2291        var mobileButton = document.querySelector('[data-af-filter-toggle]');
2292
2293        if (!layout || !filter) {
2294            return;
2295        }
2296
2297        if (!filter.querySelector('.af-smart-filter')) {
2298            layout.classList.add('af-section-layout--no-filter');
2299            filter.setAttribute('hidden', 'hidden');
2300            if (mobileButton) {
2301                mobileButton.setAttribute('hidden', 'hidden');
2302            }
2303            return;
2304        }
2305
2306        layout.classList.remove('af-section-layout--no-filter');
2307        filter.removeAttribute('hidden');
2308        if (mobileButton) {
2309            mobileButton.removeAttribute('hidden');
2310        }
2311    }
2312
2313    function initShortCartPanel() {
2314        var panel = document.querySelector('.sticky__menu');
2315        if (!panel) {
2316            return;
2317        }
2318
2319        var targetSelectors = ['.af-ready-info', '.af-size-calc'];
2320
2321        var getTargetBlocks = function () {
2322            return targetSelectors
2323                .map(function (selector) { return document.querySelector(selector); })
2324                .filter(function (element) {
2325                    return element && element.getClientRects().length > 0;
2326                });
2327        };
2328
2329        var isModalOpen = function () {
2330            var orderModal = document.querySelector('#orderModal');
2331            if (orderModal && orderModal.classList.contains('show')) {
2332                return true;
2333            }
2334
2335            var visualModal = document.querySelector('#modalVisulate');
2336            if (visualModal && (visualModal.classList.contains('show') || visualModal.getAttribute('aria-hidden') === 'false')) {
2337                return true;
2338            }
2339
2340            return !!document.querySelector('.modal.show');
2341        };
2342
2343        var closePanel = function () {
2344            panel.classList.add('closed');
2345            panel.classList.remove('is-visible');
2346        };
2347
2348        var openPanel = function () {
2349            panel.classList.remove('closed');
2350            panel.classList.add('is-visible');
2351        };
2352
2353        var isBlockVisible = function (element) {
2354            var rect = element.getBoundingClientRect();
2355            var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
2356            return rect.bottom > 0 && rect.top < viewportHeight;
2357        };
2358
2359        var toggle = function () {
2360            if (isModalOpen()) {
2361                closePanel();
2362                return;
2363            }
2364
2365            var targetBlocks = getTargetBlocks();
2366            if (!targetBlocks.length) {
2367                closePanel();
2368                return;
2369            }
2370
2371            var hasVisibleTargetBlock = targetBlocks.some(function (block) {
2372                return isBlockVisible(block);
2373            });
2374
2375            if (hasVisibleTargetBlock) {
2376                closePanel();
2377            } else {
2378                openPanel();
2379            }
2380        };
2381
2382        toggle();
2383        window.addEventListener('scroll', toggle, { passive: true });
2384        window.addEventListener('resize', toggle);
2385        document.addEventListener('shown.bs.modal', toggle);
2386        document.addEventListener('hidden.bs.modal', toggle);
2387    }
2388
2389    function formatPhone(value) {
2390        var digits = String(value || '').replace(/\D/g, '');
2391        if (digits[0] === '8') {
2392            digits = '7' + digits.slice(1);
2393        }
2394        if (digits[0] !== '7') {
2395            digits = '7' + digits;
2396        }
2397        digits = digits.slice(0, 11);
2398
2399        var result = '+7';
2400        if (digits.length > 1) { result += ' (' + digits.slice(1, 4); }
2401        if (digits.length >= 4) { result += ')'; }
2402        if (digits.length > 4) { result += ' ' + digits.slice(4, 7); }
2403        if (digits.length > 7) { result += '-' + digits.slice(7, 9); }
2404        if (digits.length > 9) { result += '-' + digits.slice(9, 11); }
2405        return result;
2406    }
2407
2408    function escapeHtml(value) {
2409        return String(value || '')
2410            .replace(/&/g, '&amp;')
2411            .replace(/</g, '&lt;')
2412            .replace(/>/g, '&gt;')
2413            .replace(/"/g, '&quot;')
2414            .replace(/'/g, '&#039;');
2415    }
2416
2417    function formatPrice(value) {
2418        return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
2419    }
2420})();

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.