PageSourceSearch

https://slamdunk.shop/wp-content/themes/slds/js/catalog-client/pag…3f2111b928f781385cde3486e4f233f472d69a555fb

js slamdunk.shop collected 2026-09-24 12:18:24 UTC 143,231 bytes, 4,207 lines download raw bytes

1if (window.Swiper && !window.Scv3Swiper) {
2	window.Scv3Swiper = window.Swiper;
3}
4
5function scv3InitFilterBlocks() {
6	const blocks = document.querySelectorAll('.scv3-filter-block');
7	if (!blocks.length) return;
8
9	blocks.forEach((block) => {
10		const toggle = block.querySelector('[data-scv3-filter-toggle]');
11		const content = block.querySelector('[data-scv3-filter-content]');
12		const more = block.querySelector('[data-scv3-filter-more]');
13		const searchInput = block.querySelector('[data-scv3-filter-search]');
14		const searchClear = block.querySelector('[data-scv3-filter-search-clear]');
15		if (!toggle || !content) return;
16		const isFilterSheet = !!block.closest('[data-scv3-filter-sheet]');
17		const isSidebarFilters = !!block.closest('.scv3-filters')
18			&& !isFilterSheet
19			&& !block.closest('[data-scv3-filter-all-sheet]');
20		const isStatic = block.classList.contains('scv3-filter-block--static');
21		const forceOpen = block.classList.contains('scv3-is-open');
22
23		const rawLimit = parseInt(content.dataset.scv3FilterLimit || '0', 10);
24		const isSizesFilter = content.classList.contains('scv3-filter-list--sizes');
25		const isDesktop = typeof window !== 'undefined' &&
26			window.matchMedia &&
27			window.matchMedia('(min-width: 769px)').matches;
28		const baseLimit = (isSizesFilter && isDesktop) ? Math.max(rawLimit, 10) : rawLimit;
29		const limit = isFilterSheet ? 0 : baseLimit;
30		const options = Array.from(content.querySelectorAll('[data-scv3-filter-option]'));
31		const canExpand = limit > 0 && options.length > limit;
32		const hasScroll = content.classList.contains('scv3-filter-list--scroll');
33		const scrollOnExpand = content.hasAttribute('data-scv3-scroll-on-expand');
34		let isExpanded = false;
35		let searchQuery = '';
36
37		const selectedIdx = options.findIndex((opt) => {
38			return (
39				(opt.querySelector('input[type="checkbox"]') || opt.querySelector('input[type="radio"]'))?.checked ||
40				opt.classList.contains('scv3-is-current')
41			);
42		});
43		const hasPriceValue = content.classList.contains('scv3-filter-list--price')
44			? Array.from(content.querySelectorAll('input')).some((inp) => (inp.value || '').trim() !== '')
45			: false;
46		if (!isSidebarFilters && selectedIdx >= 0 && canExpand && selectedIdx >= limit) {
47			isExpanded = true; // раскрываем, чтобы выбранный элемент не скрывался
48		}
49
50		if (more && !canExpand) {
51			more.addEventListener('click', (e) => e.preventDefault());
52		}
53
54		const normalize = (val) => String(val || '').toLowerCase().trim();
55		const getOptionText = (opt) => {
56			if (opt.dataset.scv3FilterLabel) return opt.dataset.scv3FilterLabel;
57			const text = (opt.textContent || '').replace(/\s+/g, ' ').trim();
58			opt.dataset.scv3FilterLabel = normalize(text);
59			return opt.dataset.scv3FilterLabel;
60		};
61
62		const updateOptions = () => {
63			const searchActive = searchQuery !== '';
64			const limitActive = canExpand && !isExpanded && !searchActive;
65			options.forEach((opt, idx) => {
66				const input = opt.querySelector('input[type="checkbox"], input[type="radio"]');
67				const isSelected = (input && input.checked) || opt.classList.contains('scv3-is-current');
68				const matches = !searchActive || getOptionText(opt).includes(searchQuery);
69				const hiddenBySearch = searchActive && !matches && !isSelected;
70				const hiddenByLimit = limitActive && idx >= limit;
71				const hidden = hiddenBySearch || hiddenByLimit;
72				opt.classList.toggle('scv3-filter-option--hidden', hiddenByLimit);
73				opt.classList.toggle('scv3-filter-option--search-hidden', hiddenBySearch);
74				opt.style.display = hidden ? 'none' : '';
75			});
76			if (hasScroll) {
77				if (scrollOnExpand) {
78					content.classList.toggle('scv3-filter-list--scroll-active', isExpanded);
79				} else {
80					content.classList.add('scv3-filter-list--scroll-active');
81				}
82			}
83			if (more) {
84				more.classList.toggle('scv3-filter-more--expanded', isExpanded);
85				const labelEl = more.querySelector('[data-scv3-filter-more-label]');
86				const expandLabel = more.getAttribute('data-label-expand') || 'Показать всё';
87				const collapseLabel = more.getAttribute('data-label-collapse') || 'Скрыть';
88				if (labelEl) {
89					labelEl.textContent = isExpanded ? collapseLabel : expandLabel;
90				}
91			}
92		};
93
94		if (more && canExpand) {
95			more.addEventListener('click', (e) => {
96				e.preventDefault();
97				isExpanded = !isExpanded;
98				updateOptions();
99			});
100		}
101
102		const setOpen = (isOpen) => {
103			block.classList.toggle('scv3-is-open', isOpen);
104			content.style.display = isOpen ? 'grid' : 'none';
105			if (more) {
106				const searchActive = searchQuery !== '';
107				const shouldShowMore = isOpen && !searchActive &&
108					(canExpand || more.hasAttribute('data-label-expand'));
109				more.style.display = shouldShowMore ? 'inline-flex' : 'none';
110			}
111			if (isOpen) {
112				updateOptions();
113			}
114		};
115
116		if (isStatic) {
117			setOpen(true);
118		} else {
119			const hasSelected = selectedIdx >= 0 || hasPriceValue;
120			setOpen(forceOpen || hasSelected);
121		}
122
123		if (searchInput) {
124			const applySearch = () => {
125				searchQuery = normalize(searchInput.value);
126				if (searchClear) {
127					searchClear.classList.toggle('scv3-is-visible', searchQuery !== '');
128				}
129				updateOptions();
130				if (more) {
131					const shouldShowMore = block.classList.contains('scv3-is-open') &&
132						!searchQuery && (canExpand || more.hasAttribute('data-label-expand'));
133					more.style.display = shouldShowMore ? 'inline-flex' : 'none';
134				}
135			};
136			searchInput.addEventListener('input', applySearch);
137			searchInput.addEventListener('search', applySearch);
138			if (searchClear) {
139				searchClear.addEventListener('click', (e) => {
140					e.preventDefault();
141					searchInput.value = '';
142					searchInput.focus();
143					applySearch();
144				});
145			}
146			applySearch();
147		}
148
149		if (!isStatic) {
150			toggle.addEventListener('click', () => {
151				setOpen(!block.classList.contains('scv3-is-open'));
152			});
153		}
154	});
155}
156
157function scv3InitFilterColors(root = document) {
158	const nodes = root.querySelectorAll('.scv3-filter-list--colors [data-scv3-color]');
159	if (!nodes.length) return;
160
161	const normalize = (val) => {
162		const raw = String(val || '').trim();
163		const match = raw.match(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
164		return match ? `#${match[1].toLowerCase()}` : '';
165	};
166	const toRgb = (hex) => {
167		const clean = hex.replace('#', '');
168		if (clean.length === 3) {
169			return clean.split('').map((c) => parseInt(c + c, 16));
170		}
171		return [clean.slice(0, 2), clean.slice(2, 4), clean.slice(4, 6)].map((c) => parseInt(c, 16));
172	};
173	const luminance = (r, g, b) => {
174		const srgb = [r, g, b].map((v) => {
175			const c = v / 255;
176			return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
177		});
178		return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
179	};
180	const contrastColor = (hex) => {
181		const [r, g, b] = toRgb(hex);
182		const lum = luminance(r, g, b);
183		const whiteContrast = (1.05) / (lum + 0.05);
184		const blackContrast = (lum + 0.05) / 0.05;
185		return whiteContrast >= blackContrast ? '#ffffff' : '#171717';
186	};
187	const isWhite = (hex) => {
188		const clean = hex.toLowerCase();
189		return clean === '#fff' || clean === '#ffffff';
190	};
191	const whiteBorderColor = '#cfcfcf';
192
193	nodes.forEach((label) => {
194		const raw = label.getAttribute('data-scv3-color') || '';
195		const color = normalize(raw);
196		if (!color) return;
197		const contrast = contrastColor(color);
198		label.style.setProperty('--scv3-filter-color', color);
199		label.style.setProperty('--scv3-filter-color-border', isWhite(color) ? whiteBorderColor : 'transparent');
200		label.style.setProperty('--scv3-filter-color-contrast', contrast);
201	});
202}
203(function () {
204	const categorySliderEl = document.querySelector('.scv3-category-chips');
205	const categoryPrev = document.querySelector('.scv3-slider-btn_prev');
206	const categoryNext = document.querySelector('.scv3-slider-btn_next');
207	const SwiperCtor = window.Scv3Swiper || window.Swiper;
208
209	const enhanceProductThumbs = () => {
210		const cards = document.querySelectorAll('.scv3-product-card');
211		cards.forEach((card) => {
212			const thumb = card.querySelector('.scv3-product-card__thumb');
213			if (!thumb || thumb.dataset.scv3SkipEnhance || thumb.querySelector('[data-scv3-card-swiper]')) return;
214
215			const mainImg = thumb.querySelector('img');
216			const variantThumbImgs = Array.from(card.querySelectorAll('.scv3-variant-thumb img'));
217			const images = [];
218
219			if (mainImg && mainImg.src) {
220				images.push({ src: mainImg.src, alt: mainImg.alt || '' });
221			}
222			variantThumbImgs.forEach((img) => {
223				if (img && img.src) {
224					images.push({ src: img.src, alt: img.alt || (mainImg ? mainImg.alt : '') });
225				}
226			});
227
228			const uniq = [];
229			const seen = new Set();
230			images.forEach((img) => {
231				const key = (img.src || '').trim();
232				if (!key || seen.has(key)) return;
233				seen.add(key);
234				uniq.push(img);
235			});
236
237			if (uniq.length <= 1) return;
238
239			const favoriteBtn = thumb.querySelector('.scv3-product-card__favorite');
240			if (favoriteBtn) {
241				thumb.removeChild(favoriteBtn);
242			}
243			thumb.innerHTML = '';
244			if (favoriteBtn) {
245				thumb.appendChild(favoriteBtn);
246			}
247
248			const swiperEl = document.createElement('div');
249			swiperEl.className = 'scv3-card-swiper swiper';
250			swiperEl.setAttribute('data-scv3-card-swiper', '');
251
252			const wrapper = document.createElement('div');
253			wrapper.className = 'swiper-wrapper';
254
255			uniq.forEach(({ src, alt }) => {
256				const slide = document.createElement('div');
257				slide.className = 'swiper-slide';
258				const imgEl = document.createElement('img');
259				imgEl.src = src;
260				imgEl.alt = alt;
261				slide.appendChild(imgEl);
262				wrapper.appendChild(slide);
263			});
264
265			const pagination = document.createElement('div');
266			pagination.className = 'scv3-card-swiper__pagination';
267
268			swiperEl.appendChild(wrapper);
269			swiperEl.appendChild(pagination);
270
271			thumb.appendChild(swiperEl);
272
273			const offset = mainImg && mainImg.src ? 1 : 0;
274			variantThumbImgs.forEach((img, idx) => {
275				img.dataset.scv3SlideIndex = offset + idx;
276			});
277		});
278	};
279
280	const initCategoryFallback = () => {
281		if (!categorySliderEl) return;
282		const track = categorySliderEl.querySelector('.swiper-wrapper');
283		if (!track) return;
284
285		categorySliderEl.classList.add('scv3-slider--fallback');
286		const getPxVar = (name, fallback) => {
287			const val = getComputedStyle(categorySliderEl).getPropertyValue(name);
288			const num = parseInt(val, 10);
289			return Number.isFinite(num) ? num : fallback;
290		};
291		const STEP =
292			getPxVar('--scv3-chip-width', 140) + getPxVar('--scv3-chip-gap', 14);
293
294		const updateButtons = () => {
295			const overflow = track.scrollWidth > categorySliderEl.clientWidth + 2;
296			if (categoryPrev && categoryNext) {
297				categoryPrev.style.display = overflow ? 'grid' : 'none';
298				categoryNext.style.display = overflow ? 'grid' : 'none';
299			}
300			if (!overflow || !categoryPrev || !categoryNext) return;
301			const maxScroll = track.scrollWidth - categorySliderEl.clientWidth - 2;
302			categoryPrev.disabled = categorySliderEl.scrollLeft <= 2;
303			categoryNext.disabled = categorySliderEl.scrollLeft >= maxScroll;
304		};
305
306		const scrollByStep = (dir) => {
307			categorySliderEl.scrollBy({ left: STEP * dir, behavior: 'smooth' });
308		};
309
310		if (categoryPrev && categoryNext) {
311			categoryPrev.addEventListener('click', () => scrollByStep(-1));
312			categoryNext.addEventListener('click', () => scrollByStep(1));
313		}
314
315		categorySliderEl.addEventListener('scroll', updateButtons);
316		window.addEventListener('resize', updateButtons);
317
318		categorySliderEl.addEventListener(
319			'wheel',
320			(e) => {
321				if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) {
322					categorySliderEl.scrollLeft += e.deltaY;
323					e.preventDefault();
324				}
325			},
326			{ passive: false }
327		);
328
329		updateButtons();
330	};
331
332	const initCategorySlider = () => {
333		if (!categorySliderEl || !categoryPrev || !categoryNext) return;
334		if (SwiperCtor) {
335			if (!window.Scv3Swiper) {
336				window.Scv3Swiper = SwiperCtor;
337			}
338			new SwiperCtor(categorySliderEl, {
339				slidesPerView: 'auto',
340				spaceBetween: 14,
341				watchOverflow: true,
342				navigation: {
343					prevEl: categoryPrev,
344					nextEl: categoryNext,
345				},
346			});
347		} else {
348			initCategoryFallback();
349		}
350	};
351
352	const initProductFallback = () => {
353		const swipers = document.querySelectorAll('[data-scv3-card-swiper]');
354		swipers.forEach((swiperEl) => {
355			if (swiperEl.dataset.scv3SwiperInit) return;
356			const slides = swiperEl.querySelectorAll('.swiper-slide');
357			if (slides.length <= 1) return;
358			swiperEl.classList.add('scv3-card-swiper_fallback');
359			const paginationEl =
360				swiperEl.querySelector('.scv3-card-swiper__pagination') ||
361				swiperEl.appendChild(document.createElement('div'));
362			paginationEl.classList.add('scv3-card-swiper__pagination');
363
364			if (!paginationEl.children.length) {
365				slides.forEach((_, idx) => {
366					const dot = document.createElement('button');
367					dot.type = 'button';
368					dot.className = 'scv3-card-swiper__bullet';
369					paginationEl.appendChild(dot);
370				});
371			}
372
373			const bullets = Array.from(paginationEl.querySelectorAll('.scv3-card-swiper__bullet'));
374			let active = 0;
375			const setActive = (idx) => {
376				active = (idx + slides.length) % slides.length;
377				slides.forEach((slide, i) => {
378					slide.classList.toggle('scv3-is-active', i === active);
379				});
380				bullets.forEach((b, i) =>
381					b.classList.toggle('scv3-card-swiper__bullet--active', i === active)
382				);
383			};
384
385			bullets.forEach((bullet, idx) => {
386				bullet.addEventListener('click', () => setActive(idx));
387				bullet.addEventListener('mouseover', () => setActive(idx));
388			});
389
390			const card = swiperEl.closest('.scv3-product-card');
391			if (card) {
392				const thumbImgs = card.querySelectorAll('.scv3-variant-thumb img');
393			}
394
395			setActive(0);
396			swiperEl.dataset.scv3SwiperInit = '1';
397		});
398	};
399
400	const initProductSwipers = () => {
401		if (!SwiperCtor) {
402			initProductFallback();
403			return;
404		}
405
406		const productSwipers = document.querySelectorAll('[data-scv3-card-swiper]');
407		productSwipers.forEach((swiperEl) => {
408			if (swiperEl.dataset.scv3SwiperInit) return;
409			const paginationEl = swiperEl.querySelector('.scv3-card-swiper__pagination');
410			const config = {
411				slidesPerView: 1,
412				loop: swiperEl.querySelectorAll('.swiper-slide').length > 1,
413				watchOverflow: true,
414			};
415			if (paginationEl) {
416				config.pagination = {
417					el: paginationEl,
418					clickable: true,
419				bulletClass: 'scv3-card-swiper__bullet',
420				bulletActiveClass: 'scv3-card-swiper__bullet--active',
421			};
422		}
423
424			const swiper = new SwiperCtor(swiperEl, config);
425			if (paginationEl) {
426				paginationEl.addEventListener('mouseover', (event) => {
427					const bullet = event.target.closest('.scv3-card-swiper__bullet');
428					if (!bullet) return;
429					const bullets = Array.from(paginationEl.querySelectorAll('.scv3-card-swiper__bullet'));
430					const idx = bullets.indexOf(bullet);
431					if (idx >= 0) {
432						swiper.slideToLoop ? swiper.slideToLoop(idx) : swiper.slideTo(idx);
433					}
434				});
435			}
436			swiperEl.dataset.scv3SwiperInit = '1';
437		});
438	};
439
440	window.scv3InitProductSliders = () => {
441		initProductSwipers();
442	};
443
444	enhanceProductThumbs();
445	initCategorySlider();
446	initProductSwipers();
447})();
448
449const Page = {
450	state: {
451		currentPage: 1,
452		basePath: "",
453		urlParams: ""
454	},
455
456	_selectors: {
457		hideInputs: {
458			count_products_in_html: "#countProductsInHTML"
459		},
460
461		header_p_count: ".header-page__count", //Селектор элемента с кол-во товаров на сайте todo: вынести в catalog-v3.php
462
463		cards: {
464			variantEl: '.scv3-variant-thumb',
465			productsBlock: '.scv3-product-grid' 
466		},
467
468		loadMoreBtn: ".scv3-load-more",
469		pagination: ".scv3-pagination",
470		termAdminInfo: "[data-scv3-term-admin-info]",
471
472		filters: {
473			categories: '[data-scv3-filter-slug="categories"] .scv3-filter-plain__item',
474			option: ".scv3-filter-option",
475			priceInput: ".scv3-price-input",
476			sortOption: ".scv3-sort-dropdown__option"
477		}
478	},
479	_state: {
480		currentPage: 1,
481		uiPage: 1,
482		basePath: '',
483		urlParams: {},
484		perPage: 24,
485		total: 0,
486		totalPages: 1,
487		filterDebounce: 600,
488		filterTimer: null,
489		categories: [],
490		activeCategorySlug: '',
491		categoryTreeExpanded: {},
492		productsById: {},
493		filterEdit: null,
494		landingContext: null
495	},
496
497
498
499	preload() {
500		let res = false;
501
502		//Берем кол-во товаров и тобображаем в шапке
503		res = this.mainBlocks.getCountProductsInHTML();
504
505		if(this.helpers.isNumeric(res))
506			this.helpers.setHeaderCount(res);
507
508		this._state = { ...this._state, ...this.helpers.getRouteState() };
509		this._state.landingContext = this.helpers.normalizeLandingContext(window.scv3LandingContext || null);
510		if(this.helpers.isNumeric(res)) {
511			this._state.total = Number(res);
512			this._state.totalPages = Math.max(1, Math.ceil(this._state.total / this._state.perPage));
513		}
514		this._state.uiPage = this._state.currentPage || 1;
515		this.helpers.ensurePagination();
516		this.helpers.updateSliderVisibility();
517
518		const initialProducts = (typeof products !== 'undefined') ? products : null;
519		const initialItems = Array.isArray(initialProducts?.items)
520			? initialProducts.items
521			: (Array.isArray(initialProducts) ? initialProducts : []);
522		if (initialItems.length) {
523			this.helpers.cacheProducts(initialItems, { reset: true });
524		}
525
526		this.helpers.syncFilterInputsFromUrl();
527		this.helpers.initCategoryTrees();
528		scv3InitFilterBlocks();
529		scv3InitFilterColors();
530
531		this.staticLisaners();
532		this.dynamicLisaners.cardVariants();
533		this.dynamicLisaners.pagination();
534		this.dynamicLisaners.categories();
535		this.dynamicLisaners.categoriesTree();
536		this.dynamicLisaners.options();
537		this.dynamicLisaners.price();
538		this.dynamicLisaners.sorting();
539	},
540
541	//Раз и навсегда после загрузки стр
542	staticLisaners() {
543		
544		$(Page._selectors.loadMoreBtn).off().on('click', async function(e){
545			e.preventDefault();
546
547			Page._state.currentPage += 1;
548			const nextPage = Page._state.currentPage;
549			Page.helpers.fetchAndRender({
550				page: nextPage,
551				append: true,
552				withFacets: false,
553				updateUrl: false,
554				updatePagination: false
555			});
556		});
557
558		$(document).off('click.scv3LandingLinks').on('click.scv3LandingLinks', '[data-scv3-landing-link]', function(e){
559			if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
560				return;
561			}
562			if (this.target && this.target.toLowerCase() !== '_self') {
563				return;
564			}
565
566			let targetUrl;
567			try {
568				targetUrl = new URL(this.href, location.origin);
569			} catch (err) {
570				return;
571			}
572			if (targetUrl.origin !== location.origin) {
573				return;
574			}
575
576			const context = Page.helpers.normalizeLandingContext(Page._state.landingContext);
577			const targetPath = targetUrl.pathname.replace(/^\/+|\/+$/g, '');
578			const landing = context?.pages?.find((page) => {
579				const pagePath = String(page?.url || '').replace(/^\/+|\/+$/g, '');
580				return `${context.basePath}/${pagePath}`.replace(/^\/+|\/+$/g, '') === targetPath;
581			});
582			if (!landing) {
583				return;
584			}
585
586			e.preventDefault();
587			if (Page._state.filterTimer) {
588				clearTimeout(Page._state.filterTimer);
589				Page._state.filterTimer = null;
590			}
591
592			const params = {};
593			targetUrl.searchParams.forEach((value, rawKey) => {
594				const key = Page.helpers.normalizeFilterParamName(rawKey);
595				if (params[key] === undefined) {
596					params[key] = value;
597				} else if (Array.isArray(params[key])) {
598					params[key].push(value);
599				} else {
600					params[key] = [params[key], value];
601				}
602			});
603			Object.entries(landing.attributes || {}
603).forEach(([slug, rawValues]) => {
604				const values = (Array.isArray(rawValues) ? rawValues : [rawValues])
605					.map((value) => String(value))
606					.filter(Boolean);
607				if (values.length) {
608					params[`pa-${slug}`] = values.length === 1 ? values[0] : values;
609				}
610			});
611
612			Page._state.basePath = context.basePath;
613			Page._state.urlParams = Page.helpers.normalizeUrlParams(params);
614			Page._state.currentPage = 1;
615			Page._state.uiPage = 1;
616			Page.helpers.fetchAndRender({
617				page: 1,
618				append: false,
619				withFacets: true,
620				history: 'push'
621			});
622			Page.helpers.closeMobileFilters();
623		});
624	},
625	
626	//После ререндера перевешиваем
627	dynamicLisaners: {
628		categories(){
629			$(document).off('click.scv3Categories').on('click.scv3Categories', Page._selectors.filters.categories, async function(e){
630				e.preventDefault();
631
632				const href = $(this).attr('href') || '';
633				if (!href || href === '#') return;
634
635				// href вида /cat/muzhskoe/jersey/ — меняем basePath и сбрасываем страницу на 1
636				let path = '';
637				try {
638					const parsed = new URL(href, location.origin);
639					path = parsed.pathname || '';
640				} catch (err) {
641					path = (href.split('?')[0] || '');
642				}
643				path = path.replace(/^\/+|\/+$/g, '');
644
645				// /cat/ без слага — это главная, приводим к /shop/
646				if (!path || /^cat\/?$/.test(path)) {
647					path = 'shop';
648				}
649
650				Page._state.basePath = path;
651				Page._state.landingContext = null;
652				Page._state.currentPage = 1;
653				Page._state.urlParams = {}; // смена категории — сбрасываем все активные фильтры
654				Page.helpers.updateUrl(1, 'push');
655				Page.helpers.updateSliderVisibility();
656
657				Page.mainBlocks.runFilters();
658				Page.helpers.closeMobileFilters();
659
660			});
661		},
662		categoriesTree() {
663			if (Page._state.categoryTreeToggleBound) {
664				return;
665			}
666			Page._state.categoryTreeToggleBound = true;
667			document.addEventListener('click', (e) => {
668				const btn = e.target && e.target.closest ? e.target.closest('[data-scv3-cat-toggle]') : null;
669				if (!btn) return;
670				e.preventDefault();
671				e.stopPropagation();
672				const row = btn.closest('[data-scv3-cat-row]');
673				if (!row) return;
674				Page.helpers.toggleCategoryTreeRow(row);
675			}, true);
676		},
677
678		options(){
679			const selector = `${Page._selectors.filters.option} input[type="checkbox"]`;
680			$(document).off('change.scv3Filters').on('change.scv3Filters', selector, function(){
681				const isEdit = Page.helpers.isFilterEditMode && Page.helpers.isFilterEditMode();
682				const $input = $(this);
683				const $label = $input.closest(Page._selectors.filters.option);
684				if ($label.length) {
685					$label.toggleClass('scv3-is-checked', $input.prop('checked'));
686				}
687				const name = Page.helpers.normalizeFilterParamName($input.attr('name') || '');
688				const value = $input.val();
689				if (!name || value === undefined) return;
690				const isCategoryTreeInput = $input.is('[data-scv3-cat-checkbox]');
691				if (isCategoryTreeInput) {
692					Page.helpers.applyCategoryTreeCascade($input.get(0));
693				}
694
695				const paramsSource = Page.helpers.normalizeUrlParams(
696					Page.helpers.getFilterEditParams ? Page.helpers.getFilterEditParams() : Page._state.urlParams
697				);
698				const usp = new URLSearchParams(Page.helpers.serializeUrlParams(paramsSource));
699
700				if (isCategoryTreeInput) {
701					const selected = [];
702					const root = $input.closest('[data-scv3-filter-slug="categories"]');
703					root.find('input[type="checkbox"]').filter(function () {
704						return Page.helpers.normalizeFilterParamName(this.getAttribute('name') || '') === name && this.checked;
705					}).each(function () {
706						const v = String($(this).val() ?? '').trim();
707						if (!v) return;
708						selected.push(v);
709					});
710					usp.delete(name);
711					selected.forEach((v) => usp.append(name, v));
712				} else {
713					const currentVals = usp.getAll(name).filter((v) => v !== '');
714					const isChecked = $input.prop('checked');
715					if (isChecked) {
716						if (!currentVals.includes(String(value))) {
717							usp.append(name, value);
718						}
719					} else {
720						const remaining = currentVals.filter((v) => v !== String(value));
721						usp.delete(name);
722						remaining.forEach((v) => usp.append(name, v));
723					}
724				}
725
726				const newParams = {};
727				usp.forEach((v, k) => {
728					if (newParams[k]) {
729						if (Array.isArray(newParams[k])) {
730							newParams[k].push(v);
731						} else {
732							newParams[k] = [newParams[k], v];
733						}
734					} else {
735						newParams[k] = v;
736					}
737				});
738				if (isEdit) {
739					Page.helpers.setFilterEditParams(newParams);
740					return;
741				}
742				Page._state.urlParams = newParams;
743				Page._state.currentPage = 1;
744				Page.helpers.updateUrl(1);
745
746				if (Page._state.filterTimer) {
747					clearTimeout(Page._state.filterTimer);
748				}
749				Page._state.filterTimer = setTimeout(() => {
750					Page._state.filterTimer = null;
751					Page.mainBlocks.runFilters();
752				}, Page._state.filterDebounce || 600);
753
754				if (typeof window.scv3UpdateMobileFilterPills === 'function') {
755					window.scv3UpdateMobileFilterPills();
756				}
757			});
758
759			$(selector).each(function(){
760				const $input = $(this);
761				const $label = $input.closest(Page._selectors.filters.option);
762				if ($label.length) {
763					$label.toggleClass('scv3-is-checked', $input.prop('checked'));
764				}
765			});
766		},
767
768		price() {
769			const selector = Page._selectors.filters.priceInput;
770			const normalizeName = (name) => {
771				const raw = String(name || '');
772				return raw.endsWith('[]') ? raw.slice(0, -2) : raw;
773			};
774			const cleanVal = (val) => {
775				const str = String(val || '').replace(/[^0-9]/g, '');
776				return str === '' ? '' : String(parseInt(str, 10));
777			};
778			const formatVal = (val) => {
779				const digits = cleanVal(val);
780				if (!digits) return '';
781				return Page.helpers.formatPriceDisplay(digits) || digits;
782			};
783			const setCaretToEnd = (el) => {
784				if (!el || typeof el.setSelectionRange !== 'function') return;
785				const len = el.value.length;
786				try {
787					el.setSelectionRange(len, len);
788				} catch (err) {
789					// ignore selection errors for unsupported input types
790				}
791			};
792			const schedule = (immediate = false) => {
793				if (Page._state.filterTimer) {
794					clearTimeout(Page._state.filterTimer);
795					Page._state.filterTimer = null;
796				}
797				if (immediate) {
798					Page.mainBlocks.runFilters();
799					return;
800				}
801				Page._state.filterTimer = setTimeout(() => {
802					Page._state.filterTimer = null;
803					Page.mainBlocks.runFilters();
804				}, Page._state.filterDebounce || 600);
805			};
806			const isInSheet = ($input) => {
807				const node = $input && $input.length ? $input.get(0) : null;
808				if (!node) return false;
809				return !!node.closest('[data-scv3-filter-sheet], [data-scv3-filter-all-sheet]');
810			};
811			const applyFromBlock = ($input, immediate = false) => {
812				const $list = $input.closest('.scv3-filter-list--price');
813				const inputs = $list.length ? $list.find(selector) : $(selector);
814				const isEdit = Page.helpers.isFilterEditMode && Page.helpers.isFilterEditMode();
815				const paramsSource = Page.helpers.getFilterEditParams ? Page.helpers.getFilterEditParams() : Page._state.urlParams;
816				const params = { ...paramsSource };
817
818				inputs.each(function(){
819					const $el = $(this);
820					const rawName = $el.attr('name') || '';
821					const name = normalizeName(rawName);
822					if (!name) return;
823					if (rawName && rawName !== name) {
824						delete params[rawName];
825					}
826					const cleaned = cleanVal($el.val());
827					if (cleaned !== '') {
828						params[name] = cleaned;
829						$el.val(formatVal(cleaned));
830					} else {
831						delete params[name];
832						$el.val('');
833					}
834					if ($input.length && $el.get(0) === $input.get(0)) {
835						setCaretToEnd($el.get(0));
836					}
837				});
838
839				if (isEdit) {
840					Page.helpers.setFilterEditParams(params);
841					return;
842				}
843				Page._state.urlParams = params;
844				Page._state.currentPage = 1;
845				Page.helpers.updateUrl(1);
846				if (typeof window.scv3UpdateMobileFilterPills === 'function') {
847					window.scv3UpdateMobileFilterPills();
848				}
849				schedule(immediate);
850				if (!isInSheet($input)) {
851					Page.helpers.closeMobileFilters();
852				}
853			};
854
855			$(document)
856				.off('input.scv3Price', selector)
857				.on('input.scv3Price', selector, function(){
858					const $input = $(this);
859					if (isInSheet($input)) return;
860					applyFromBlock($input, false);
861				});
862
863			$(document)
864				.off('change.scv3Price', selector)
865				.on('change.scv3Price', selector, function(){
866					applyFromBlock($(this), false);
867				});
868
869			$(document)
870				.off('keydown.scv3Price', selector)
871				.on('keydown.scv3Price', selector, function(e){
872					if (e.key === 'Enter') {
873						e.preventDefault();
874						applyFromBlock($(this), true);
875					}
876				});
877		},
878
879		sorting() {
880			const selector = Page._selectors.filters.sortOption;
881			const dropdownSelector = '.scv3-sort-dropdown';
882			const normalizeSort = (val) => {
883				if (!val) return '';
884				const v = String(val).trim();
885				if (v === 'popular') return 'popularity';
886				return v;
887			};
888			const setActive = (key, label, $option) => {
889				const $dropdown = $option.closest('.scv3-sort-dropdown');
890				if ($dropdown.length) {
891					$dropdown.find(selector).removeClass('scv3-is-active');
892					$option.addClass('scv3-is-active');
893					const summary = $dropdown.find('summary').get(0);
894					if (summary) {
895						const labelEl = summary.querySelector('.scv3-sort-dropdown__label');
896						if (labelEl) {
897							labelEl.textContent = label || key || '';
898						} else {
899							summary.textContent = label || key || '';
900						}
901					}
902					const detailsEl = $dropdown.get(0);
903					if (detailsEl) {
904						detailsEl.removeAttribute('open');
905					}
906				}
907			};
908
909			$(document).off('click.scv3Sort', selector).on('click.scv3Sort', selector, function(e){
910				e.preventDefault();
911				const $opt = $(this);
912				const rawSort = $opt.data('sort') || $opt.attr('data-sort-key') || $opt.attr('data-sort') || '';
913				const label = $opt.text().trim();
914				const sort = normalizeSort(rawSort || label);
915				if (!sort) return;
916
917				const params = { ...Page._state.urlParams };
918				params.sort = sort;
919				Page._state.urlParams = params;
920				Page._state.currentPage = 1;
921				Page.helpers.updateUrl(1);
922				setActive(sort, label, $opt);
923				Page.helpers.fetchAndRender({
924					page: 1,
925					append: false,
926					withFacets: false
927				});
928				Page.helpers.closeMobileFilters();
929			});
930
931			$(document).off('click.scv3SortOutside').on('click.scv3SortOutside', function(e){
932				const $target = $(e.target);
933				if ($target.closest(dropdownSelector).length) return;
934				$(dropdownSelector).removeAttr('open');
935			});
936
937			const currentSort = normalizeSort(Page._state.urlParams.sort || '');
938			const $opts = $(selector);
939			if (currentSort && $opts.length) {
940				const $activeOpt = $opts.filter(`[data-sort="${currentSort}"], [data-sort-key="${currentSort}"]`).first();
941				if ($activeOpt.length) {
942					setActive(currentSort, $activeOpt.text().trim(), $activeOpt);
943				}
944			}
945		},
946
947		cardVariants(){
948			const selector = Page._selectors.cards.variantEl;
949			const $variants = $(selector);
950
951				$variants.off().on('click', function(){
952					const variantId = Number($(this).data('variantId')) || 0;
953					if (!variantId) return;
954
955					const $card = $(this).closest('article');
956					const prevVariantsTrack = $card.length
957						? $card.find('.scv3-product-card__variants').get(0)
958						: null;
959					const prevVariantsScrollLeft = prevVariantsTrack ? prevVariantsTrack.scrollLeft : 0;
960					const prevThumbData = $card.length
961						? $card.find(Page._selectors.cards.variantEl).map((_, el) => {
962							const $el = $(el);
963							const id = Number($el.data('variantId')) || 0;
964							const $img = $el.find('img').first();
965							return {
966								id,
967								src: ($img.attr('src') || '').trim(),
968								alt: ($img.attr('alt') || '').trim()
969							};
970						}).get().filter((item) => item.id > 0 && item.src !== '')
971						: [];
972					const domOrder = $card.length
973						? $card.find(Page._selectors.cards.variantEl)
974							.map((_, el) => Number($(el).data('variantId')) || 0)
975							.get()
976							.filter((id) => id > 0)
977					: [];
978
979				const cached = Page.helpers.getCachedProduct(variantId);
980				if (!cached) return;
981
982				const cachedOrder = Array.isArray(cached.variant_order) ? cached.variant_order : [];
983				const variantOrder = domOrder.length ? domOrder : cachedOrder;
984				const renderData = variantOrder.length ? { ...cached, variant_order: variantOrder } : cached;
985					const cardHtml = Render.Cards.card(renderData);
986
987						if (cardHtml) {
988							const $newCard = $(cardHtml);
989							const newTrackBeforeInsert = $newCard.find('.scv3-product-card__variants').get(0);
990							if (newTrackBeforeInsert) {
991								newTrackBeforeInsert.style.scrollBehavior = 'auto';
992								newTrackBeforeInsert.scrollLeft = prevVariantsScrollLeft;
993								newTrackBeforeInsert.dataset.scv3RestoreScrollLeft = String(prevVariantsScrollLeft || 0);
994							}
995							$card.replaceWith($newCard);
996							const $variantsWrap = $newCard.find('.scv3-product-card__variants').first();
997							if ($variantsWrap.length && domOrder.length) {
998								const existingIds = new Set(
999									$variantsWrap.find(Page._selectors.cards.variantEl)
1000										.map((_, el) => Number(el.getAttribute('data-variant-id') || 0))
1001										.get()
1002										.filter((id) => id > 0)
1003								);
1004								prevThumbData.forEach((item) => {
1005									if (existingIds.has(item.id)) return;
1006									const node = document.createElement('div');
1007									node.className = 'scv3-variant-thumb';
1008									node.setAttribute('data-variant-id', String(item.id));
1009									const img = document.createElement('img');
1010									img.src = item.src;
1011									img.alt = item.alt;
1012									node.appendChild(img);
1013									$variantsWrap.append(node);
1014									existingIds.add(item.id);
1015								});
1016
1017								const nodes = $variantsWrap.find(Page._selectors.cards.variantEl).get();
1018								const byId = new Map();
1019								nodes.forEach((node) => {
1020									const id = Number(node.getAttribute('data-variant-id') || 0);
1021									if (id > 0 && !byId.has(id)) {
1022										byId.set(id, node);
1023									}
1024								});
1025
1026								const orderedNodes = [];
1027								domOrder.forEach((id) => {
1028									const node = byId.get(id);
1029									if (node) {
1030										orderedNodes.push(node);
1031										byId.delete(id);
1032									}
1033								});
1034								byId.forEach((node) => orderedNodes.push(node));
1035								if (orderedNodes.length) {
1036									orderedNodes.forEach((node) => {
1037										$variantsWrap.append(node);
1038									});
1039								}
1040							}
1041
1042							const $thumbs = $newCard.find(Page._selectors.cards.variantEl);
1043							if ($thumbs.length) {
1044							const $active = $thumbs.filter(`[data-variant-id="${variantId}"]`);
1045							if ($active.length) {
1046								$thumbs.removeClass('scv3-variant-thumb--active');
1047								$active.addClass('scv3-variant-thumb--active');
1048						}
1049					}
1050							Page.dynamicLisaners.cardVariants();
1051							if (typeof window.scv3InitProductSliders === 'function') {
1052								window.scv3InitProductSliders();
1053							}
1054							const trackAfterInit = $newCard.find('.scv3-product-card__variants').get(0);
1055							if (trackAfterInit) {
1056								const restoreValue = Number(trackAfterInit.dataset.scv3RestoreScrollLeft || prevVariantsScrollLeft || 0);
1057								trackAfterInit.scrollLeft = restoreValue;
1058								delete trackAfterInit.dataset.scv3RestoreScrollLeft;
1059								setTimeout(() => {
1060									trackAfterInit.style.scrollBehavior = '';
1061								}, 0);
1062							}
1063						}
1064					});
1065
1066			$variants.on('mouseenter', function(){
1067				const $variant = $(this);
1068				const src = $variant.find('img').attr('src') || '';
1069				if (!src) return;
1070				const $card = $variant.closest('article');
1071				if (!$card.length) return;
1072				const imgEl = Page.helpers.getCardMainImageEl($card);
1073				if (!imgEl) return;
1074
1075				const $img = $(imgEl);
1076				if ($img.data('scv3OrigSrc') === undefined) {
1077					$img.data('scv3OrigSrc', imgEl.getAttribute('src') || '');
1078					if (imgEl.hasAttribute('srcset')) {
1079						$img.data('scv3OrigSrcset', imgEl.getAttribute('srcset') || '');
1080					}
1081				}
1082
1083				imgEl.setAttribute('src', src);
1084				if (imgEl.hasAttribute('srcset')) {
1085					imgEl.setAttribute('srcset', src);
1086				}
1087
1088				$variant.data('scv3HoverImg', imgEl);
1089			});
1090
1091			$variants.on('mouseleave', function(){
1092				const $variant = $(this);
1093				const imgEl = $variant.data('scv3HoverImg');
1094				if (!imgEl) return;
1095				const $img = $(imgEl);
1096				const orig = $img.data('scv3OrigSrc');
1097				if (orig !== undefined) {
1098					imgEl.setAttribute('src', orig);
1099				}
1100				const origSet = $img.data('scv3OrigSrcset');
1101				if (origSet !== undefined) {
1102					if (origSet) {
1103						imgEl.setAttribute('srcset', origSet);
1104					} else {
1105						imgEl.removeAttribute('srcset');
1106					}
1107				}
1108				$variant.removeData('scv3HoverImg');
1109			});
1110		},
1111
1112		pagination() {
1113			$(Page._selectors.pagination).off().on('click', 'a', async function(e){
1114				e.preventDefault();
1115				const href = $(this).attr('href') || '';
1116				if (!href || href === '#') return;
1117
1118				// Парсим номер страницы из href (ищем /page/N/ или query page).
1119				let page = 1;
1120				const match = href.match(/\/page\/(\d+)/);
1121				if (match && match[1]) {
1122					page = parseInt(match[1], 10);
1123				} else {
1124					const usp = new URLSearchParams(href.split('?')[1] || '');
1125					if (usp.has('page')) {
1126						page = parseInt(usp.get('page'), 10);
1127					}
1128				}
1129				if (!Number.isFinite(page) || page < 1) page = 1;
1130
1131				Page.helpers.fetchAndRender({
1132					page: page,
1133					append: false,
1134					withFacets: false,
1135					history: 'push'
1136				});
1137			});
1138		}
1139	},
1140
1141	mainBlocks: {
1142
1143		runFilters() {
1144			Page._state.currentPage = 1;
1145			Page.helpers.fetchAndRender({
1146				page: 1,
1147				append: false,
1148				withFacets: true
1149			});
1150		},
1151
1152		getCountProductsInHTML() {
1153			let el = $(Page._selectors.hideInputs.count_products_in_html);
1154
1155			if(el.length === 0)
1156				return false;
1157
1158			return el.val();
1159		}
1160	},
1161
1162	helpers: {
1163		getProductId(item = {}) {
1164			const dataId = Number(item?.data?.product_id || item?.data?.id || 0);
1165			const directId = Number(item?.product_id || item?.post_id || item?.id || 0);
1166			return dataId || directId || 0;
1167		},
1168
1169		getCardMainImageEl(cardEl) {
1170			const el = cardEl && cardEl.jquery ? cardEl.get(0) : cardEl;
1171			if (!el) return null;
1172			const thumb = el.querySelector('.scv3-product-card__thumb');
1173			if (!thumb) return null;
1174			return (
1175				thumb.querySelector('.swiper-slide-active img') ||
1176				thumb.querySelector('.swiper-slide img') ||
1177				thumb.querySelector('img')
1178			);
1179		},
1180
1181		normalizeCachedItem(raw = {}, colorsGroup = []) {
1182			const item = { ...raw };
1183			const id = Page.helpers.getProductId(item);
1184			const srcData = (item.data && typeof item.data === 'object') ? item.data : {};
1185			const data = { ...srcData };
1186			if (id) {
1187				data.product_id = id;
1188			}
1189			if (!data.title) data.title = item.title || '';
1190			if (!data.name) data.name = item.name || '';
1191			if (!data.thumbnail_img) data.thumbnail_img = item.thumbnail_img || '';
1192			if (!data.product_url) data.product_url = item.product_url || item.url || '';
1193			if (!data.img_sizes) data.img_sizes = item.img_sizes || '';
1194			if (!data.thumbnail_img) {
1195				const gallery = Render?.Helpers?.normalizeGallery
1196					? Render.Helpers.normalizeGallery(data.gallery_imgs || data.galleryImgs)
1197					: [];
1198				if (gallery.length) data.thumbnail_img = gallery[0];
1199			}
1200			item.data = data;
1201			if (!item.product_id && id) {
1202				item.product_id = id;
1203			}
1204			if (!Array.isArray(item.colors)) {
1205				item.colors = Array.isArray(colorsGroup) ? colorsGroup : [];
1206			}
1207			if (!item.post_id && id) {
1208				item.post_id = id;
1209			}
1210			return item;
1211		},
1212
1213		buildSelfColor(item = {}) {
1214			const id = Page.helpers.getProductId(item);
1215			if (!id) return null;
1216			const data = (item.data && typeof item.data === 'object') ? item.data : {};
1217			let thumbnail = data.thumbnail_img || item.thumbnail_img || '';
1218			if (!thumbnail && Render?.Helpers?.normalizeGallery) {
1219				const gallery = Render.Helpers.normalizeGallery(data.gallery_imgs || data.galleryImgs);
1220				if (gallery.length) thumbnail = gallery[0];
1221			}
1222
1223			return {
1224				product_id: id,
1225				title: data.title || item.title || '',
1226				name: data.name || item.name || '',
1227				product_url: data.product_url || item.product_url || item.url || '',
1228				thumbnail_img: thumbnail,
1229				img_sizes: data.img_sizes || item.img_sizes || '',
1230				data: data,
1231				min_regular_price: item.min_regular_price ?? 0,
1232				max_regular_price: item.max_regular_price ?? 0,
1233				min_sale_price: item.min_sale_price ?? 0,
1234				max_sale_price: item.max_sale_price ?? 0,
1235				min_price_effective: item.min_price_effective ?? 0,
1236				stock_status: item.stock_status ?? 0
1237			};
1238		},
1239
1240		mergeCachedProduct(existing = null, incoming = null) {
1241			if (!existing) return incoming;
1242			if (!incoming) return existing;
1243			const merged = { ...existing, ...incoming };
1244			if (existing.data || incoming.data) {
1245				merged.data = { ...(existing.data || {}), ...(incoming.data || {}) };
1246			}
1247			if (Array.isArray(incoming.colors) && incoming.colors.length) {
1248				merged.colors = incoming.colors;
1249			}
1250			return merged;
1251		},
1252
1253		cacheProducts(items = [], { reset = false } = {}) {
1254			if (reset) {
1255				Page._state.productsById = {};
1256			}
1257			const cache = Page._state.productsById;
1258			if (!Array.isArray(items)) return;
1259
1260			items.forEach((item) => {
1261				if (!item || typeof item !== 'object') return;
1262				const colorsRaw = Array.isArray(item.colors) ? item.colors : [];
1263				const colorsGroup = colorsRaw.slice();
1264				const selfColor = Page.helpers.buildSelfColor(item);
1265				if (selfColor) {
1266					const selfId = Page.helpers.getProductId(selfColor);
1267					const hasSelf = colorsGroup.some((color) => Page.helpers.getProductId(color) === selfId);
1268					if (!hasSelf) {
1269						colorsGroup.push(selfColor);
1270					}
1271				}
1272				const itemId = Page.helpers.getProductId(item);
1273				if (itemId) {
1274					const normalized = Page.helpers.normalizeCachedItem(item, colorsGroup);
1275					cache[itemId] = Page.helpers.mergeCachedProduct(cache[itemId], normalized);
1276				}
1277				colorsGroup.forEach((color) => {
1278					if (!color || typeof color !== 'object') return;
1279					const colorId = Page.helpers.getProductId(color);
1280					if (!colorId) return;
1281					const colorItem = Page.helpers.normalizeCachedItem(color, colorsGroup);
1282					cache[colorId] = Page.helpers.mergeCachedProduct(cache[colorId], colorItem);
1283				});
1284			});
1285		},
1286
1287		getCachedProduct(id) {
1288			const key = Number(id) || 0;
1289			return key ? (Page._state.productsById[key] || null) : null;
1290		},
1291
1292		formatPriceDisplay(value) {
1293			const raw = String(value ?? '').trim();
1294			if (!raw) return '';
1295			const digits = raw.replace(/[^0-9]/g, '');
1296			if (!digits) return '';
1297			const num = Number(digits);
1298			if (!Number.isFinite(num)) return '';
1299			return num.toLocaleString('ru-RU');
1300		},
1301
1302		normalizePriceRange(priceRange = null) {
1303			const extract = (src) => {
1304				if (!src || typeof src !== 'object') return {};
1305				if (src.value && typeof src.value === 'object') return extract(src.value);
1306				return src;
1307			};
1308			const value = extract(priceRange);
1309			const selected = extract(priceRange && priceRange.selected ? priceRange.selected : null);
1310			return {
1311				min: value.min ?? value.from ?? null,
1312				max: value.max ?? value.to ?? null,
1313				selected_from: selected.from ?? selected.min ?? null,
1314				selected_to: selected.to ?? selected.max ?? null
1315			};
1316		},
1317		syncFilterInputsFromUrl() {
1318			const filters = document.querySelector('.scv3-filters');
1319			if (!filters) return;
1320
1321			const params = Page._state.urlParams || {};
1322			const selectedMap = {};
1323			Object.entries(params).forEach(([key, val]) => {
1324				if (Array.isArray(val)) {
1325					selectedMap[key] = val.map((item) => String(item));
1326				} else if (val !== null && val !== undefined) {
1327					selectedMap[key] = [String(val)];
1328				}
1329			});
1330
1331			const getValues = (name) => {
1332				if (!name) return [];
1333				if (selectedMap[name]) return selectedMap[name];
1334				if (name.endsWith('[]')) {
1335					return selectedMap[name.slice(0, -2)] || [];
1336				}
1337				return selectedMap[`${name}[]`] || [];
1338			};
1339
1340			const checkboxes = filters.querySelectorAll('.scv3-filter-option input[type="checkbox"]');
1341			checkboxes.forEach((input) => {
1342				const name = input.getAttribute('name') || '';
1343				const values = getValues(name);
1344				const val = input.value !== undefined ? String(input.value) : '';
1345				const shouldCheck = val !== '' && values.includes(val);
1346				input.checked = shouldCheck;
1347				const label = input.closest('.scv3-filter-option');
1348				if (label) {
1349					label.classList.toggle('scv3-is-checked', shouldCheck);
1350				}
1351			});
1352
1353			const priceInputs = filters.querySelectorAll('.scv3-filter-list--price input');
1354			priceInputs.forEach((input) => {
1355				const name = input.getAttribute('name') || '';
1356				const values = getValues(name);
1357				const raw = values[0] !== undefined ? String(values[0]) : '';
1358				if (!raw) {
1359					input.value = '';
1360					return;
1361				}
1362				const formatted = Page.helpers.formatPriceDisplay(raw);
1363				input.value = formatted || raw;
1364			});
1365			Page.helpers.initCategoryTrees(filters);
1366		},
1367		initCategoryTrees(root = document) {
1368			if (!root || !root.querySelectorAll) return;
1369			const trees = root.querySelectorAll('[data-scv3-category-tree]');
1370			trees.forEach((tree) => {
1371				Page.helpers.syncCategoryTreeExpandedState(tree);
1372				Page.helpers.updateCategoryTreeVisibility(tree);
1373				Page.helpers.updateCategoryTreeParents(tree);
1374			});
1375		},
1376		syncCategoryTreeExpandedState(tree) {
1377			if (!tree) return;
1378			const rows = Array.from(tree.querySelectorAll('[data-scv3-cat-row]'));
1379			rows.forEach((row) => {
1380				const termId = Number(row.getAttribute('data-scv3-cat-term-id') || 0);
1381				if (!termId) return;
1382				if (Object.prototype.hasOwnProperty.call(Page._state.categoryTreeExpanded, termId)) {
1383					const expanded = !!Page._state.categoryTreeExpanded[termId];
1384					row.setAttribute('data-scv3-cat-expanded', expanded ? '1' : '0');
1385				} else {
1386					Page._state.categoryTreeExpanded[termId] = row.getAttribute('data-scv3-cat-expanded') === '1';
1387				}
1388			});
1389		},
1390		toggleCategoryTreeRow(row) {
1391			if (!row) return;
1392			const termId = Number(row.getAttribute('data-scv3-cat-term-id') || 0);
1393			const current = termId > 0
1394				? !!Page._state.categoryTreeExpanded[termId]
1395				: (row.getAttribute('data-scv3-cat-expanded') === '1');
1396			const next = !current;
1397			row.setAttribute('data-scv3-cat-expanded', next ? '1' : '0');
1398			if (termId > 0) {
1399				Page._state.categoryTreeExpanded[termId] = next;
1400			}
1401			const tree = row.closest('[data-scv3-category-tree]') || row.parentElement;
1402			Page.helpers.updateCategoryTreeVisibility(tree);
1403			if (typeof window.requestAnimationFrame === 'function') {
1404				window.requestAnimationFrame(() => {
1405					Page.helpers.updateCategoryTreeVisibility(tree);
1406				});
1407			}
1408		},
1409		updateCategoryTreeVisibility(tree) {
1410			if (!tree) return;
1411			Page.helpers.syncCategoryTreeExpandedState(tree);
1412			const rows = Array.from(tree.querySelectorAll('[data-scv3-cat-row]'));
1413			if (!rows.length) return;
1414			const byTerm = new Map();
1415			rows.forEach((row) => {
1416				const termId = Number(row.getAttribute('data-scv3-cat-term-id') || 0);
1417				if (termId) byTerm.set(termId, row);
1418			});
1419
1420			rows.forEach((row) => {
1421				const termId = Number(row.getAttribute('data-scv3-cat-term-id') || 0);
1422				const hasChildren = row.hasAttribute('data-scv3-cat-has-children');
1423				const expanded = termId > 0
1424					? !!Page._state.categoryTreeExpanded[termId]
1425					: (row.getAttribute('data-scv3-cat-expanded') === '1');
1426				row.setAttribute('data-scv3-cat-expanded', expanded ? '1' : '0');
1427				const toggle = row.querySelector('[data-scv3-cat-toggle]');
1428				if (toggle && hasChildren) {
1429					toggle.textContent = expanded ? '-' : '+';
1430					toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
1431				}
1432
1433				let visible = true;
1434				let parentId = Number(row.getAttribute('data-scv3-cat-parent-term-id') || 0);
1435				let guard = 0;
1436				while (parentId > 0 && guard < 30) {
1437					guard += 1;
1438					const parentRow = byTerm.get(parentId);
1439					if (!parentRow) break;
1440					const parentTermId = Number(parentRow.getAttribute('data-scv3-cat-term-id') || 0);
1441					const parentExpanded = parentTermId > 0
1442						? !!Page._state.categoryTreeExpanded[parentTermId]
1443						: (parentRow.getAttribute('data-scv3-cat-expanded') === '1');
1444					if (!parentExpanded) {
1445						visible = false;
1446						break;
1447					}
1448					parentId = Number(parentRow.getAttribute('data-scv3-cat-parent-term-id') || 0);
1449				}
1450				row.style.display = visible ? '' : 'none';
1451			});
1452		},
1453		_updateCategoryTreeParentState(row, tree) {
1454			if (!row || !tree) return;
1455			const descendantsRaw = String(row.getAttribute('data-scv3-cat-descendants') || '').trim();
1456			if (!descendantsRaw) {
1457				const input = row.querySelector('input[type="checkbox"][data-scv3-cat-checkbox]');
1458				if (input) {
1459					input.indeterminate = false;
1460				}
1461				return;
1462			}
1463			const ids = descendantsRaw.split(',').map((id) => Number(String(id).trim() || 0)).filter((id) => id > 0);
1464			if (!ids.length) return;
1465			const input = row.querySelector('input[type="checkbox"][data-scv3-cat-checkbox]');
1466			if (!input) return;
1467
1468			let checkedCount = 0;
1469			ids.forEach((id) => {
1470				const childInput = tree.querySelector(`input[type="checkbox"][data-scv3-cat-checkbox][value="${id}"]`);
1471				if (childInput && childInput.checked) checkedCount += 1;
1472			});
1473			if (checkedCount === 0) {
1474				input.indeterminate = false;
1475				return;
1476			}
1477			if (checkedCount === ids.length) {
1478				input.indeterminate = false;
1479				return;
1480			}
1481			input.indeterminate = true;
1482		},
1483		updateCategoryTreeParents(tree) {
1484			if (!tree) return;
1485			const rows = Array.from(tree.querySelectorAll('[data-scv3-cat-row]'));
1486			for (let i = rows.length - 1; i >= 0; i -= 1) {
1487				Page.helpers._updateCategoryTreeParentState(rows[i], tree);
1488			}
1489		},
1490		applyCategoryTreeCascade(inputEl) {
1491			if (!inputEl) return;
1492			const row = inputEl.closest('[data-scv3-cat-row]');
1493			const tree = inputEl.closest('[data-scv3-category-tree]');
1494			if (!row || !tree) return;
1495
1496			const descendantsRaw = String(row.getAttribute('data-scv3-cat-descendants') || '').trim();
1497			if (descendantsRaw) {
1498				const ids = descendantsRaw.split(',').map((id) => Number(String(id).trim() || 0)).filter((id) => id > 0);
1499				const checked = !!inputEl.checked;
1500				ids.forEach((id) => {
1501					const target = tree.querySelector(`input[type="checkbox"][data-scv3-cat-checkbox][value="${id}"]`);
1502					if (!target) return;
1503					target.checked = checked;
1504					target.indeterminate = false;
1505					const targetLabel = target.closest('.scv3-filter-option');
1506					if (targetLabel) {
1507						targetLabel.classList.toggle('scv3-is-checked', checked);
1508					}
1509				});
1510			}
1511
1512			Page.helpers.updateCategoryTreeParents(tree);
1513		},
1514		cloneParams(params = {}) {
1515			return JSON.parse(JSON.stringify(params || {}));
1516		},
1517		areParamsEqual(a = {}, b = {}) {
1518			const normalize = (params) => {
1519				const normalized = {};
1520				Object.entries(params || {}).forEach(([key, val]) => {
1521					if (Array.isArray(val)) {
1522						const items = val.map((item) => String(item)).filter((item) => item !== '').sort();
1523						if (items.length) {
1524							normalized[key] = items;
1525						}
1526						return;
1527					}
1528					if (val !== null && val !== undefined && val !== '') {
1529						normalized[key] = [String(val)];
1530					}
1531				});
1532				return normalized;
1533			};
1534
1535			const left = normalize(a);
1536			const right = normalize(b);
1537			const keysA = Object.keys(left).sort();
1538			const keysB = Object.keys(right).sort();
1539			if (keysA.length !== keysB.length) return false;
1540			for (let i = 0; i < keysA.length; i += 1) {
1541				const key = keysA[i];
1542				if (key !== keysB[i]) return false;
1543				const valsA = left[key];
1544				const valsB = right[key];
1545				if (valsA.length !== valsB.length) return false;
1546				for (let j = 0; j < valsA.length; j += 1) {
1547					if (valsA[j] !== valsB[j]) return false;
1548				}
1549			}
1550			return true;
1551		},
1552		isFilterEditMode() {
1553			return !!(Page._state.filterEdit && Page._state.filterEdit.active);
1554		},
1555		getFilterEditParams() {
1556			if (Page.helpers.isFilterEditMode()) {
1557				return Page._state.filterEdit.pendingParams || {};
1558			}
1559			return Page._state.urlParams || {};
1560		},
1561		setFilterEditParams(params = {}) {
1562			if (Page.helpers.isFilterEditMode()) {
1563				Page._state.filterEdit.pendingParams = params;
1564			} else {
1565				Page._state.urlParams = params;
1566			}
1567		},
1568		beginFilterEdit() {
1569			if (Page.helpers.isFilterEditMode()) return;
1570			if (Page._state.filterTimer) {
1571				clearTimeout(Page._state.filterTimer);
1572				Page._state.filterTimer = null;
1573			}
1574			Page._state.filterEdit = {
1575				active: true,
1576				baseParams: Page.helpers.cloneParams(Page._state.urlParams || {}),
1577				pendingParams: Page.helpers.cloneParams(Page._state.urlParams || {}),
1578				page: Page._state.currentPage || 1
1579			};
1580		},
1581		commitFilterEdit() {
1582			const edit = Page._state.filterEdit;
1583			if (!edit || !edit.active) return;
1584			Page._state.filterEdit = null;
1585			const nextParams = edit.pendingParams || {};
1586			if (Page.helpers.areParamsEqual(edit.baseParams, nextParams)) {
1587				return;
1588			}
1589			Page._state.urlParams = nextParams;
1590			Page._state.currentPage = 1;
1591			Page.helpers.updateUrl(1);
1592			if (typeof window.scv3UpdateMobileFilterPills === 'function') {
1593				window.scv3UpdateMobileFilterPills();
1594			}
1595			Page.mainBlocks.runFilters();
1596		},
1597		cancelFilterEdit() {
1598			const edit = Page._state.filterEdit;
1599			if (!edit || !edit.active) return;
1600			Page._state.filterEdit = null;
1601			const baseParams = edit.baseParams || {};
1602			Page._state.urlParams = baseParams;
1603			Page._state.currentPage = edit.page || Page._state.currentPage || 1;
1604			Page.helpers.updateUrl(Page._state.currentPage);
1605			Page.helpers.syncFilterInputsFromUrl();
1606			if (typeof window.scv3UpdateMobileFilterPills === 'function') {
1607				window.scv3UpdateMobileFilterPills();
1608			}
1609		},
1610		escapeSelector(value) {
1611			const raw = String(value ?? '');
1612			if (window.CSS && typeof window.CSS.escape === 'function') {
1613				return window.CSS.escape(raw);
1614			}
1615			return raw.replace(/([^\w-])/g, '\\$1');
1616		},
1617		captureFilterFocus() {
1618			const active = document.activeElement;
1619			if (!active || !active.closest) return null;
1620			const root = active.closest('.scv3-filters');
1621			if (!root) return null;
1622			const tag = (active.tagName || '').toLowerCase();
1623			if (tag !== 'input' && tag !== 'textarea') return null;
1624
1625			const state = {
1626				tag,
1627				type: active.getAttribute('type') || '',
1628				name: active.getAttribute('name') || '',
1629				slug: '',
1630				isSearch: active.hasAttribute('data-scv3-filter-search'),
1631				selectionStart: null,
1632				selectionEnd: null,
1633				scrollLeft: typeof active.scrollLeft === 'number' ? active.scrollLeft : null
1634			};
1635
1636			const block = active.closest('.scv3-filter-block');
1637			if (block) {
1638				state.slug = block.getAttribute('data-scv3-filter-slug') || '';
1639			}
1640
1641			try {
1642				if (typeof active.selectionStart === 'number') {
1643					state.selectionStart = active.selectionStart;
1644				}
1645				if (typeof active.selectionEnd === 'number') {
1646					state.selectionEnd = active.selectionEnd;
1647				}
1648			} catch (e) {}
1649
1650			if (!state.name && !(state.isSearch && state.slug)) {
1651				return null;
1652			}
1653
1654			return state;
1655		},
1656		restoreFilterFocus(state) {
1657			if (!state) return;
1658			const root = document.querySelector('.scv3-filters');
1659			if (!root) return;
1660			const esc = Page.helpers.escapeSelector;
1661			let selector = '';
1662			if (state.name) {
1663				selector = `input[name="${esc(state.name)}"]`;
1664			} else if (state.isSearch && state.slug) {
1665				selector = `[data-scv3-filter-slug="${esc(state.slug)}"] [data-scv3-filter-search]`;
1666			}
1667			if (!selector) return;
1668
1669			const el = root.querySelector(selector);
1670			if (!el || typeof el.focus !== 'function') return;
1671			try {
1672				el.focus({ preventScroll: true });
1673			} catch (e) {
1674				el.focus();
1675			}
1676
1677			if (typeof el.setSelectionRange === 'function' &&
1678				state.selectionStart !== null &&
1679				state.selectionEnd !== null) {
1680				const len = (el.value || '').length;
1681				const start = Math.min(state.selectionStart, len);
1682				const end = Math.min(state.selectionEnd, len);
1683				try {
1684					el.setSelectionRange(start, end);
1685				} catch (e) {}
1686			}
1687
1688			if (state.scrollLeft !== null && state.scrollLeft !== undefined) {
1689				el.scrollLeft = state.scrollLeft;
1690			}
1691		},
1692
1693		buildFilterBlocksFromResponse(data = {}) {
1694			const blocks = [];
1695			const total = Number(data.total);
1696			const categoryBlock = data.category_block || data.categoryBlock;
1697			if (categoryBlock && Array.isArray(categoryBlock.options) && categoryBlock.options.length) {
1698				if (Number.isFinite(total) && total >= 0) {
1699					const activeOpt = categoryBlock.options.find((opt) => opt.is_active || opt.disabled);
1700					if (activeOpt) {
1701						activeOpt.count = total;
1702					}
1703				}
1704				blocks.push(categoryBlock);
1705			}
1706
1707			if (Array.isArray(data.filter_blocks)) {
1708				data.filter_blocks.forEach((block) => {
1709					if (!block || block.slug === 'categories') return;
1710					blocks.push(block);
1711				});
1712			} else if (data.available_filters || data.availableFilters) {
1713				const available = data.available_filters || data.availableFilters || {};
1714				const meta = data.filters_meta || data.filtersMeta || {};
1715				const buildBlock = (slug, metaEntry = {}, availableEntry = {}) => {
1716					if (!Render || !Render.Filters || typeof Render.Filters._buildBlock !== 'function') return null;
1717					return Render.Filters._buildBlock(slug, metaEntry || {}, availableEntry || {});
1718				};
1719
1720				Object.entries(meta || {}
1720).forEach(([slug, metaEntry]) => {
1721					if (slug === 'categories') return;
1722					const block = buildBlock(slug, metaEntry, available[slug]);
1723					if (block) blocks.push(block);
1724				});
1725
1726				Object.entries(available || {}).forEach(([slug, availableEntry]) => {
1727					if (slug === 'categories') return;
1728					if (blocks.find((b) => b.slug === slug)) return;
1729					const block = buildBlock(slug, {}, availableEntry);
1730					if (block) blocks.push(block);
1731				});
1732			}
1733
1734			const priceRange = data.price_range || data.priceRange || null;
1735			const withSelection = Page.helpers.applySelectionToBlocks(blocks, priceRange);
1736			const normalizeSlug = (slug) => String(slug || '').toLowerCase();
1737			const isPrice = (slug) => {
1738				const s = normalizeSlug(slug);
1739				return s === 'price' || s === 'cena';
1740			};
1741			const isCategories = (slug) => normalizeSlug(slug) === 'categories';
1742
1743			let catBlock = null;
1744			let priceBlock = null;
1745			const rest = [];
1746			withSelection.forEach((block) => {
1747				if (!block || !block.slug) {
1748					rest.push(block);
1749					return;
1750				}
1751				if (isCategories(block.slug)) {
1752					catBlock = block;
1753					return;
1754				}
1755				if (isPrice(block.slug)) {
1756					priceBlock = block;
1757					return;
1758				}
1759				rest.push(block);
1760			});
1761
1762			const ordered = [];
1763			if (catBlock) ordered.push(catBlock);
1764			if (priceBlock) ordered.push(priceBlock);
1765			ordered.push(...rest);
1766			return ordered;
1767		},
1768		_collectFilterBlocks(root) {
1769			if (!root) return [];
1770			return Array.from(root.children).filter((node) => {
1771				return node && node.nodeType === 1 && node.classList.contains('scv3-filter-block');
1772			});
1773		},
1774		_escapeFilterSlug(slug = '') {
1775			const value = String(slug || '').trim();
1776			if (!value) return '';
1777			if (typeof window !== 'undefined' && window.CSS && typeof window.CSS.escape === 'function') {
1778				return CSS.escape(value);
1779			}
1780			return value.replace(/["\\\\]/g, '\\\\$&');
1781		},
1782		_findNextFilterInsertionTarget(container, newBlocks, currentIndex) {
1783			if (!container || !Array.isArray(newBlocks)) return null;
1784			for (let i = currentIndex + 1; i < newBlocks.length; i += 1) {
1785				const slug = newBlocks[i]?.dataset?.scv3FilterSlug;
1786				if (!slug) continue;
1787				const selector = `[data-scv3-filter-slug="${Page.helpers._escapeFilterSlug(slug)}"]`;
1788				const nextEl = container.querySelector(selector);
1789				if (nextEl) {
1790					return nextEl;
1791				}
1792			}
1793			return null;
1794		},
1795		_filterHtmlToElement(html = '') {
1796			const markup = String(html || '').trim();
1797			if (!markup || typeof document === 'undefined') return null;
1798			const template = document.createElement('template');
1799			template.innerHTML = markup;
1800			return template.content.querySelector('.scv3-filters');
1801		},
1802		_animateFilterBlockInsertion(block) {
1803			if (!block || !block.classList) return;
1804			block.classList.add('scv3-filter-block--animating', 'scv3-filter-block--entering');
1805			const computed = typeof window !== 'undefined' && window.getComputedStyle
1806				? window.getComputedStyle(block)
1807				: null;
1808			const targetMargin = computed?.marginBottom || '';
1809			block.style.opacity = '0';
1810			block.style.transform = 'translateY(-12px)';
1811			block.style.maxHeight = '0px';
1812			block.style.marginBottom = '0px';
1813			block.getBoundingClientRect();
1814
1815			requestAnimationFrame(() => {
1816				block.style.maxHeight = `${block.scrollHeight}px`;
1817				block.style.opacity = '1';
1818				block.style.transform = 'translateY(0)';
1819				if (targetMargin) {
1820					block.style.marginBottom = targetMargin;
1821				}
1822			});
1823
1824			const cleanup = (event) => {
1825				if (event && event.propertyName !== 'max-height') return;
1826				block.removeEventListener('transitionend', cleanup);
1827				block.classList.remove('scv3-filter-block--animating', 'scv3-filter-block--entering');
1828				block.style.maxHeight = '';
1829				block.style.marginBottom = '';
1830				block.style.opacity = '';
1831				block.style.transform = '';
1832			};
1833			block.addEventListener('transitionend', cleanup);
1834		},
1835		_animateFilterBlockRemoval(block) {
1836			if (!block || !block.classList || block.dataset.scv3FilterRemoving === '1') return;
1837			block.dataset.scv3FilterRemoving = '1';
1838			block.classList.add('scv3-filter-block--animating', 'scv3-filter-block--exiting');
1839			const computed = typeof window !== 'undefined' && window.getComputedStyle
1840				? window.getComputedStyle(block)
1841				: null;
1842			const marginValue = computed?.marginBottom || '0px';
1843			const height = block.getBoundingClientRect().height || block.scrollHeight;
1844			block.style.maxHeight = `${height}px`;
1845			block.style.marginBottom = marginValue;
1846			block.style.opacity = '1';
1847			block.style.transform = 'translateY(0)';
1848			block.getBoundingClientRect();
1849
1850			requestAnimationFrame(() => {
1851				block.style.maxHeight = '0px';
1852				block.style.opacity = '0';
1853				block.style.transform = 'translateY(-12px)';
1854				block.style.marginBottom = '0px';
1855			});
1856
1857			const cleanup = (event) => {
1858				if (event && event.propertyName !== 'max-height') return;
1859				block.removeEventListener('transitionend', cleanup);
1860				if (block.parentNode) {
1861					block.parentNode.removeChild(block);
1862				}
1863			};
1864			
1865			block.addEventListener('transitionend', cleanup);
1866		},
1867		_isDesktopFilterAnimationEnabled() {
1868			if (typeof window === 'undefined' || !window.matchMedia) return false;
1869			return window.matchMedia('(min-width: 769px)').matches;
1870		},
1871		_animateFilterBlockReplacement(existing, newBlock, reference = null) {
1872			if (!existing || !newBlock || !existing.parentNode) return false;
1873			const container = existing.parentNode;
1874			const oldHeight = existing.getBoundingClientRect().height || existing.scrollHeight || 0;
1875			const targetMargin = (typeof window !== 'undefined' && window.getComputedStyle)
1876				? (window.getComputedStyle(newBlock).marginBottom || '')
1877				: '';
1878			const startHeight = Math.max(1, oldHeight || newBlock.scrollHeight || 1);
1879
1880			if (reference && reference.parentNode === container) {
1881				container.insertBefore(newBlock, reference);
1882			} else {
1883				container.appendChild(newBlock);
1884			}
1885			existing.remove();
1886
1887			newBlock.classList.add('scv3-filter-block--animating', 'scv3-filter-block--entering');
1888			newBlock.style.maxHeight = `${startHeight}px`;
1889			newBlock.style.marginBottom = targetMargin || '';
1890			newBlock.style.opacity = '1';
1891			newBlock.style.transform = 'translateY(0)';
1892			newBlock.getBoundingClientRect();
1893
1894			let phase = 'collapse';
1895			let timeoutId = null;
1896			const cleanup = () => {
1897				if (timeoutId) {
1898					clearTimeout(timeoutId);
1899					timeoutId = null;
1900				}
1901				newBlock.removeEventListener('transitionend', onTransitionEnd);
1902				newBlock.classList.remove('scv3-filter-block--animating', 'scv3-filter-block--entering');
1903				newBlock.style.maxHeight = '';
1904				newBlock.style.marginBottom = '';
1905				newBlock.style.opacity = '';
1906				newBlock.style.transform = '';
1907			};
1908			const expand = () => {
1909				phase = 'expand';
1910				newBlock.style.maxHeight = `${Math.max(1, newBlock.scrollHeight || 1)}px`;
1911				newBlock.style.opacity = '1';
1912				newBlock.style.transform = 'translateY(0)';
1913				if (targetMargin) {
1914					newBlock.style.marginBottom = targetMargin;
1915				}
1916				timeoutId = setTimeout(cleanup, 420);
1917			};
1918			const onTransitionEnd = (event) => {
1919				if (!event || event.target !== newBlock || event.propertyName !== 'max-height') return;
1920				if (phase === 'collapse') {
1921					expand();
1922					return;
1923				}
1924				cleanup();
1925			};
1926
1927			newBlock.addEventListener('transitionend', onTransitionEnd);
1928			requestAnimationFrame(() => {
1929				newBlock.style.maxHeight = '0px';
1930				newBlock.style.opacity = '0';
1931				newBlock.style.transform = 'translateY(-12px)';
1932				newBlock.style.marginBottom = '0px';
1933			});
1934			timeoutId = setTimeout(() => {
1935				if (phase === 'collapse') {
1936					expand();
1937					return;
1938				}
1939				cleanup();
1940			}, 420);
1941
1942			return true;
1943		},
1944		_normalizeFilterBlockForCompare(block) {
1945			if (!block || !block.cloneNode) return '';
1946			const clone = block.cloneNode(true);
1947			const stripClass = (el, className) => {
1948				if (el && el.classList) {
1949					el.classList.remove(className);
1950				}
1951			};
1952			clone.removeAttribute('style');
1953			stripClass(clone, 'scv3-filter-block--animating');
1954			stripClass(clone, 'scv3-filter-block--entering');
1955			stripClass(clone, 'scv3-filter-block--exiting');
1956			stripClass(clone, 'scv3-is-open');
1957			clone.querySelectorAll('[style]').forEach((node) => node.removeAttribute('style'));
1958			clone.querySelectorAll('[data-scv3-filter-label]').forEach((node) => node.removeAttribute('data-scv3-filter-label'));
1959			clone.querySelectorAll('.scv3-filter-list').forEach((node) => {
1960				stripClass(node, 'scv3-filter-list--scroll-active');
1961			});
1962			clone.querySelectorAll('.scv3-filter-more').forEach((node) => {
1963				stripClass(node, 'scv3-filter-more--expanded');
1964			});
1965			clone.querySelectorAll('[data-scv3-filter-option]').forEach((node) => {
1966				stripClass(node, 'scv3-filter-option--hidden');
1967				stripClass(node, 'scv3-filter-option--search-hidden');
1968			});
1969			return clone.outerHTML || '';
1970		},
1971		_getFilterBlockUiState(block) {
1972			if (!block || !block.querySelectorAll) {
1973				return { hasMore: false, expanded: false, visibleOptionCount: 0 };
1974			}
1975			const more = block.querySelector('[data-scv3-filter-more]');
1976			const hasMore = !!more;
1977			const expanded = !!(more && more.classList.contains('scv3-filter-more--expanded'));
1978			const visibleOptionCount = block.querySelectorAll('[data-scv3-filter-option]:not(.scv3-filter-option--hidden
1978):not(.scv3-filter-option--search-hidden)').length;
1979			return { hasMore, expanded, visibleOptionCount };
1980		},
1981		_buildFilterBlockSemanticSignature(block, options = {}) {
1982			if (!block || !block.querySelectorAll) return null;
1983			const slug = String(block.dataset?.scv3FilterSlug || '').trim();
1984			const list = block.querySelector('[data-scv3-filter-content]');
1985			const normalizeListClass = (raw = '') => String(raw || '')
1986				.split(/\s+/)
1987				.filter(Boolean)
1988				.filter((cls) => cls !== 'scv3-filter-list--scroll-active')
1989				.sort()
1990				.join(' ');
1991			const listClass = normalizeListClass(list ? String(list.className || '') : '');
1992			const ignoreChecked = options.ignoreChecked === true;
1993			const ignoreOrder = options.ignoreOrder === true;
1994			const compareMode = String(options.compareMode || 'all');
1995			const firstCount = Number.isFinite(options.firstCount) && options.firstCount > 0
1996				? options.firstCount
1997				: 0;
1998			const priceOnly = options.priceOnly === true;
1999			const allOptions = Array.from(block.querySelectorAll('[data-scv3-filter-option]')).map((opt) => {
2000				const input = opt.querySelector('input[type="checkbox"]');
2001				return {
2002					value: input ? String(input.value || '') : '',
2003					checked: ignoreChecked ? false : !!(input && input.checked),
2004					hiddenByLimit: opt.classList.contains('scv3-filter-option--hidden'),
2005					hiddenBySearch: opt.classList.contains('scv3-filter-option--search-hidden')
2006				};
2007			});
2008			let optionsForCompare = allOptions;
2009			if (priceOnly) {
2010				optionsForCompare = [];
2011			} else if (firstCount > 0) {
2012				optionsForCompare = allOptions.slice(0, firstCount).map((item) => ({
2013					value: item.value,
2014					checked: item.checked
2015				}));
2016			} else if (compareMode === 'visible_only') {
2017				optionsForCompare = allOptions
2018					.filter((item) => !item.hiddenByLimit && !item.hiddenBySearch)
2019					.map((item) => ({
2020						value: item.value,
2021						checked: item.checked
2022					}));
2023			} else {
2024				optionsForCompare = allOptions.map((item) => ({
2025					value: item.value,
2026					checked: item.checked
2027				}));
2028			}
2029			if (ignoreOrder) {
2030				optionsForCompare = optionsForCompare
2031					.map((item) => ({
2032						value: item.value,
2033						checked: item.checked
2034					}))
2035					.sort((a, b) => {
2036						const av = `${a.value}|${a.checked ? '1' : '0'}`;
2037						const bv = `${b.value}|${b.checked ? '1' : '0'}`;
2038						return av.localeCompare(bv);
2039					});
2040			}
2041			const priceInputs = Array.from(block.querySelectorAll('.scv3-filter-list--price input')).map((input) => ({
2042				name: String(input.name || '')
2043			}));
2044			return {
2045				slug,
2046				listClass: priceOnly ? '' : listClass,
2047				priceInputs,
2048				options: optionsForCompare
2049			};
2050		},
2051		_toDebugJson(value) {
2052			try {
2053				return JSON.stringify(value);
2054			} catch (e) {
2055				return '';
2056			}
2057		},
2058		_hashDebugString(str = '') {
2059			const s = String(str || '');
2060			let hash = 2166136261;
2061			for (let i = 0; i < s.length; i += 1) {
2062				hash ^= s.charCodeAt(i);
2063				hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
2064			}
2065			return (hash >>> 0).toString(16).padStart(8, '0');
2066		},
2067		_persistFilterAnimationDebug(entry = {}) {
2068			if (typeof window === 'undefined' || !window.localStorage) return;
2069			const enabledFlag = window.scv3FilterAnimationDebug;
2070			if (enabledFlag === false) return;
2071			const key = 'scv3-filter-debug-log';
2072			const maxEntries = 250;
2073			let list = [];
2074			try {
2075				const raw = window.localStorage.getItem(key);
2076				const parsed = raw ? JSON.parse(raw) : [];
2077				if (Array.isArray(parsed)) list = parsed;
2078			} catch (e) {
2079				list = [];
2080			}
2081			list.push({
2082				ts: new Date().toISOString(),
2083				...entry
2084			});
2085			if (list.length > maxEntries) {
2086				list = list.slice(list.length - maxEntries);
2087			}
2088			try {
2089				window.localStorage.setItem(key, JSON.stringify(list));
2090			} catch (e) {}
2091		},
2092		_ensureFilterDebugStorageTools() {
2093			if (typeof window === 'undefined') return;
2094			if (typeof window.scv3GetFilterAnimationDebugLog !== 'function') {
2095				window.scv3GetFilterAnimationDebugLog = () => {
2096					try {
2097						const raw = window.localStorage.getItem('scv3-filter-debug-log');
2098						return raw ? JSON.parse(raw) : [];
2099					} catch (e) {
2100						return [];
2101					}
2102				};
2103			}
2104			if (typeof window.scv3ClearFilterAnimationDebugLog !== 'function') {
2105				window.scv3ClearFilterAnimationDebugLog = () => {
2106					try {
2107						window.localStorage.removeItem('scv3-filter-debug-log');
2108					} catch (e) {}
2109					return true;
2110				};
2111			}
2112		},
2113		_hasFilterBlockDataChanges(existing, incoming, compareOptions = {}) {
2114			const currentSemantic = Page.helpers._buildFilterBlockSemanticSignature(existing, compareOptions);
2115			const nextSemantic = Page.helpers._buildFilterBlockSemanticSignature(incoming, compareOptions);
2116			if (currentSemantic && nextSemantic) {
2117				return JSON.stringify(currentSemantic) !== JSON.stringify(nextSemantic);
2118			}
2119			const current = Page.helpers._normalizeFilterBlockForCompare(existing);
2120			const next = Page.helpers._normalizeFilterBlockForCompare(incoming);
2121			return current !== next;
2122		},
2123		_buildSemanticCompareDebug(beforeSemantic, afterSemantic) {
2124			const toPriceNames = (semantic) => Array.isArray(semantic?.priceInputs)
2125				? semantic.priceInputs.map((item) => String(item?.name || ''))
2126				: [];
2127			const toOptionKeys = (semantic) => Array.isArray(semantic?.options)
2128				? semantic.options.map((item) => `${String(item?.value || '')}|${item?.checked ? '1' : '0'}`)
2129				: [];
2130			const bPrice = toPriceNames(beforeSemantic);
2131			const aPrice = toPriceNames(afterSemantic);
2132			const bOpts = toOptionKeys(beforeSemantic);
2133			const aOpts = toOptionKeys(afterSemantic);
2134			const maxLen = Math.max(bOpts.length, aOpts.length);
2135			const diffIndexes = [];
2136			for (let i = 0; i < maxLen; i += 1) {
2137				if ((bOpts[i] || '') !== (aOpts[i] || '')) {
2138					diffIndexes.push(i);
2139					if (diffIndexes.length >= 8) break;
2140				}
2141			}
2142			const samplePairs = diffIndexes.slice(0, 4).map((idx) => ({
2143				idx,
2144				before: typeof bOpts[idx] === 'undefined' ? null : bOpts[idx],
2145				after: typeof aOpts[idx] === 'undefined' ? null : aOpts[idx]
2146			}));
2147			return {
2148				compared: {
2149					before: {
2150						listClass: String(beforeSemantic?.listClass || ''),
2151						priceInputNames: bPrice,
2152						optionsCount: bOpts.length,
2153						optionsSample: bOpts.slice(0, 12)
2154					},
2155					after: {
2156						listClass: String(afterSemantic?.listClass || ''),
2157						priceInputNames: aPrice,
2158						optionsCount: aOpts.length,
2159						optionsSample: aOpts.slice(0, 12)
2160					}
2161				},
2162				mismatch: {
2163					listClassChanged: String(beforeSemantic?.listClass || '') !== String(afterSemantic?.listClass || ''),
2164					priceInputNamesChanged: JSON.stringify(bPrice) !== JSON.stringify(aPrice),
2165					optionsCountChanged: bOpts.length !== aOpts.length,
2166					optionDiffIndexes: diffIndexes,
2167					optionDiffSample: samplePairs
2168				}
2169			};
2170		},
2171		_collectFilterBlockDebugData(block) {
2172			if (!block || !block.querySelectorAll) return null;
2173			const slug = String(block.dataset?.scv3FilterSlug || '').trim();
2174			const labelNode = block.querySelector('.scv3-filter-block__label');
2175			const label = (labelNode ? labelNode.textContent : '').replace(/\s+/g, ' ').trim();
2176			const allOptions = Array.from(block.querySelectorAll('[data-scv3-filter-option]'));
2177			const options = allOptions.map((opt) => {
2178				const input = opt.querySelector('input[type="checkbox"]');
2179				const textNode = opt.querySelector('.scv3-filter-option__text') || opt;
2180				const text = (textNode.textContent || '').replace(/\s+/g, ' ').trim();
2181				return {
2182					label: text,
2183					value: input ? String(input.value || '') : '',
2184					checked: !!(input && input.checked),
2185					hiddenByLimit: opt.classList.contains('scv3-filter-option--hidden'),
2186					hiddenBySearch: opt.classList.contains('scv3-filter-option--search-hidden')
2187				};
2188			});
2189			const checkedValues = options.filter((item) => item.checked).map((item) => item.value || item.label);
2190			return {
2191				slug,
2192				label,
2193				optionsCount: options.length,
2194				checkedValues,
2195				options
2196			};
2197		},
2198		_logFilterAnimationDebug(payload = {}) {
2199			if (typeof window === 'undefined' || !window.console || typeof window.console.log !== 'function') return;
2200			const enabledFlag = window.scv3FilterAnimationDebug;
2201			if (enabledFlag === false) return;
2202			const verbose = window.scv3FilterAnimationDebugVerbose === true;
2203			const stamp = new Date().toISOString();
2204			const action = payload.action || 'unknown';
2205			const slug = payload.slug || '';
2206			const changedSuffix = Object.prototype.hasOwnProperty.call(payload, 'changed')
2207				? ` changed=${payload.changed ? '1' : '0'}`
2208				: '';
2209			console.log(`[scv3-filter-debug ${stamp}] action=${action} slug=${slug}${changedSuffix}`);
2210			Page.helpers._persistFilterAnimationDebug({
2211				type: 'action',
2212				action,
2213				slug,
2214				changed: Object.prototype.hasOwnProperty.call(payload, 'changed') ? !!payload.changed : null,
2215				reason: payload.reason || '',
2216				meta: payload.meta || null
2217			});
2218			if (!verbose) return;
2219			console.groupCollapsed(`[scv3-filter-debug-details ${stamp}] action=${action} slug=${slug}`);
2220			if (payload.reason) console.log('reason:', payload.reason);
2221			if (payload.meta) console.log('meta:', payload.meta);
2222			if (payload.before) console.log('before:', payload.before);
2223			if (payload.after) console.log('after:', payload.after);
2224			console.groupEnd();
2225		},
2226		_logFilterAnimationSummary(summary = {}) {
2227			if (typeof window === 'undefined' || !window.console || typeof window.console.log !== 'function') return;
2228			const enabledFlag = window.scv3FilterAnimationDebug;
2229			if (enabledFlag === false) return;
2230			const updated = Array.isArray(summary.updated) ? summary.updated : [];
2231			const skipped = Array.isArray(summary.skipped) ? summary.skipped : [];
2232			const inserted = Array.isArray(summary.inserted) ? summary.inserted : [];
2233			const removed = Array.isArray(summary.removed) ? summary.removed : [];
2234			const total = updated.length + skipped.length + inserted.length + removed.length;
2235			console.log('[scv3-filter-debug-summary]', {
2236				total,
2237				updated,
2238				skipped,
2239				inserted,
2240				removed
2241			});
2242			Page.helpers._persistFilterAnimationDebug({
2243				type: 'summary',
2244				total,
2245				updated,
2246				skipped,
2247				inserted,
2248				removed
2249			});
2250		},
2251		updateFilterBlocksWithAnimation(container, html) {
2252			const newFilters = Page.helpers._filterHtmlToElement(html);
2253			if (!newFilters || !container) return false;
2254			Page.helpers._ensureFilterDebugStorageTools();
2255			const newBlocks = Page.helpers._collectFilterBlocks(newFilters);
2256			const existingBlocks = Page.helpers._collectFilterBlocks(container);
2257			const slugs = new Set(newBlocks.map((block) => block.dataset?.scv3FilterSlug || ''));
2258			const useReplacementAnimation = Page.helpers._isDesktopFilterAnimationEnabled();
2259			const summary = {
2260				updated: [],
2261				skipped: [],
2262				inserted: [],
2263				removed: []
2264			};
2265			Page.helpers._logFilterAnimationDebug({
2266				action: 'start',
2267				meta: {
2268					useReplacementAnimation,
2269					incomingSlugs: newBlocks.map((block) => String(block.dataset?.scv3FilterSlug || '')),
2270					existingSlugs: existingBlocks.map((block) => String(block.dataset?.scv3FilterSlug || ''))
2271				}
2272			});
2273
2274			existingBlocks.forEach((block) => {
2275				const slug = block.dataset?.scv3FilterSlug || '';
2276				if (slug && !slugs.has(slug)) {
2277					summary.removed.push(slug);
2278					Page.helpers._logFilterAnimationDebug({
2279						action: 'remove',
2280						slug,
2281						reason: 'missing in backend response',
2282						before: Page.helpers._collectFilterBlockDebugData(block),
2283						after: null
2284					});
2285					Page.helpers._animateFilterBlockRemoval(block);
2286				}
2287			});
2288
2289			newBlocks.forEach((newBlock, idx) => {
2290				const slug = newBlock.dataset?.scv3FilterSlug || '';
2291				const selector = slug
2292					? `[data-scv3-filter-slug="${Page.helpers._escapeFilterSlug(slug)}"]`
2293					: null;
2294				const existing = selector ? container.querySelector(selector) : null;
2295				const reference = Page.helpers._findNextFilterInsertionTarget(container, newBlocks, idx);
2296				if (existing) {
2297					const safeReference = reference === existing ? existing.nextSibling : reference;
2298					const uiState = Page.helpers._getFilterBlockUiState(existing);
2299					const isPriceBlock = !!existing.querySelector('.scv3-filter-list--price');
2300					const compareOptions = isPriceBlock
2301						? { ignoreChecked: true, priceOnly: true }
2302						: ((uiState.hasMore && !uiState.expanded && uiState.visibleOptionCount > 0)
2303							? {
2304								firstCount: uiState.visibleOptionCount,
2305								ignoreOrder: false,
2306								ignoreChecked: true
2307							}
2308							: { ignoreChecked: true });
2309					const beforeSemantic = Page.helpers._buildFilterBlockSemanticSignature(existing, compareOptions);
2310					const afterSemantic = Page.helpers._buildFilterBlockSemanticSignature(newBlock, compareOptions);
2311					const beforeJson = Page.helpers._toDebugJson(beforeSemantic);
2312					const afterJson = Page.helpers._toDebugJson(afterSemantic);
2313					const hasChanges = beforeJson !== afterJson;
2314					const semanticCompare = Page.helpers._buildSemanticCompareDebug(beforeSemantic, afterSemantic);
2315					const signatureMeta = {
2316						beforeHash: Page.helpers._hashDebugString(beforeJson),
2317						afterHash: Page.helpers._hashDebugString(afterJson),
2318						beforeSize: beforeJson.length,
2319						afterSize: afterJson.length
2320					};
2321					if (!hasChanges) {
2322						// Keep block intact to avoid unnecessary re-render and animation.
2323						summary.skipped.push(slug);
2324						Page.helpers._logFilterAnimationDebug({
2325							action: 'skip',
2326							slug,
2327							changed: false,
2328							reason: 'block data is identical',
2329							meta: { compareOptions, uiState, signature: signatureMeta, compare: semanticCompare },
2330							before: Page.helpers._collectFilterBlockDebugData(existing),
2331							after: Page.helpers._collectFilterBlockDebugData(newBlock)
2332						});
2333						if (safeReference) {
2334							if (safeReference !== existing) {
2335								container.insertBefore(existing, safeReference);
2336							}
2337						} else {
2338							container.appendChild(existing);
2339						}
2340						return;
2341					}
2342					summary.updated.push(slug);
2343					Page.helpers._logFilterAnimationDebug({
2344						action: useReplacementAnimation ? 'replace-animated' : 'replace',
2345						slug,
2346						changed: true,
2347						meta: { compareOptions, uiState, signature: signatureMeta, compare: semanticCompare },
2348						before: Page.helpers._collectFilterBlockDebugData(existing),
2349						after: Page.helpers._collectFilterBlockDebugData(newBlock)
2350					});
2351					if (useReplacementAnimation) {
2352						Page.helpers._animateFilterBlockReplacement(existing, newBlock, safeReference);
2353					} else {
2354						existing.replaceWith(newBlock);
2355						// Ensure correct ordering even if the block already existed.
2356						if (safeReference) {
2357							if (safeReference !== newBlock) {
2358								container.insertBefore(newBlock, safeReference);
2359							}
2360						} else {
2361							container.appendChild(newBlock);
2362						}
2363					}
2364				} else {
2365					summary.inserted.push(slug);
2366					Page.helpers._logFilterAnimationDebug({
2367						action: 'insert',
2368						slug,
2369						changed: true,
2370						reason: 'new block from backend response',
2371						before: null,
2372						after: Page.helpers._collectFilterBlockDebugData(newBlock)
2373					});
2374					container.insertBefore(newBlock, reference);
2375					Page.helpers._animateFilterBlockInsertion(newBlock);
2376				}
2377			});
2378			Page.helpers._logFilterAnimationSummary(summary);
2379
2380			return true;
2381		},
2382		buildHeaderData(data = {}) {
2383			const baseTitle = 'Все товары каталога slamdunk';
2384			let title = baseTitle;
2385			const breadcrumbs = [
2386				{ label: 'Главная', url: '/' },
2387				{ label: baseTitle, url: '/shop/' }
2388			];
2389
2390			const params = Page._state.urlParams || {};
2391			const searchText = typeof params.s === 'string' ? params.s.trim() : '';
2392			const route = data.route || data.routeInfo || {};
2393			const routeType = String(route.route_type || route.type || '').toLowerCase();
2394			const extra = data.route_extra || data.routeExtra || {};
2395			const termsPath = Array.isArray(extra.terms_path) ? extra.terms_path : [];
2396
2397			let skipCategories = false;
2398			if (searchText) {
2399				title = `Результат поиска: “${searchText}”`;
2400				breadcrumbs.push({ label: title });
2401				skipCategories = true;
2402			} else if (routeType === 'tags' && termsPath.length) {
2403				const tag = termsPath[termsPath.length - 1];
2404				const tagName = Page.helpers.stripLegacySeoTitlePlaceholder(tag?.name || tag?.slug || '');
2405				if (tagName) {
2406					title = tagName;
2407					breadcrumbs.push({ label: title });
2408					skipCategories = true;
2409				}
2410			}
2411
2412			if (routeType === 'cat' && termsPath.length) {
2413				const segments = [];
2414				termsPath.forEach((term, idx) => {
2415					const slug = String(term?.slug || '').trim();
2416					if (!slug) return;
2417					segments.push(slug);
2418					const name = Page.helpers.stripLegacySeoTitlePlaceholder(term?.name || slug);
2419					breadcrumbs.push({
2420						label: name,
2421						url: idx === termsPath.length - 1 ? '' : `/cat/${segments.join('/')}/`
2422					});
2423				});
2424				if (segments.length) {
2425					title = breadcrumbs[breadcrumbs.length - 1].label;
2426					skipCategories = true;
2427				}
2428			}
2429
2430			if (!skipCategories) {
2431				const categories = Array.isArray(data.categories)
2432				? data.categories
2433				: (Array.isArray(Page._state.categories) ? Page._state.categories : []);
2434				const activeSlug = String(
2435					data.active_category_slug || data.activeCategorySlug || Page._state.activeCategorySlug || ''
2436				).toLowerCase().trim();
2437
2438				if (activeSlug && categories.length) {
2439					const byId = new Map();
2440					const bySlug = new Map();
2441					categories.forEach((item) => {
2442						const id = Number(item.term_id || item.id || item.termId || 0);
2443						const slug = String(item.slug || '').toLowerCase().trim();
2444						if (!id || !slug) return;
2445						const parent = Number(item.parent || item.parent_id || item.parentId || 0);
2446						const name = Page.helpers.stripLegacySeoTitlePlaceholder(item.name || item.title || slug);
2447						const entry = { id, slug, parent, name };
2448						byId.set(id, entry);
2449						bySlug.set(slug, entry);
2450					});
2451
2452					const chain = [];
2453					let current = bySlug.get(activeSlug) || null;
2454					let guard = 0;
2455					while (current && guard < 12) {
2456						chain.unshift(current);
2457						const parentId = current.parent || 0;
2458						current = parentId ? byId.get(parentId) : null;
2459						guard += 1;
2460					}
2461
2462					if (chain.length) {
2463						const segments = [];
2464						chain.forEach((term, idx) => {
2465							segments.push(term.slug);
2466							const url = `/cat/${segments.join('/')}/`;
2467							breadcrumbs.push({
2468								label: term.name,
2469								url: idx === chain.length - 1 ? '' : url
2470							});
2471						});
2472						title = chain[chain.length - 1].name;
2473					}
2474				}
2475			}
2476
2477			const page = Page._state.currentPage || 1;
2478			if (page > 1) {
2479				title = `${title} - Страница ${page}`;
2480				const lastIdx = breadcrumbs.length - 1;
2481				if (lastIdx >= 0) {
2482					breadcrumbs[lastIdx].label = title;
2483				}
2484			}
2485			if (data.page_meta && data.page_meta.h1) {
2486				title = Page.helpers.stripLegacySeoTitlePlaceholder(data.page_meta.h1);
2487				if (page > 1) {
2488					title += ` - Страница ${page}`;
2489				}
2490			}
2491
2492			return { title, breadcrumbs };
2493		},
2494		buildMetaData(data = {}) {
2495			const params = Page._state.urlParams || {};
2496			const page = Page._state.currentPage || 1;
2497			const navigation = Page.helpers.resolveLandingNavigation(params);
2498			const hasQuery = Object.keys(navigation.query || {}).length > 0;
2499			const headerData = Page.helpers.buildHeaderData(data);
2500			const route = data.route || data.routeInfo || {};
2501			const routeType = String(route.route_type || route.type || '').toLowerCase();
2502			const extra = data.route_extra || data.routeExtra || {};
2503			const termsPath = Array.isArray(extra.terms_path) ? extra.terms_path : [];
2504
2505			let metaTitle = 'Все товары в каталоге сервиса Slamdunk.shop';
2506			let metaDescription = null;
2507
2508			const searchText = typeof params.s === 'string' ? params.s.trim() : '';
2509			if (searchText) {
2510				if (page > 1) {
2511					metaTitle = `Результат поиска “${searchText}” — Страница ${page}`;
2512				} else {
2513					metaTitle = `${searchText} — Slamdunk.Shop`;
2514				}
2515				metaDescription = '';
2516			} else if (routeType === 'cat' || routeType === 'tags') {
2517				let termName = '';
2518				if (termsPath.length) {
2519					const last = termsPath[termsPath.length - 1];
2520					termName = Page.helpers.stripLegacySeoTitlePlaceholder(last?.name || last?.slug || '');
2521				}
2522				if (!termName) {
2523					termName = Page.helpers.stripLegacySeoTitlePlaceholder(headerData.title || '');
2524				}
2525				if (termName) {
2526					if (page > 1) {
2527						metaTitle = `${termName} - Страница ${page}`;
2528						metaDescription = `${termName} - Страница ${page}.`;
2529					} else {
2530						metaTitle = `${termName} купить оригинал - цена в Москве и России с бесплатной доставкой`;
2531						metaDescription = `Цена ${termName} - оригинальные товары спортивных брендов в интернет-магазине Слэмданк.`;
2532					}
2533				}
2534			}
2535
2536			if (navigation.landing && data.page_meta) {
2537				metaTitle = String(data.page_meta.title || metaTitle);
2538				metaDescription = data.page_meta.description ?? metaDescription;
2539			}
2540			const canonical = Render.Helpers.pageUrl(Render.Helpers.buildPageUrl(navigation.path, 1, {}));
2541			const nextPage = page < Page._state.totalPages ? page + 1 : null;
2542			const prevPage = page > 1 ? page - 1 : null;
2543			const nextUrl = nextPage
2544				? Render.Helpers.pageUrl(Render.Helpers.buildPageUrl(navigation.path, nextPage, navigation.query))
2545				: '';
2546			const prevUrl = prevPage
2547				? Render.Helpers.pageUrl(Render.Helpers.buildPageUrl(navigation.path, prevPage, navigation.query))
2548				: '';
2549			const robots = hasQuery
2550				? 'noindex, follow'
2551				: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';
2552			const modifiedRaw = String(
2553				data.page_last_modified_gmt
2554				|| data.pageLastModifiedGmt
2555				|| ''
2556			).trim();
2557			const existingModified = document.head.querySelector('meta[property="article:modified_time"]')?.getAttribute('content') || '';
2558			let articleModifiedTime = modifiedRaw || String(existingModified || '').trim();
2559			if (articleModifiedTime && !articleModifiedTime.includes('T')) {
2560				const dt = new Date(articleModifiedTime);
2561				if (!Number.isNaN(dt.getTime())) {
2562					articleModifiedTime = dt.toISOString();
2563				}
2564			}
2565
2566			return {
2567				title: metaTitle,
2568				description: metaDescription,
2569				og_title: metaTitle,
2570				og_description: metaDescription,
2571				canonical,
2572				og_url: canonical,
2573				next: nextUrl,
2574				prev: prevUrl,
2575				robots,
2576				article_modified_time: articleModifiedTime || undefined
2577			};
2578		},
2579		updateHeader(data = {}, total = null) {
2580			if (!Render || !Render.Header) return;
2581			const title = data.title || '';
2582			const breadcrumbs = Array.isArray(data.breadcrumbs) ? data.breadcrumbs : [];
2583			const crumbsHtml = Render.Header.renderBreadcrumbs(breadcrumbs);
2584			let crumbsReplaced = false;
2585			if (crumbsHtml) {
2586				const $crumbs = $('.crumbs.crumbs__container').first();
2587				if ($crumbs.length) {
2588					$crumbs.replaceWith(crumbsHtml);
2589					crumbsReplaced = true;
2590				}
2591			}
2592			if (crumbsReplaced && typeof window.bc_renew === 'function') {
2593				window.bc_renew();
2594			}
2595			const headerHtml = Render.Header.renderHeader({ title, count: total });
2596			if (headerHtml) {
2597				const $header = $('.header-page').first();
2598				if ($header.length) {
2599					$header.replaceWith(headerHtml);
2600				}
2601			}
2602		},
2603		updateMeta(meta = {}) {
2604			if (!Render || !Render.Meta) return;
2605			Render.Meta.apply(meta);
2606		},
2607		updateMainTextSpacing(totalPages = null) {
2608			const pages = Number.isFinite(Number(totalPages))
2609				? Number(totalPages)
2610				: Number(Page._state.totalPages || 1);
2611			const $mainText = $('.main-text').first();
2612			if (!$mainText.length) return;
2613			$mainText.toggleClass('main-text--no-pagination', pages < 2);
2614		},
2615		updateTermDescription(descriptionHtml = '') {
2616			const $mainText = $('.main-text').first();
2617			if (!$mainText.length) return;
2618			const $desc = $mainText.find('.term-description').first();
2619			if (!$desc.length) return;
2620			const html = String(descriptionHtml || '').trim();
2621			if (html === '') {
2622				$desc.html('');
2623				$mainText.hide();
2624				return;
2625			}
2626			$desc.html(html);
2627			$mainText.show();
2628		},
2629		updateTermAdminInfo(data = {}) {
2630			const block = document.querySelector(Page._selectors.termAdminInfo);
2631			if (!block) return;
2632
2633			const idNode = block.querySelector('[data-scv3-term-admin-id]');
2634			const linkNode = block.querySelector('[data-scv3-term-admin-link]');
2635			if (!idNode || !linkNode) return;
2636
2637			let termId = 0;
2638			let routeType = '';
2639			let href = '';
2640
2641			const apiInfo = (data && typeof data.catalog_term_admin_info === 'object' && data.catalog_term_admin_info)
2642				? data.catalog_term_admin_info
2643				: ((data && typeof data.catalogTermAdminInfo === 'object' && data.catalogTermAdminInfo) ? data.catalogTermAdminInfo : null);
2644			if (apiInfo) {
2645				termId = Number(apiInfo.term_id || apiInfo.termId || 0);
2646				href = String(apiInfo.edit_url || apiInfo.editUrl || '').trim();
2647			}
2648
2649			if (!Number.isFinite(termId) || termId <= 0) {
2650				const route = (data && typeof data.route === 'object' && data.route) ? data.route : {};
2651				routeType = String(route.route_type || route.routeType || '').trim();
2652				const routeExtra = (data && typeof data.route_extra === 'object' && data.route_extra)
2653					? data.route_extra
2654					: ((data && typeof data.routeExtra === 'object' && data.routeExtra) ? data.routeExtra : {});
2655				const termsPathRaw = routeExtra.terms_path || routeExtra.termsPath || [];
2656				const termsPath = Array.isArray(termsPathRaw) ? termsPathRaw : [];
2657				if ((routeType === 'cat' || routeType === 'tags') && termsPath.length) {
2658					const last = termsPath[termsPath.length - 1];
2659					if (last && typeof last === 'object') {
2660						termId = Number(last.term_id || last.termId || 0);
2661					}
2662				}
2663				if (Number.isFinite(termId) && termId > 0) {
2664					const taxonomy = routeType === 'cat' ? 'product_cat' : 'product_tag';
2665					href = `/wp-admin/term.php?taxonomy=${encodeURIComponent(taxonomy)}&tag_ID=${Math.trunc(termId)}&post_type=product`;
2666				}
2667			}
2668
2669			if (!Number.isFinite(termId) || termId <= 0 || !href) {
2670				block.style.display = 'none';
2671				return;
2672			}
2673
2674			idNode.textContent = String(Math.trunc(termId));
2675			linkNode.setAttribute('href', href);
2676			block.style.display = '';
2677		},
2678		setListNavigationLoading(isLoading = false) {
2679			const on = !!isLoading;
2680			document.documentElement.classList.toggle('scv3-list-loading', on);
2681			if (document.body) {
2682				document.body.classList.toggle('scv3-list-loading', on);
2683			}
2684		},
2685		ensurePagination() {
2686			if (!Render || !Render.Pagination) return;
2687			const totalPages = Page._state.totalPages || 1;
2688			Page.helpers.updateMainTextSpacing(totalPages);
2689			const $existing = $(Page._selectors.pagination).first();
2690			if (totalPages < 2) {
2691				if ($existing.length) {
2692					$existing.remove();
2693				}
2694				return;
2695			}
2696
2697			const currentPage = Page._state.uiPage || Page._state.currentPage || 1;
2698			const navigation = Page.helpers.resolveLandingNavigation();
2699			const paginationHtml = Render.Pagination.render({
2700				totalPages,
2701				currentPage,
2702				baseUrl: navigation.path,
2703				queryParams: navigation.query
2704			});
2705			if (!paginationHtml) return;
2706			if ($existing.length) {
2707				$existing.replaceWith(paginationHtml);
2708				Page.dynamicLisaners.pagination();
2709				return;
2710			}
2711
2712			const $loadMoreWrap = $('.scv3-load-more-wrap').first();
2713			if ($loadMoreWrap.length) {
2714				$loadMoreWrap.after(paginationHtml);
2715			} else {
2716				const $grid = $(Page._selectors.cards.productsBlock).first();
2717				if ($grid.length) {
2718					$grid.after(paginationHtml);
2719				}
2720			}
2721			Page.dynamicLisaners.pagination();
2722		},
2723		applySelectionToBlocks(blocks = [], priceRange = null) {
2724			const params = Page._state.urlParams || {};
2725			const selectedMap = {};
2726			Object.entries(params).forEach(([k, v]) => {
2727				if (Array.isArray(v)) {
2728					selectedMap[k] = v.map((x) => String(x));
2729				} else if (v !== undefined && v !== null) {
2730					selectedMap[k] = [String(v)];
2731				}
2732			});
2733
2734			const priceMeta = Page.helpers.normalizePriceRange(priceRange);
2735			blocks.forEach((block) => {
2736				if (!block || !block.slug) return;
2737				if (block.type === 'price') {
2738					const fromName = block.from_name || 'pa-price_from';
2739					const toName = block.to_name || 'pa-price_to';
2740					const hasFrom = selectedMap[fromName] && selectedMap[fromName][0] !== undefined;
2741					const hasTo = selectedMap[toName] && selectedMap[toName][0] !== undefined;
2742					const selectedFrom = hasFrom ? selectedMap[fromName][0] : priceMeta.selected_from;
2743					const selectedTo = hasTo ? selectedMap[toName][0] : priceMeta.selected_to;
2744					const phFrom = Page.helpers.formatPriceDisplay(priceMeta.min);
2745					const phTo = Page.helpers.formatPriceDisplay(priceMeta.max);
2746					const displayFrom = Page.helpers.formatPriceDisplay(selectedFrom);
2747					const displayTo = Page.helpers.formatPriceDisplay(selectedTo);
2748
2749					block.value_from = displayFrom || (selectedFrom !== null && selectedFrom !== undefined ? String(selectedFrom) : '');
2750					block.value_to = displayTo || (selectedTo !== null && selectedTo !== undefined ? String(selectedTo) : '');
2751					block.placeholder_from = phFrom || block.placeholder_from || 'От';
2752					block.placeholder_to = phTo || block.placeholder_to || 'До';
2753					return;
2754				}
2755
2756				const opts = Array.isArray(block.options) ? block.options : [];
2757				if (block.type === 'category_tree') {
2758					opts.forEach((opt) => {
2759						const termId = Number(opt.term_id || 0);
2760						if (!termId) return;
2761						if (Object.prototype.hasOwnProperty.call(Page._state.categoryTreeExpanded, termId)) {
2762							opt.expanded = !!Page._state.categoryTreeExpanded[termId];
2763						}
2764					});
2765				}
2766				opts.forEach((opt) => {
2767					const nameAttr =
2768						opt.name_attr ||
2769						opt.name ||
2770						block.input_name ||
2771						block.name ||
2772						`pa-${block.slug}[]`;
2773					const normalizedNameAttr = Page.helpers.normalizeFilterParamName(nameAttr);
2774					const values = selectedMap[normalizedNameAttr] || selectedMap[nameAttr] || [];
2775					const val = opt.value !== undefined ? String(opt.value) : '';
2776					const isSelected = val !== '' && values.includes(val);
2777					if (isSelected) {
2778						opt.checked = true;
2779						opt.is_active = true;
2780					}
2781				});
2782				block.options = opts;
2783			});
2784
2785			return blocks;
2786		},
2787		serializeUrlParams(params) {
2788			const usp = new URLSearchParams();
2789			Object.entries(Page.helpers.normalizeUrlParams(params)).forEach(([k, v]) => {
2790				if (Array.isArray(v)) {
2791					v.forEach((item) => {
2792						if (item !== null && item !== undefined && item !== '') usp.append(k, item);
2793					});
2794				} else if (v !== null && v !== undefined && v !== '') {
2795					usp.append(k, v);
2796				}
2797			});
2798			return usp;
2799		},
2800		normalizeFilterParamName(name) {
2801			const value = String(name || '');
2802			return /^pa-[a-z0-9_-]+\[\]$/i.test(value) ? value.slice(0, -2) : value;
2803		},
2804		stripLegacySeoTitlePlaceholder(value) {
2805			return String(value || '')
2806				.replaceAll('{SEO_TITLE}', '')
2807				.replace(/\s{2,}/g, ' ')
2808				.replace(/\s+([,.;:!?])/g, '$1')
2809				.trim();
2810		},
2811		normalizeUrlParams(params = {}) {
2812			const normalized = {};
2813			Object.entries(params || {}).forEach(([rawKey, rawValue]) => {
2814				const key = Page.helpers.normalizeFilterParamName(rawKey);
2815				const values = Array.isArray(rawValue) ? rawValue : [rawValue];
2816				values.forEach((value) => {
2817					if (value === null || value === undefined || value === '') return;
2818					if (normalized[key] === undefined) {
2819						normalized[key] = value;
2820					} else if (Array.isArray(normalized[key])) {
2821						if (!normalized[key].map(String).includes(String(value))) {
2822							normalized[key].push(value);
2823						}
2824					} else if (String(normalized[key]) !== String(value)) {
2825						normalized[key] = [normalized[key], value];
2826					}
2827				});
2828			});
2829			return normalized;
2830		},
2831		normalizeLandingContext(context) {
2832			if (!context || typeof context !== 'object') return null;
2833			const pages = Array.isArray(context.pages) ? context.pages : [];
2834			return {
2835				basePath: String(context.base_path || context.basePath || '').replace(/^\/+|\/+$/g, ''),
2836				pages,
2837				active: context.active && typeof context.active === 'object' ? context.active : null,
2838				selectedAttributes: context.selected_attributes && typeof context.selected_attributes === 'object'
2839					? context.selected_attributes
2840					: (context.selectedAttributes && typeof context.selectedAttributes === 'object'
2841						? context.selectedAttributes
2842						: {})
2843			};
2844		},
2845		landingAttributesKey(attributes = {}) {
2846			return Object.keys(attributes)
2847				.sort()
2848				.map((slug) => {
2849					const values = (Array.isArray(attributes[slug]) ? attributes[slug] : [attributes[slug]])
2850						.map((value) => Number(value))
2851						.filter((value) => Number.isFinite(value) && value > 0)
2852						.sort((a, b) => a - b);
2853					return values.length ? `${slug}=${values.join(',')}` : '';
2854				})
2855				.filter(Boolean)
2856				.join('&');
2857		},
2858		resolveLandingNavigation(params = Page._state.urlParams) {
2859			const context = Page._state.landingContext;
2860			const basePath = context?.basePath || Page._state.basePath || '';
2861			if (!context || !Array.isArray(context.pages) || !context.pages.length) {
2862				return { path: basePath, query: { ...(params || {}) }, landing: null };
2863			}
2864
2865			const attributes = {};
2866			const query = { ...(params || {}) };
2867			Object.entries(params || {}).forEach(([rawKey, rawValue]) => {
2868				const key = String(rawKey || '').replace(/\[\]$/, '');
2869				const match = key.match(/^pa-([a-z0-9_-]+)$/i);
2870				if (!match) {
2871					return;
2872				}
2873				const values = (Array.isArray(rawValue) ? rawValue : [rawValue])
2874					.flatMap((value) => String(value).split(','))
2875					.map((value) => Number(value))
2876					.filter((value) => Number.isFinite(value) && value > 0);
2877				if (!values.length) {
2878					return;
2879				}
2880				attributes[match[1]] = values;
2881			});
2882
2883			const landing = context.pages
2884				.slice()
2885				.sort((a, b) => Object.keys(b?.attributes || {}).length - Object.keys(a?.attributes || {}).length)
2886				.find((page) => {
2887					const pageAttributes = page?.attributes || {};
2888					return Object.entries(pageAttributes).every(([slug, values]) => {
2889						const selected = (Array.isArray(attributes[slug]) ? attributes[slug] : [attributes[slug]])
2890							.map((value) => Number(value))
2891							.filter((value) => Number.isFinite(value) && value > 0)
2892							.sort((a, b) => a - b);
2893						const expected = (Array.isArray(values) ? values : [values])
2894							.map((value) => Number(value))
2895							.filter((value) => Number.isFinite(value) && value > 0)
2896							.sort((a, b) => a - b);
2897						return expected.length > 0
2898							&& expected.length === selected.length
2899							&& expected.every((value, idx) => value === selected[idx]);
2900					});
2901				});
2902			if (!landing) {
2903				return { path: basePath, query, landing: null };
2904			}
2905			Object.keys(landing.attributes || {}).forEach((slug) => {
2906				delete query[`pa-${slug}`];
2907				delete query[`pa-${slug}[]`];
2908			});
2909			return {
2910				path: `${basePath}/${String(landing.url || '').replace(/^\/+|\/+$/g, '')}`,
2911				query,
2912				landing
2913			};
2914		},
2915		getRouteState() {
2916			const rawPath = (location.pathname || '').replace(/^\/+|\/+$/g, '');
2917			const matchPage = rawPath.match(/^(.*?)(?:\/page\/(\d+))?$/);
2918			let basePath = (matchPage && matchPage[1]) ? matchPage[1].replace(/\/+$/,'') : rawPath;
2919			const currentPage = matchPage && matchPage[2] ? Math.max(1, parseInt(matchPage[2], 10)) : 1;
2920
2921			// /cat/ без слага — это главная страница (отдаём все товары)
2922			if (!basePath || /^cat\/?$/.test(basePath)) {
2923				basePath = '';
2924			}
2925
2926			const urlParams = {};
2927			const usp = new URLSearchParams(location.search || '');
2928			usp.forEach((v, rawKey) => {
2929				if (v === null || v === undefined || v === '') return;
2930				const k = Page.helpers.normalizeFilterParamName(rawKey);
2931				const isCompactFilter = /^pa-[a-z0-9_-]+(?:\[\])?$/i.test(String(k || ''));
2932				if (isCompactFilter && String(v).includes(',')) {
2933					const items = String(v)
2934						.split(',')
2935						.map((item) => item.trim())
2936						.filter((item) => item !== '');
2937					if (!items.length) return;
2938					if (urlParams[k] !== undefined) {
2939						if (Array.isArray(urlParams[k])) {
2940							urlParams[k].push(...items);
2941						} else {
2942							urlParams[k] = [urlParams[k], ...items];
2943						}
2944					} else {
2945						urlParams[k] = items;
2946					}
2947					return;
2948				}
2949				if (urlParams[k] !== undefined) {
2950					if (Array.isArray(urlParams[k])) {
2951						urlParams[k].push(v);
2952					} else {
2953						urlParams[k] = [urlParams[k], v];
2954					}
2955				} else {
2956					urlParams[k] = v;
2957				}
2958			});
2959			const initialLanding = Page.helpers.normalizeLandingContext(
2960				Page._state?.landingContext || window.scv3LandingContext || null
2961			);
2962			const pathWithoutPage = basePath;
2963			const matchedPage = initialLanding?.pages?.find((page) => {
2964				const landingPath = `${initialLanding.basePath}/${String(page?.url || '').replace(/^\/+|\/+$/g, '')}`;
2965				return landingPath === pathWithoutPage;
2966			});
2967			if (matchedPage) {
2968				Object.entries(matchedPage.attributes || {}).forEach(([slug, values]) => {
2969					if (urlParams[`pa-${slug}`] !== undefined || urlParams[`pa-${slug}[]`] !== undefined) {
2970						return;
2971					}
2972					const normalized = Array.isArray(values) ? values.map(String) : [String(values)];
2973					urlParams[`pa-${slug}`] = normalized.length === 1 ? normalized[0] : normalized;
2974				});
2975				basePath = initialLanding.basePath || basePath;
2976			}
2977
2978			return {
2979				currentPage,
2980				basePath,
2981				urlParams,
2982				perPage: 24,
2983				total: 0,
2984				totalPages: 1
2985			};
2986		},
2987		fetchAndRender({ page = 1, append = false, withFacets = true, history = 'replace', updateUrl = true, updatePagination = true } = {}) {
2988			const navigation = Page.helpers.resolveLandingNavigation();
2989			const pathForRequest = navigation.path
2990				? `${navigation.path}${page > 1 ? '/page/' + page : ''}`
2991				: (page > 1 ? `page/${page}` : '');
2992			Page.helpers.setListNavigationLoading(true);
2993
2994			const $grid = $(Page._selectors.cards.productsBlock);
2995			if ($grid.length) {
2996				if (append) {
2997					if (!$grid.find('[data-scv3-loader]').length) {
2998						$grid.append(Render.Loading.spinner());
2999					}
3000				} else {
3001					$grid.html(Render.Loading.spinner());
3002				}
3003			}
3004
3005			Back.getProducts({
3006				path: pathForRequest,
3007				with_facets: withFacets ? 1 : 0,
3008				...navigation.query
3009			}).then(res => {
3010				if (res.isErr) {
3011					if ($grid.length) {
3012						$grid.find('[data-scv3-loader]').remove();
3013					}
3014					alert(res.err);
3015					return;
3016				}
3017
3018				const data = res.data || {};
3019				if (data.landing_context) {
3020					Page._state.landingContext = Page.helpers.normalizeLandingContext(data.landing_context);
3021				}
3022				Page.helpers.cacheProducts(data.items || [], { reset: !append });
3023				const filterBlocks = Page.helpers.buildFilterBlocksFromResponse(data);
3024				if (filterBlocks.length) {
3025					const focusState = Page.helpers.captureFilterFocus();
3026					const filtersHtml = Render.Filters.render(filterBlocks);
3027				if (filtersHtml) {
3028					let $filters = $(Page._selectors.filters.categories).first().closest('.scv3-filters');
3029					if (!$filters.length) {
3030						$filters = $('.scv3-filters').first();
3031					}
3032					const filtersElement = $filters.length ? $filters.get(0) : null;
3033					const animatedUpdate = filtersElement
3034						? Page.helpers.updateFilterBlocksWithAnimation(filtersElement, filtersHtml)
3035						: false;
3036					if (!animatedUpdate && $filters.length) {
3037						$filters.replaceWith(filtersHtml);
3038					}
3039					Page.helpers.syncFilterInputsFromUrl();
3040					scv3InitFilterBlocks();
3041					scv3InitFilterColors();
3042					Page.helpers.initCategoryTrees();
3043					Page.dynamicLisaners.categories();
3044						Page.dynamicLisaners.categoriesTree();
3045						Page.dynamicLisaners.options();
3046						Page.dynamicLisaners.price();
3047						if (typeof window.scv3UpdateMobileFilterPills === 'function') {
3048							window.scv3UpdateMobileFilterPills();
3049						}
3050						Page.helpers.restoreFilterFocus(focusState);
3051					}
3052				}
3053
3054				if (Render?.Helpers?.isProductUrlDebugEnabled && Render.Helpers.isProductUrlDebugEnabled()) {
3055					try {
3056						console.warn('[scv3][product-url] api response items', {
3057							page,
3058							append,
3059							basePath: Page._state.basePath,
3060							urlParams: Page._state.urlParams,
3061							items: data.items || []
3062						});
3063					} catch (e) {}
3064				}
3065
3066				const productsHtml = Render.Cards.cards(data.items || []);
3067				if ($grid.length) {
3068					$grid.find('[data-scv3-loader]').remove();
3069					if (append) {
3070						$grid.append(productsHtml);
3071					} else {
3072						$grid.html(productsHtml);
3073					}
3074					Page.dynamicLisaners.cardVariants();
3075					Page.dynamicLisaners.pagination();
3076					Page.dynamicLisaners.categories();
3077					Page.dynamicLisaners.categoriesTree();
3078					Page.dynamicLisaners.options();
3079					Page.dynamicLisaners.price();
3080					Page.dynamicLisaners.sorting();
3081					if (typeof window.scv3InitProductSliders === 'function') {
3082						window.scv3InitProductSliders();
3083					}
3084				}
3085
3086				if (data) {
3087					if (Page.helpers.isNumeric(data.per_page)) {
3088						Page._state.perPage = Number(data.per_page);
3089					}
3090					if (Page.helpers.isNumeric(data.total)) {
3091						Page._state.total = Number(data.total);
3092					}
3093					if (Array.isArray(data.categories)) {
3094						Page._state.categories = data.categories;
3095					}
3096					const activeSlug = data.active_category_slug || data.activeCategorySlug;
3097					if (activeSlug !== undefined && activeSlug !== null) {
3098						Page._state.activeCategorySlug = String(activeSlug || '');
3099					}
3100					Page._state.totalPages = Math.max(1, Math.ceil(Page._state.total / Page._state.perPage));
3101					Page.helpers.setHeaderCount(Page._state.total);
3102				}
3103
3104				Page._state.currentPage = page;
3105				if (!append) {
3106					Page._state.uiPage = page;
3107				}
3108				if (!append) {
3109					Page.helpers.updateHeader(Page.helpers.buildHeaderData(data), Page._state.total);
3110					Page.helpers.updateMeta(Page.helpers.buildMetaData(data));
3111				}
3112				if (updateUrl) {
3113					Page.helpers.updateUrl(page, history);
3114				}
3115				Page.helpers.updateTermDescription(data.term_description_html || '');
3116				Page.helpers.updateTermAdminInfo(data);
3117
3118				const itemsCount = Array.isArray(data.items) ? data.items.length : 0;
3119				const shouldShowLoadMore = Page._state.totalPages > page && itemsCount >= Page._state.perPage;
3120				const nextLoadMoreUrl = shouldShowLoadMore
3121					? Render.Helpers.buildPageUrl(
3122						Page.helpers.resolveLandingNavigation().path,
3123						page + 1,
3124						Page.helpers.resolveLandingNavigation().query
3125					)
3126					: '';
3127				let $loadMoreWrap = $('.scv3-load-more-wrap').first();
3128				if (shouldShowLoadMore) {
3129					if (!$loadMoreWrap.length) {
3130						const $grid = $(Page._selectors.cards.productsBlock).first();
3131						if ($grid.length) {
3132							$loadMoreWrap = $(`
3133								<div class="scv3-load-more-wrap">
3134									<a class="scv3-button scv3-load-more" href="#">Показать еще</a>
3135								</div>
3136							`);
3137							$grid.after($loadMoreWrap);
3138							Page.staticLisaners();
3139						}
3140					}
3141					if ($loadMoreWrap.length) {
3142						const $btn = $loadMoreWrap.find(Page._selectors.loadMoreBtn).first();
3143						if ($btn.length && nextLoadMoreUrl) {
3144							$btn.attr('href', nextLoadMoreUrl);
3145						}
3146						$loadMoreWrap.show();
3147					}
3148				} else if ($loadMoreWrap.length) {
3149					$loadMoreWrap.hide();
3150				}
3151
3152				if (updatePagination) {
3153					const paginationHtml = Render.Pagination.render({
3154						totalPages: Page._state.totalPages,
3155						currentPage: page,
3156						baseUrl: Page.helpers.resolveLandingNavigation().path,
3157						queryParams: Page.helpers.resolveLandingNavigation().query
3158					});
3159					if (paginationHtml) {
3160						const $pagination = $(Page._selectors.pagination).first();
3161						if ($pagination.length) {
3162							$pagination.replaceWith(paginationHtml);
3163							Page.dynamicLisaners.pagination();
3164						} else {
3165							let $loadMoreWrap = $('.scv3-load-more-wrap').first();
3166							if ($loadMoreWrap.length) {
3167								$loadMoreWrap.after(paginationHtml);
3168							} else {
3169								const $grid = $(Page._selectors.cards.productsBlock).first();
3170								if ($grid.length) {
3171									$grid.after(paginationHtml);
3172								}
3173							}
3174							Page.dynamicLisaners.pagination();
3175						}
3176					} else {
3177						const $pagination = $(Page._selectors.pagination).first();
3178						if ($pagination.length) {
3179							$pagination.remove();
3180						}
3181					}
3182				} else {
3183					Page.helpers.ensurePagination();
3184				}
3185				Page.helpers.updateMainTextSpacing(Page._state.totalPages);
3186			}).finally(() => {
3187				Page.helpers.setListNavigationLoading(false);
3188			});
3189		},
3190		updateUrl(page, mode = 'replace') {
3191			const navigation = Page.helpers.resolveLandingNavigation();
3192			const newUrl = Render.Helpers.buildPageUrl(
3193				navigation.path,
3194				page,
3195				navigation.query
3196			);
3197			const currentUrl = location.pathname + location.search;
3198			if (newUrl === currentUrl) {
3199				return;
3200			}
3201			if (typeof history !== 'undefined') {
3202				if (mode === 'push' && typeof history.pushState === 'function') {
3203					history.pushState({ scv3: true }, '', newUrl);
3204				} else if (typeof history.replaceState === 'function') {
3205					history.replaceState({ scv3: true }, '', newUrl);
3206				}
3207			}
3208		},
3209		setHeaderCount(count) {
3210			let el = $(Page._selectors.header_p_count);
3211
3212			if(el.length === 0)
3213				return;
3214
3215			const text = (Render && Render.Helpers && Render.Helpers.productsCountText)
3216				? Render.Helpers.productsCountText(count)
3217				: `${count} товаров`;
3218			$(el[0]).text(text);
3219		},
3220		closeMobileFilters() {
3221			const mq = window.matchMedia ? window.matchMedia('(max-width: 768px)') : null;
3222			if (mq && !mq.matches) return;
3223			if (typeof window.scv3CloseFilterAllSheet === 'function') {
3224				window.scv3CloseFilterAllSheet();
3225			}
3226			if (typeof window.scv3CloseFilterSheet === 'function') {
3227				window.scv3CloseFilterSheet();
3228			}
3229		},
3230		updateSliderVisibility() {
3231			/*
3232			const slider = document.querySelector('.scv3-category-slider');
3233			if (!slider || typeof Page === 'undefined') return;
3234			const basePath = Page?._state?.basePath || '';
3235			const hasSearch = !!(Page?._state?.urlParams?.s);
3236			const isCatalog = !hasSearch && (!basePath || basePath === 'shop' || basePath.indexOf('cat/') === 0);
3237			slider.style.display = isCatalog ? '' : 'none';
3238			 */
3239		},
3240		isNumeric(str) {
3241			if (str === null || str === undefined) return false;
3242			if (typeof str === "number") return Number.isFinite(str);
3243			if (typeof str !== "string") return false;
3244			const trimmed = str.trim();
3245			if (!trimmed) return false;
3246			return !isNaN(trimmed) && isFinite(Number(trimmed));
3247		}
3248	},
3249};
3250
3251Page.preload();
3252
3253(function () {
3254	const sheet = document.querySelector('[data-scv3-sort-sheet]');
3255	const triggers = document.querySelectorAll('[data-scv3-sort-trigger]');
3256	if (!sheet || !triggers.length || typeof Page === 'undefined') return;
3257
3258	const closeEls = sheet.querySelectorAll('[data-scv3-sort-close]');
3259	const applyBtn = sheet.querySelector('[data-scv3-sort-apply]');
3260	const inputs = Array.from(sheet.querySelectorAll('input[name="scv3-sort-mobile"]'));
3261	if (!inputs.length) return;
3262
3263	let currentSort = '';
3264
3265	const normalizeSort = (val) => {
3266		if (!val) return '';
3267		const v = String(val).trim();
3268		if (v === 'popular') return 'popularity';
3269		return v;
3270	};
3271
3272	const getCurrentSort = () => {
3273		const stateSort = normalizeSort(Page?._state?.urlParams?.sort || '');
3274		if (stateSort) return stateSort;
3275		const active = document.querySelector('.scv3-sort-dropdown__option.scv3-is-active');
3276		if (active) {
3277			const dataSort = active.getAttribute('data-sort') || active.getAttribute('data-sort-key') || '';
3278			return normalizeSort(dataSort || active.textContent || '');
3279		}
3280		return 'popularity';
3281	};
3282
3283	const setState = (state) => {
3284		sheet.setAttribute('data-scv3-sort-state', state);
3285	};
3286
3287	const syncInputs = (value) => {
3288		inputs.forEach((input) => {
3289			input.checked = input.value === value;
3290		});
3291	};
3292
3293	const openSheet = () => {
3294		currentSort = getCurrentSort();
3295		syncInputs(currentSort);
3296		setState('idle');
3297		sheet.classList.add('scv3-is-open');
3298		sheet.setAttribute('aria-hidden', 'false');
3299		document.documentElement.classList.add('scv3-sort-lock');
3300		document.body.classList.add('scv3-sort-lock');
3301	};
3302
3303	const closeSheet = (restore = true) => {
3304		sheet.classList.remove('scv3-is-open');
3305		sheet.setAttribute('aria-hidden', 'true');
3306		document.documentElement.classList.remove('scv3-sort-lock');
3307		document.body.classList.remove('scv3-sort-lock');
3308		if (restore) {
3309			currentSort = getCurrentSort();
3310			syncInputs(currentSort);
3311			setState('idle');
3312		}
3313	};
3314
3315	const getSelected = () => {
3316		const checked = inputs.find((input) => input.checked);
3317		return checked ? normalizeSort(checked.value) : '';
3318	};
3319
3320	triggers.forEach((btn) => {
3321		btn.addEventListener('click', (e) => {
3322			e.preventDefault();
3323			openSheet();
3324		});
3325	});
3326
3327	closeEls.forEach((el) => {
3328		el.addEventListener('click', (e) => {
3329			e.preventDefault();
3330			closeSheet(true);
3331		});
3332	});
3333
3334	inputs.forEach((input) => {
3335		input.addEventListener('change', () => {
3336			const selected = getSelected();
3337			setState(selected && selected !== currentSort ? 'changed' : 'idle');
3338		});
3339	});
3340
3341	if (applyBtn) {
3342		applyBtn.addEventListener('click', (e) => {
3343			e.preventDefault();
3344			const selected = getSelected();
3345			if (!selected || selected === currentSort) {
3346				closeSheet(true);
3347				return;
3348			}
3349			Page._state.urlParams = { ...Page._state.urlParams, sort: selected };
3350			Page._state.currentPage = 1;
3351			Page.helpers.updateUrl(1);
3352			Page.helpers.fetchAndRender({
3353				page: 1,
3354				append: false,
3355				withFacets: false
3356			});
3357			Page.helpers.closeMobileFilters();
3358			currentSort = selected;
3359			closeSheet(false);
3360		});
3361	}
3362
3363	document.addEventListener('keydown', (e) => {
3364		if (e.key === 'Escape' && sheet.classList.contains('scv3-is-open')) {
3365			closeSheet(true);
3366		}
3367	});
3368})();
3369
3370(function () {
3371	const sheet = document.querySelector('[data-scv3-filter-sheet]');
3372	const triggers = document.querySelectorAll('[data-scv3-filter-trigger]');
3373	const body = sheet ? sheet.querySelector('[data-scv3-filter-body]') : null;
3374	if (!sheet || !body || !triggers.length || typeof Page === 'undefined') return;
3375
3376	const closeEls = sheet.querySelectorAll('[data-scv3-filter-close]');
3377	const resetBtn = sheet.querySelector('[data-scv3-filter-reset]');
3378	const applyBtn = sheet.querySelector('[data-scv3-filter-apply]');
3379	let anchorParent = null;
3380	let anchorNext = null;
3381	let editSessionActive = false;
3382
3383	const escSelector = (value) => {
3384		if (window.CSS && typeof window.CSS.escape === 'function') {
3385			return window.CSS.escape(value);
3386		}
3387		return String(value || '').replace(/"/g, '\\"');
3388	};
3389
3390	const getFilters = () => document.querySelector('.scv3-filters');
3391
3392	const clearInputs = (filters) => {
3393		if (!filters) return;
3394		filters.querySelectorAll('.scv3-filter-option input[type="checkbox"], .scv3-filter-option input[type="radio"]').forEach((input) => {
3395			input.checked = false;
3396			input.indeterminate = false;
3397			const label = input.closest('.scv3-filter-option');
3398			if (label) {
3399				label.classList.remove('scv3-is-checked');
3400			}
3401		});
3402		filters.querySelectorAll('.scv3-filter-list--price input').forEach((input) => {
3403			input.value = '';
3404		});
3405	};
3406
3407	const ensureCategoryMobileTrigger = (filters) => {
3408		if (!filters) return;
3409		const block = filters.querySelector('[data-scv3-filter-slug="categories"]');
3410		if (!block) return;
3411
3412		const toggle = block.querySelector('[data-scv3-filter-toggle]');
3413		if (toggle) {
3414			toggle.setAttribute('data-scv3-filter-mobile-open', '1');
3415		}
3416
3417		let more = block.querySelector('[data-scv3-filter-more]');
3418		if (!more) {
3419			more = document.createElement('button');
3420			more.type = 'button';
3421			more.className = 'scv3-filter-more';
3422			more.setAttribute('data-scv3-filter-more', '');
3423			more.setAttribute('data-label-expand', 'Все');
3424			more.setAttribute('data-label-collapse', 'Все');
3425			more.innerHTML = '<span data-scv3-filter-more-label>Все</span>';
3426			block.appendChild(more);
3427		}
3428		more.setAttribute('data-scv3-filter-mobile-open', '1');
3429		block.classList.add('scv3-filter-block--mobile-link');
3430
3431		const content = block.querySelector('[data-scv3-filter-content]');
3432		if (content) {
3433			content.style.display = 'none';
3434		}
3435		block.classList.remove('scv3-is-open');
3436	};
3437
3438	const openSheet = () => {
3439		const filters = getFilters();
3440		if (!filters) return;
3441		if (!anchorParent) {
3442			anchorParent = filters.parentElement;
3443			anchorNext = filters.nextSibling;
3444		}
3445		body.appendChild(filters);
3446		if (typeof scv3InitFilterBlocks === 'function') {
3447			scv3InitFilterBlocks();
3448		}
3449		if (typeof scv3InitFilterColors === 'function') {
3450			scv3InitFilterColors();
3451		}
3452		ensureCategoryMobileTrigger(filters);
3453		if (window.matchMedia('(max-width: 768px)').matches && Page.helpers && typeof Page.helpers.beginFilterEdit === 'function') {
3454			Page.helpers.beginFilterEdit();
3455			editSessionActive = true;
3456		} else {
3457			editSessionActive = false;
3458		}
3459		sheet.classList.add('scv3-is-open');
3460		sheet.setAttribute('aria-hidden', 'false');
3461		document.documentElement.classList.add('scv3-filter-lock');
3462		document.body.classList.add('scv3-filter-lock');
3463	};
3464
3465	const closeSheet = (action = 'cancel') => {
3466		const filters = getFilters();
3467		if (editSessionActive && Page.helpers && typeof Page.helpers.isFilterEditMode === 'function') {
3468			if (Page.helpers.isFilterEditMode()) {
3469				if (action === 'apply' && typeof Page.helpers.commitFilterEdit === 'function') {
3470					Page.helpers.commitFilterEdit();
3471				} else if (typeof Page.helpers.cancelFilterEdit === 'function') {
3472					Page.helpers.cancelFilterEdit();
3473				}
3474			}
3475			editSessionActive = false;
3476		}
3477		if (filters && anchorParent) {
3478			if (anchorNext && anchorNext.parentNode === anchorParent) {
3479				anchorParent.insertBefore(filters, anchorNext);
3480			} else {
3481				anchorParent.appendChild(filters);
3482			}
3483		}
3484		sheet.classList.remove('scv3-is-open');
3485		sheet.setAttribute('aria-hidden', 'true');
3486		document.documentElement.classList.remove('scv3-filter-lock');
3487		document.body.classList.remove('scv3-filter-lock');
3488	};
3489
3490	const scrollToSlug = (slug) => {
3491		if (!slug) return;
3492		const target = body.querySelector(`[data-scv3-filter-slug="${escSelector(slug)}"]`);
3493		if (!target) return;
3494		const top = Math.max(0, target.offsetTop - 12);
3495		body.scrollTo({ top, behavior: 'smooth' });
3496	};
3497
3498	const resetFilters = () => {
3499		const filters = getFilters();
3500		if (!filters) return;
3501		clearInputs(filters);
3502		const paramsSource = Page.helpers.isFilterEditMode && Page.helpers.isFilterEditMode()
3503			? Page.helpers.getFilterEditParams()
3504			: Page._state.urlParams;
3505		const params = { ...(paramsSource || {}) };
3506		const names = new Set();
3507		filters.querySelectorAll('input[name]').forEach((input) => {
3508			let name = input.getAttribute('name') || '';
3509			if (!name) return;
3510			if (name.endsWith('[]')) {
3511				name = name.slice(0, -2);
3512			}
3513			names.add(name);
3514		});
3515		names.forEach((name) => {
3516			delete params[name];
3517			delete params[`${name}[]`];
3518		});
3519		if (Page.helpers.isFilterEditMode && Page.helpers.isFilterEditMode()) {
3520			Page.helpers.setFilterEditParams(params);
3521			if (typeof window.scv3UpdateMobileFilterPills === 'function') {
3522				window.scv3UpdateMobileFilterPills();
3523			}
3524			return;
3525		}
3526		Page._state.urlParams = params;
3527		Page._state.currentPage = 1;
3528		Page.helpers.updateUrl(1);
3529		Page.mainBlocks.runFilters();
3530	};
3531
3532	window.scv3OpenFilterSheet = (slug = '') => {
3533		openSheet();
3534		if (slug) {
3535			setTimeout(() => scrollToSlug(slug), 50);
3536		}
3537	};
3538
3539	window.scv3CloseFilterSheet = () => {
3540		closeSheet();
3541	};
3542
3543	triggers.forEach((btn) => {
3544		btn.addEventListener('click', (e) => {
3545			e.preventDefault();
3546			openSheet();
3547		});
3548	});
3549
3550	closeEls.forEach((el) => {
3551		el.addEventListener('click', (e) => {
3552			e.preventDefault();
3553			closeSheet();
3554		});
3555	});
3556
3557	if (resetBtn) {
3558		resetBtn.addEventListener('click', (e) => {
3559			e.preventDefault();
3560			resetFilters();
3561		});
3562	}
3563
3564	if (applyBtn) {
3565		applyBtn.addEventListener('click', (e) => {
3566			e.preventDefault();
3567			closeSheet('apply');
3568		});
3569	}
3570
3571	body.addEventListener('click', (e) => {
3572		const trigger = e.target.closest('[data-scv3-filter-mobile-open]');
3573		if (!trigger) return;
3574		const block = trigger.closest('.scv3-filter-block');
3575		if (!block) return;
3576		e.preventDefault();
3577		e.stopPropagation();
3578		if (typeof window.scv3OpenFilterAllSheet === 'function') {
3579			window.scv3OpenFilterAllSheet('', { block, fromFilters: true });
3580		}
3581	});
3582
3583	document.addEventListener('keydown', (e) => {
3584		if (e.key === 'Escape' && sheet.classList.contains('scv3-is-open')) {
3585			closeSheet('cancel');
3586		}
3587	});
3588})();
3589
3590(function () {
3591	const sheet = document.querySelector('[data-scv3-filter-all-sheet]');
3592	const body = sheet ? sheet.querySelector('[data-scv3-filter-all-body]') : null;
3593	const titleEl = sheet ? sheet.querySelector('[data-scv3-filter-all-title]') : null;
3594	if (!sheet || !body || typeof Page === 'undefined') return;
3595
3596	const filterSheet = document.querySelector('[data-scv3-filter-sheet]');
3597	const closeEls = sheet.querySelectorAll('[data-scv3-filter-all-close]');
3598	const backBtn = sheet.querySelector('[data-scv3-filter-all-back]');
3599	const applyBtn = sheet.querySelector('[data-scv3-filter-all-apply]');
3600	let currentBlock = null;
3601	let anchorParent = null;
3602	let anchorNext = null;
3603	let prevOpen = false;
3604	let prevDisplay = '';
3605	let expandedByScript = false;
3606	let moreEl = null;
3607	let ignoreMoreClick = false;
3608	let editSessionActive = false;
3609
3610	const isMobile = () => window.matchMedia('(max-width: 768px)').matches;
3611
3612	const escSelector = (value) => {
3613		if (window.CSS && typeof window.CSS.escape === 'function') {
3614			return window.CSS.escape(value);
3615		}
3616		return String(value || '').replace(/"/g, '\\"');
3617	};
3618
3619	const getLabelText = (el) => {
3620		if (!el) return '';
3621		const clone = el.cloneNode(true);
3622		clone.querySelectorAll('.scv3-filter-count').forEach((node) => node.remove());
3623		return (clone.textContent || '').replace(/\s+/g, ' ').trim();
3624	};
3625
3626	const getBlockBySlug = (slug) => {
3627		if (!slug) return null;
3628		return document.querySelector(`[data-scv3-filter-slug="${escSelector(slug)}"]`);
3629	};
3630
3631	const ensureExpanded = (block) => {
3632		expandedByScript = false;
3633		moreEl = block.querySelector('[data-scv3-filter-more]');
3634		if (!moreEl) return;
3635		if (!moreEl.classList.contains('scv3-filter-more--expanded')) {
3636			expandedByScript = true;
3637			ignoreMoreClick = true;
3638			moreEl.click();
3639		}
3640	};
3641
3642	const openSheet = (block, fromFilters) => {
3643		if (!block) return;
3644		currentBlock = block;
3645		anchorParent = block.parentElement;
3646		anchorNext = block.nextSibling;
3647		prevOpen = block.classList.contains('scv3-is-open');
3648		const content = block.querySelector('[data-scv3-filter-content]');
3649		prevDisplay = content ? content.style.display : '';
3650
3651		ensureExpanded(block);
3652
3653		const label = getLabelText(block.querySelector('.scv3-filter-block__label'));
3654		if (titleEl) {
3655			titleEl.textContent = label || 'Фильтр';
3656		}
3657
3658		body.appendChild(block);
3659		block.classList.add('scv3-is-open');
3660		if (content) {
3661			content.style.display = 'grid';
3662		}
3663
3664		if (isMobile() && Page.helpers && typeof Page.helpers.beginFilterEdit === 'function') {
3665			Page.helpers.beginFilterEdit();
3666			editSessionActive = true;
3667		} else {
3668			editSessionActive = false;
3669		}
3670
3671		sheet.classList.toggle('scv3-filter-all-sheet--full', !!fromFilters);
3672		sheet.classList.add('scv3-is-open');
3673		sheet.setAttribute('aria-hidden', 'false');
3674		document.documentElement.classList.add('scv3-filter-all-lock');
3675		document.body.classList.add('scv3-filter-all-lock');
3676
3677		const searchInput = body.querySelector('[data-scv3-filter-search]');
3678		if (searchInput) {
3679			searchInput.focus();
3680		}
3681	};
3682
3683	const closeSheet = (action = 'cancel') => {
3684		if (currentBlock && anchorParent) {
3685			const content = currentBlock.querySelector('[data-scv3-filter-content]');
3686			if (content) {
3687				content.style.display = prevDisplay || '';
3688			}
3689			if (!prevOpen) {
3690				currentBlock.classList.remove('scv3-is-open');
3691			}
3692			if (anchorNext && anchorNext.parentNode === anchorParent) {
3693				anchorParent.insertBefore(currentBlock, anchorNext);
3694			} else {
3695				anchorParent.appendChild(currentBlock);
3696			}
3697		}
3698
3699		if (moreEl && expandedByScript) {
3700			ignoreMoreClick = true;
3701			moreEl.click();
3702		}
3703
3704		if (editSessionActive && Page.helpers && typeof Page.helpers.isFilterEditMode === 'function') {
3705			if (Page.helpers.isFilterEditMode()) {
3706				if (action === 'apply' && typeof Page.helpers.commitFilterEdit === 'function') {
3707					Page.helpers.commitFilterEdit();
3708				} else if (typeof Page.helpers.cancelFilterEdit === 'function') {
3709					Page.helpers.cancelFilterEdit();
3710				}
3711			}
3712			editSessionActive = false;
3713		}
3714
3715		currentBlock = null;
3716		anchorParent = null;
3717		anchorNext = null;
3718		prevOpen = false;
3719		prevDisplay = '';
3720		expandedByScript = false;
3721		moreEl = null;
3722
3723		sheet.classList.remove('scv3-is-open', 'scv3-filter-all-sheet--full');
3724		sheet.setAttribute('aria-hidden', 'true');
3725		document.documentElement.classList.remove('scv3-filter-all-lock');
3726		document.body.classList.remove('scv3-filter-all-lock');
3727	};
3728
3729	window.scv3OpenFilterAllSheet = (slug = '', options = {}) => {
3730		const block = options.block || getBlockBySlug(slug);
3731		if (!block) return;
3732		const fromFilters = typeof options.fromFilters === 'boolean'
3733			? options.fromFilters
3734			: !!(filterSheet && filterSheet.classList.contains('scv3-is-open') && block.closest('[data-scv3-filter-sheet]'));
3735		openSheet(block, fromFilters);
3736	};
3737
3738	window.scv3CloseFilterAllSheet = () => {
3739		closeSheet();
3740	};
3741
3742	document.addEventListener('click', (e) => {
3743		const more = e.target.closest('[data-scv3-filter-more]');
3744		if (!more) return;
3745		if (!isMobile()) return;
3746		if (ignoreMoreClick) {
3747			ignoreMoreClick = false;
3748			return;
3749		}
3750		const block = more.closest('.scv3-filter-block');
3751		if (!block) return;
3752		const fromFilters = !!(filterSheet && filterSheet.classList.contains('scv3-is-open')) ||
3753			!!more.closest('[data-scv3-filter-sheet]');
3754		openSheet(block, fromFilters);
3755	});
3756
3757	closeEls.forEach((el) => {
3758		el.addEventListener('click', (e) => {
3759			e.preventDefault();
3760			if (sheet.classList.contains('scv3-filter-all-sheet--full') &&
3761				el.classList.contains('scv3-filter-all-sheet__overlay')) {
3762				return;
3763			}
3764			closeSheet('cancel');
3765		});
3766	});
3767
3768	if (backBtn) {
3769		backBtn.addEventListener('click', (e) => {
3770			e.preventDefault();
3771			closeSheet('apply');
3772		});
3773	}
3774
3775	if (applyBtn) {
3776		applyBtn.addEventListener('click', (e) => {
3777			e.preventDefault();
3778			closeSheet('apply');
3779		});
3780	}
3781
3782	document.addEventListener('keydown', (e) => {
3783		if (e.key === 'Escape' && sheet.classList.contains('scv3-is-open')) {
3784			closeSheet('cancel');
3785		}
3786	});
3787})();
3788
3789(function () {
3790	const roots = Array.from(document.querySelectorAll('[data-scv3-filter-pills]'));
3791	if (!roots.length) return;
3792
3793	const escSelector = (value) => {
3794		if (window.CSS && typeof window.CSS.escape === 'function') {
3795			return window.CSS.escape(value);
3796		}
3797		return String(value || '').replace(/"/g, '\\"');
3798	};
3799
3800	const getFilters = () => document.querySelector('.scv3-filters');
3801
3802	const getLabelText = (el) => {
3803		if (!el) return '';
3804		const clone = el.cloneNode(true);
3805		clone.querySelectorAll('.scv3-filter-count').forEach((node) => node.remove());
3806		return (clone.textContent || '').replace(/\s+/g, ' ').trim();
3807	};
3808
3809	const buildBlockPills = (filters, blocksContainer) => {
3810		if (!blocksContainer) return;
3811		blocksContainer.innerHTML = '';
3812		const blocks = Array.from(filters.querySelectorAll('.scv3-filter-block'));
3813		blocks.forEach((block) => {
3814			const slug = (block.dataset.scv3FilterSlug || '').trim();
3815			const isPriceBlock = slug === 'price' || slug === 'cena';
3816			let label = getLabelText(block.querySelector('.scv3-filter-block__label'));
3817			if (isPriceBlock) {
3818				label = 'Цена';
3819			}
3820			if (!label) return;
3821			const btn = document.createElement('button');
3822			btn.type = 'button';
3823			btn.className = 'scv3-filter-pill scv3-filter-pill--block';
3824			btn.textContent = label;
3825			if (slug) {
3826				btn.dataset.scv3FilterTarget = slug;
3827			}
3828			blocksContainer.appendChild(btn);
3829		});
3830	};
3831
3832	const buildActivePills = (filters, activeContainer) => {
3833		if (!activeContainer) return 0;
3834		activeContainer.innerHTML = '';
3835		let count = 0;
3836
3837		const appendActive = (label, data = {}) => {
3838			const btn = document.createElement('button');
3839			btn.type = 'button';
3840			btn.className = 'scv3-filter-pill scv3-filter-pill--active';
3841			btn.dataset.scv3FilterRemove = '1';
3842			Object.entries(data).forEach(([key, val]) => {
3843				btn.dataset[key] = val;
3844			});
3845			btn.appendChild(document.createTextNode(label));
3846			const close = document.createElement('span');
3847			close.className = 'scv3-filter-pill__close scv3-filter-icon material-symbols-rounded';
3848			close.setAttribute('aria-hidden', 'true');
3849			close.textContent = 'close';
3850			btn.appendChild(close);
3851			activeContainer.appendChild(btn);
3852			count += 1;
3853		};
3854
3855		const appendCategoryTreeGroups = () => {
3856			const tree = filters.querySelector('[data-scv3-filter-slug="categories"] [data-scv3-category-tree]') ||
3857				filters.querySelector('[data-scv3-category-tree]');
3858			if (!tree) return;
3859			const rows = Array.from(tree.querySelectorAll('[data-scv3-cat-row]'));
3860			if (!rows.length) return;
3861			const byTerm = new Map();
3862			rows.forEach((row) => {
3863				const termId = Number(row.getAttribute('data-scv3-cat-term-id') || 0);
3864				if (termId > 0) {
3865					byTerm.set(termId, row);
3866				}
3867			});
3868			const groups = new Map();
3869			rows.forEach((row) => {
3870				const input = row.querySelector('input[type="checkbox"][data-scv3-cat-checkbox]');
3871				if (!input || !input.checked) return;
3872				let topRow = row;
3873				let parentId = Number(row.getAttribute('data-scv3-cat-parent-term-id') || 0);
3874				let guard = 0;
3875				while (parentId > 0 && guard < 30) {
3876					guard += 1;
3877					const parentRow = byTerm.get(parentId);
3878					if (!parentRow) break;
3879					topRow = parentRow;
3880					parentId = Number(parentRow.getAttribute('data-scv3-cat-parent-term-id') || 0);
3881				}
3882				const topTermId = Number(topRow.getAttribute('data-scv3-cat-term-id') || 0);
3883				if (!topTermId) return;
3884				const topLabel = getLabelText(topRow.querySelector('.scv3-category-tree__label')) || 'Категория';
3885				const topInput = topRow.querySelector('input[type="checkbox"][data-scv3-cat-checkbox]');
3886				const name = (topInput && topInput.getAttribute('name')) || input.getAttribute('name') || '';
3887				const value = String(input.value || '').trim();
3888				if (!value || !name) return;
3889				if (!groups.has(topTermId)) {
3890					groups.set(topTermId, { label: topLabel, name, values: new Set(), subCount: 0 });
3891				}
3892				const group = groups.get(topTermId);
3893				group.values.add(value);
3894				const rowTermId = Number(row.getAttribute('data-scv3-cat-term-id') || 0);
3895				if (rowTermId !== topTermId) {
3896					group.subCount += 1;
3897				}
3898			});
3899
3900			groups.forEach((group) => {
3901				const selectedCount = group.values.size;
3902				if (selectedCount <= 0) return;
3903				let label = group.label;
3904				if (group.subCount > 0) {
3905					label = `${group.label} (${group.subCount})`;
3906				}
3907				appendActive(label, {
3908					scv3FilterType: 'categoryGroup',
3909					scv3FilterName: group.name,
3910					scv3FilterValues: Array.from(group.values).join(',')
3911				});
3912			});
3913		};
3914
3915		appendCategoryTreeGroups();
3916
3917		const checkedInputs = Array.from(
3918			filters.querySelectorAll('.scv3-filter-option input[type="checkbox"]:checked')
3919		);
3920		checkedInputs.forEach((input) => {
3921			if (input.hasAttribute('data-scv3-cat-checkbox')) return;
3922			const label = getLabelText(input.closest('.scv3-filter-option'));
3923			if (!label) return;
3924			appendActive(label, {
3925				scv3FilterName: input.getAttribute('name') || '',
3926				scv3FilterValue: input.value || ''
3927			});
3928		});
3929
3930		const priceInputs = Array.from(filters.querySelectorAll('.scv3-filter-list--price input'));
3931		let priceFrom = '';
3932		let priceTo = '';
3933		priceInputs.forEach((input) => {
3934			const name = input.getAttribute('name') || '';
3935			const value = String(input.value || '').trim();
3936			if (!value) return;
3937			if (/_from$/.test(name)) {
3938				priceFrom = value;
3939			} else if (/_to$/.test(name)) {
3940				priceTo = value;
3941			}
3942		});
3943		if (priceFrom || priceTo) {
3944			let label = 'Цена';
3945			if (priceFrom && priceTo) {
3946				label = `Цена ${priceFrom}–${priceTo}`;
3947			} else if (priceFrom) {
3948				label = `Цена от ${priceFrom}`;
3949			} else if (priceTo) {
3950				label = `Цена до ${priceTo}`;
3951			}
3952			appendActive(label, { scv3FilterType: 'price' });
3953		}
3954
3955		return count;
3956	};
3957
3958	const resetAllFilters = () => {
3959		const filters = getFilters();
3960		if (!filters) return;
3961		const params = { ...Page._state.urlParams };
3962		const names = new Set();
3963		filters.querySelectorAll('input[name]').forEach((input) => {
3964			let name = input.getAttribute('name') || '';
3965			if (!name) return;
3966			if (name.endsWith('[]')) {
3967				name = name.slice(0, -2);
3968			}
3969			names.add(name);
3970		});
3971		names.forEach((name) => {
3972			delete params[name];
3973			delete params[`${name}[]`];
3974		});
3975		filters.querySelectorAll('.scv3-filter-option input[type="checkbox"], .scv3-filter-option input[type="radio"]').forEach((input) => {
3976			input.checked = false;
3977			input.indeterminate = false;
3978			const label = input.closest('.scv3-filter-option');
3979			if (label) {
3980				label.classList.remove('scv3-is-checked');
3981			}
3982		});
3983		filters.querySelectorAll('.scv3-filter-list--price input').forEach((input) => {
3984			input.value = '';
3985		});
3986		Page._state.urlParams = params;
3987		Page._state.currentPage = 1;
3988		Page.helpers.updateUrl(1);
3989		updateAll();
3990		Page.mainBlocks.runFilters();
3991	};
3992
3993	const updateRoot = (root) => {
3994		const filters = getFilters();
3995		const blocksContainer = root.querySelector('[data-scv3-filter-pills-blocks]');
3996		const activeContainer = root.querySelector('[data-scv3-filter-pills-active]');
3997		const countEl = root.querySelector('[data-scv3-filter-count]');
3998		const filterBtn = root.querySelector('[data-scv3-filter-trigger]');
3999		const resetBtn = root.querySelector('[data-scv3-filter-reset-all]');
4000
4001		if (!filters) {
4002			if (blocksContainer) blocksContainer.innerHTML = '';
4003			if (activeContainer) activeContainer.innerHTML = '';
4004			if (filterBtn) filterBtn.classList.remove('scv3-is-active');
4005			if (countEl) countEl.textContent = '';
4006			if (resetBtn) resetBtn.classList.remove('scv3-is-visible');
4007			return;
4008		}
4009
4010		buildBlockPills(filters, blocksContainer);
4011		const activeCount = buildActivePills(filters, activeContainer);
4012		if (filterBtn) {
4013			filterBtn.classList.toggle('scv3-is-active', activeCount > 0);
4014		}
4015		if (countEl) {
4016			countEl.textContent = activeCount > 0 ? String(activeCount) : '';
4017		}
4018		if (resetBtn) {
4019			resetBtn.classList.toggle('scv3-is-visible', activeCount > 1);
4020		}
4021	};
4022
4023	const updateAll = () => {
4024		roots.forEach(updateRoot);
4025	};
4026
4027	roots.forEach((root) => {
4028		root.addEventListener('click', (e) => {
4029			const resetBtn = e.target.closest('[data-scv3-filter-reset-all]');
4030			if (resetBtn) {
4031				e.preventDefault();
4032				resetAllFilters();
4033				return;
4034			}
4035
4036			const removeBtn = e.target.closest('[data-scv3-filter-remove]');
4037			if (removeBtn) {
4038				const filters = getFilters();
4039				if (!filters) return;
4040				if (removeBtn.dataset.scv3FilterType === 'price') {
4041					const inputs = Array.from(filters.querySelectorAll('.scv3-filter-list--price input'));
4042					inputs.forEach((input) => {
4043						input.value = '';
4044						input.dispatchEvent(new Event('input', { bubbles: true }));
4045						input.dispatchEvent(new Event('change', { bubbles: true }));
4046					});
4047					return;
4048				}
4049				if (removeBtn.dataset.scv3FilterType === 'categoryGroup') {
4050					const name = removeBtn.dataset.scv3FilterName || '';
4051					const values = String(removeBtn.dataset.scv3FilterValues || '')
4052						.split(',')
4053						.map((v) => v.trim())
4054						.filter((v) => v !== '');
4055					if (!name || !values.length) return;
4056					let changedInput = null;
4057					values.forEach((value) => {
4058						const selector = `.scv3-filter-option input[name="${escSelector(name)}"][value="${escSelector(value)}"]`;
4059						const input = filters.querySelector(selector);
4060						if (!input || !input.checked) return;
4061						input.checked = false;
4062						input.indeterminate = false;
4063						const label = input.closest('.scv3-filter-option');
4064						if (label) {
4065							label.classList.remove('scv3-is-checked');
4066						}
4067						changedInput = input;
4068					});
4069					if (changedInput) {
4070						changedInput.dispatchEvent(new Event('change', { bubbles: true }));
4071					}
4072					return;
4073				}
4074				const name = removeBtn.dataset.scv3FilterName || '';
4075				const value = removeBtn.dataset.scv3FilterValue || '';
4076				if (!name || value === '') return;
4077				const selector = `.scv3-filter-option input[name="${escSelector(name)}"][value="${escSelector(value)}"]`;
4078				const input = filters.querySelector(selector);
4079				if (input) {
4080					input.checked = false;
4081					input.dispatchEvent(new Event('change', { bubbles: true }));
4082				}
4083				return;
4084			}
4085
4086			const targetBtn = e.target.closest('[data-scv3-filter-target]');
4087			if (targetBtn) {
4088				const slug = targetBtn.dataset.scv3FilterTarget || '';
4089				if (typeof window.scv3OpenFilterAllSheet === 'function') {
4090					window.scv3OpenFilterAllSheet(slug, { fromFilters: false });
4091				} else if (typeof window.scv3OpenFilterSheet === 'function') {
4092					window.scv3OpenFilterSheet(slug);
4093				}
4094			}
4095		});
4096	});
4097
4098	window.scv3UpdateMobileFilterPills = updateAll;
4099	updateAll();
4100})();
4101
4102window.addEventListener('popstate', () => {
4103	if (!Page || !Page.helpers) return;
4104	const routeState = Page.helpers.getRouteState();
4105	Page._state.basePath = routeState.basePath;
4106	Page._state.currentPage = routeState.currentPage;
4107	Page._state.urlParams = routeState.urlParams;
4108	Page.helpers.updateSliderVisibility();
4109	Page.helpers.fetchAndRender({
4110		page: routeState.currentPage,
4111		append: false,
4112		withFacets: true,
4113		history: 'replace',
4114		updateUrl: false
4115	});
4116});
4117
4118(function() {
4119	const container = document.getElementById('tagsCloud');
4120	if (!container) return;
4121
4122	const toggleBtn = container.querySelector('.tag-toggle');
4123	const items = Array.from(container.querySelectorAll('.tag-item'));
4124	let isExpanded = false;
4125	let sizes = null; // { collapsed, full }
4126
4127// Замеряем высоты строки (свёрнуто) и полную высоту
4128	function measure() {
4129// Все элементы в потоке, кнопка на своём месте
4130		items.forEach(el => el.style.display = 'inline-block');
4131		toggleBtn.style.display = 'inline-block';
4132		toggleBtn.textContent = 'Все';
4133
4134// Граница первой строки (как и раньше)
4135		const firstTop = items[0].offsetTop;
4136		let splitIndex = items.length - 1;
4137		for (let i = 0; i < items.length; i++) {
4138			items[i].after(toggleBtn);
4139			if ((toggleBtn.offsetTop - 20) > firstTop || items[i].offsetTop > firstTop) {
4140				splitIndex = i - 1;
4141				break;
4142			}
4143		}
4144		splitIndex = Math.max(0, splitIndex);
4145		items[splitIndex].after(toggleBtn);
4146
4147// Высота одной (первой) строки: прячем остальное только для замера
4148		items.forEach((el, idx) => { el.style.display = (idx > splitIndex) ? 'none' : 'inline-block'; });
4149		container.style.height = 'auto';
4150		const collapsedH = container.clientHeight;
4151
4152// Полная высота
4153		items.forEach(el => el.style.display = 'inline-block');
4154		container.style.height = 'auto';
4155		const fullH = container.clientHeight;
4156
4157// Возвращаем все в поток — прятать будет сам overflow: hidden
4158		items.forEach(el => el.style.display = 'inline-block');
4159
4160		return { collapsed: collapsedH, full: fullH, hasOverflow: fullH > collapsedH };
4161	}
4162
4163	function animateTo(h) {
4164// Основной путь — CSS transition (уже в стилях).
4165// Если подключён jQuery — можно раскомментировать строку ниже.
4166// jQuery(container).stop().animate({ height: h + 'px' }, 350);
4167		container.style.height = h + 'px';
4168	}
4169
4170	function recompute(animate) {
4171		sizes = measure();
4172
4173		if (!sizes.hasOverflow) {
4174// Всё влезает в одну строку — кнопка не нужна, авто-высота
4175			container.style.height = 'auto';
4176			toggleBtn.style.display = 'none';
4177			return;
4178		}
4179
4180		toggleBtn.style.display = 'inline-block';
4181		toggleBtn.textContent = isExpanded ? 'Скрыть' : 'Все';
4182
4183		const target = isExpanded ? sizes.full : sizes.collapsed;
4184		if (animate) animateTo(target);
4185		else container.style.height = target + 'px';
4186	}
4187
4188	toggleBtn.addEventListener('click', function(e) {
4189		e.preventDefault();
4190		isExpanded = !isExpanded;
4191		toggleBtn.textContent = isExpanded ? 'Скрыть' : 'Все';
4192		animateTo(isExpanded ? sizes.full : sizes.collapsed);
4193	});
4194
4195// Пересчёт при изменении ширины окна (без анимации, чтобы не дёргалось)
4196	let resizeTimeout;
4197	window.addEventListener('resize', function() {
4198		clearTimeout(resizeTimeout);
4199		resizeTimeout = setTimeout(function() { recompute(false); }, 120);
4200	});
4201
4202	if (document.readyState === 'loading') {
4203		document.addEventListener('DOMContentLoaded', function() { recompute(false); });
4204	} else {
4205		recompute(false);
4206	}
4207})();

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.