PageSourceSearch

https://mhave.ru/local/templates/mhave/redesign/redesign.js?179076005634451

js mhave.ru collected 2026-10-10 02:33:54 UTC 34,451 bytes, 657 lines download raw bytes

1/* MHAVE — редизайн: слайдеры на Swiper 11 (бандл из шаблона Aspro), выпадающий каталог, мобильное меню и поиск. */
2/* Защита jQuery от повторной загрузки. Окно входа (компонент bxmaker.authuserphone) в своём HTML заново подключает
3   jquery-3.6.0.min.js; при вставке формы jQuery выполняет его, создаёт новую копию и затирает плагины Aspro
4   (jqmEx, tooltip, validate), после чего остальные попапы перестают открываться. Закрепляем первую копию. */
5(function () {
6	var jq = window.jQuery;
7	if (!jq || window.__rdJqLocked) { return; }
8	try {
9		['jQuery', '$'].forEach(function (name) {
10			Object.defineProperty(window, name, {
11				configurable: true,
12				get: function () { return jq; },
13				set: function (v) { if (v && v.fn && v.fn.jquery && v !== jq) { return; } jq = v || jq; }
14			});
15		});
16		window.__rdJqLocked = true;
17	} catch (e) {}
18})();
19
20(function () {
21	'use strict';
22
23	function ready(fn) {
24		if (document.readyState !== 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); }
25	}
26
27	/* ---------- конфигурации слайдеров по типу (data-rd-swiper) ---------- */
28	var configs = {
29		hero: {
30			effect: 'fade',
31			fadeEffect: { crossFade: true },
32			loop: true,
33			speed: 600,
34			autoplay: { delay: 7000, disableOnInteraction: false, pauseOnMouseEnter: true },
35			slidesPerView: 1
36		},
37		tiles: { /* только мобильный: на десктопе обычная сетка 2×2 без Swiper */
38			media: '(max-width: 900px)',
39			slidesPerView: 1,
40			spaceBetween: 16
41		},
42		products: { /* мобильный: видно 1.5 карточки (ширина слайда в CSS), листание по одной */
43			slidesPerView: 'auto',
44			slidesPerGroup: 1,
45			spaceBetween: 12,
46			breakpoints: { 901: { slidesPerView: 2, slidesPerGroup: 2, spaceBetween: 20 }, 1281: { slidesPerView: 4, slidesPerGroup: 4, spaceBetween: 20 } }
47		},
48		promos: { /* листание по одной карточке на всех ширинах */
49			slidesPerView: 'auto',
50			slidesPerGroup: 1,
51			spaceBetween: 12,
52			breakpoints: { 901: { slidesPerView: 2, slidesPerGroup: 1, spaceBetween: 17 }, 1281: { slidesPerView: 3, slidesPerGroup: 1, spaceBetween: 17 } }
53		},
54		posts: {
55			slidesPerView: 'auto',
56			spaceBetween: 12,
57			breakpoints: { 901: { slidesPerView: 2, slidesPerGroup: 2, spaceBetween: 17 }, 1281: { slidesPerView: 3, slidesPerGroup: 3, spaceBetween: 17 } }
58		},
59		trends: { /* мобильный: слайдер карточек 240px (макет 49:10010), десктоп: 4 в ряд */
60			slidesPerView: 'auto',
61			slidesPerGroup: 1,
62			spaceBetween: 12,
63			breakpoints: { 901: { slidesPerView: 4, slidesPerGroup: 4, spaceBetween: 20 } }
64		},
65		articles: {
66			slidesPerView: 'auto',
67			slidesPerGroup: 1,
68			spaceBetween: 12,
69			breakpoints: { 901: { slidesPerView: 3, slidesPerGroup: 3, spaceBetween: 20 }, 1281: { slidesPerView: 5, slidesPerGroup: 5, spaceBetween: 20 } }
70		},
71		brands: { /* только мобильный: на десктопе сетка 4 карточки без Swiper */
72			media: '(max-width: 900px)',
73			slidesPerView: 'auto',
74			spaceBetween: 12
75		},
76		reviews: {
77			slidesPerView: 'auto',
78			slidesPerGroup: 1,
79			spaceBetween: 12,
80			breakpoints: { 901: { slidesPerView: 'auto', slidesPerGroup: 1, spaceBetween: 20 } }
81		}
82	};
83
84	function initSwipers() {
85		if (typeof window.Swiper !== 'function') { return; }
86		var nodes = Array.prototype.slice.call(document.querySelectorAll('.redesign [data-rd-swiper]'));
87		nodes.forEach(function (el) {
88			var type = el.getAttribute('data-rd-swiper');
89			var base = configs[type] || { slidesPerView: 'auto', spaceBetween: 16 };
90			var opts = JSON.parse(JSON.stringify(base));
91			opts.watchOverflow = true;
92			opts.observer = true;
93			opts.observeParents = true;
94
95			var pag = el.querySelector('[data-rd-pagination]');
96			if (pag) {
97				opts.pagination = { el: pag, clickable: true, bulletElement: 'i', bulletClass: 'rd-dot', bulletActiveClass: 'is-active' };
98			}
99			/* стрелки: внутри слайдера (hero) или в заголовке секции (.rd-arrows) */
100			var prev = el.querySelector('[data-rd-prev]');
101			var next = el.querySelector('[data-rd-next]');
102			if (!prev || !next) {
103				var section = el.closest('.rd-sec');
104				var arrows = section ? section.querySelector('.rd-arrows') : null;
105				if (arrows) {
106					var btns = arrows.querySelectorAll('button');
107					if (btns.length >= 2) { prev = btns[0]; next = btns[1]; }
108				}
109			}
110			if (prev && next) {
111				opts.navigation = { prevEl: prev, nextEl: next, disabledClass: 'is-disabled' };
112			}
113			/* fade (hero) — единичный слайд без loop */
114			if (type === 'hero' && el.querySelectorAll('.swiper-slide').length < 2) {
115				opts.loop = false; opts.autoplay = false;
116			}
117			var media = opts.media; delete opts.media;
118			if (!media) { new window.Swiper(el, opts); return; }
119			/* слайдер только в заданном диапазоне ширины: создаём/уничтожаем по matchMedia */
120			var mq = window.matchMedia(media);
121			var instance = null;
122			function apply() {
123				if (mq.matches && !instance) {
124					instance = new window.Swiper(el, opts);
125				} else if (!mq.matches && instance) {
126					instance.destroy(true, true);
127					instance = null;
128				}
129			}
130			apply();
131			if (mq.addEventListener) { mq.addEventListener('change', apply); } else { mq.addListener(apply); }
132		});
133	}
134
135	/* ---------- выпадающий каталог в шапке (десктоп) ---------- */
136	function initCatalog() {
137		/* выпадающий список каталога заменён общей панелью меню (см. initMobile): кнопка «Каталог» открывает её */
138	}
139
140	/* ---------- панель каталога по кнопке «Каталог» (десктоп; прежнее меню по макетам 49:10403/49:10494), кнопка поиска ----------
141	   Бургер мобильной шапки открывает отдельное меню #rd-mmenu (initMobileMenu), здесь он не обрабатывается. */
142	function initMobile() {
143		var burger = null;
144		var burgerBtn = document.querySelector('[data-rd-toggle="mobile"]');
145		var catBtn = document.querySelector('[data-rd-toggle="catalog"]');
146		var panel = document.getElementById('rd-mobile-menu');
147		var searchBtn = document.querySelector('[data-rd-toggle="search"]');
148		var search = document.querySelector('.redesign .rd-search');
149		if (burgerBtn) { burgerBtn.hidden = false; }
150		if (searchBtn) { searchBtn.hidden = false; }
151
152		var I = {
153			close: '<svg viewBox="0 0 22 22" width="22" height="22" aria-hidden="true"><path d="M3 3l16 16M19 3L3 19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
154			back: '<svg viewBox="0 0 22 22" width="22" height="22" aria-hidden="true"><path d="M14 4l-7 7 7 7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
155			pin: '<svg viewBox="0 0 14 14" width="13" height="13" aria-hidden="true"><path fill="currentColor" d="M7 .8A4.7 4.7 0 0 0 2.3 5.5c0 3.3 4.7 7.7 4.7 7.7s4.7-4.4 4.7-7.7A4.7 4.7 0 0 0 7 .8zm0 6.4a1.7 1.7 0 1 1 0-3.4 1.7 1.7 0 0 1 0 3.4z"/></svg>',
156			lupa: '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5" fill="none" stroke="currentColor" stroke-width="2"/><path d="M15.5 15.5L21 21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>'
157		};
158		function esc(str) { return String(str || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;'); }
159		function txt(el) { return el ? el.textContent.replace(/\s+/g, ' ').trim() : ''; }
160		function row(href, text, cls, extraAttrs) {
161			return '<a href="' + esc(href) + '" class="rd-mm__row' + (cls ? ' ' + cls : '') + '"' + (extraAttrs || '') + '><span>' + esc(text) + '</span><i>→</i></a>';
162		}
163		/* копия атрибутов штатной ссылки Aspro (попап авторизации, обратный звонок) */
164		function attrsOf(link) {
165			if (!link) { return ''; }
166			var out = '';
167			Array.prototype.forEach.call(link.attributes, function (a) {
168				if (/^data-/.test(a.name)) { out += ' ' + a.name + '="' + esc(a.value) + '"'; }
169			});
170			if (/animate-load/.test(link.className)) { out += ' data-rd-animate="1"'; }
171			return out;
172		}
173
174		function build() {
175			var nav = document.querySelectorAll('.redesign .rd-nav__list > li > a');
176			var cols = document.querySelectorAll('.redesign .rd-catalog__col:not(.rd-catalog__col--all)');
177			var top = document.querySelectorAll('.redesign .rd-topbar__links a');
178			var city = txt(document.querySelector('.redesign .rd-city'));
179			var phone = document.querySelector('.redesign .rd-phone__num');
180			var call = document.querySelector('.redesign .rd-phone__call');
181			var cabinet = document.querySelector('.redesign .rd-icon--cabinet a');
182			var fav = document.querySelector('.redesign .rd-icon--favorite a');
183			var cart = document.querySelector('.redesign .rd-cart a');
184			var track = null;
185			Array.prototype.forEach.call(document.querySelectorAll('.redesign .rd-footer__links a'), function (a) { if (!track && /отследит/i.test(a.textContent)) { track = a; } });
186			var logo = document.querySelector('.redesign .rd-logo img');
187
188			/* разделы каталога с подразделами: по ссылке корня */
189			var catalog = {};
190			Array.prototype.forEach.call(cols, function (col) {
191				var root = col.querySelector('.rd-catalog__root');
192				if (!root) { return; }
193				var groups = [];
194				Array.prototype.forEach.call(col.querySelectorAll(':scope > .rd-catalog__sub > li'), function (li) {
195					var a = li.querySelector(':scope > a');
196					var subs = li.querySelectorAll(':scope > .rd-catalog__sub3 > li > a');
197					groups.push({ href: a.getAttribute('href'), text: txt(a), items: Array.prototype.map.call(subs, function (s) { return { href: s.getAttribute('href'), text: txt(s) }; }) });
198				});
199				if (groups.length) { catalog[root.getAttribute('href')] = { href: root.getAttribute('href'), text: txt(root), groups: groups }; }
200			});
201
202			var cityHtml = city ? '<a href="' + esc(SITE_DIR_() + 'contacts/') + '" class="rd-mm__city">' + I.pin + '<span>' + esc(city) + '</span></a>' : '';
203			var h = '<div class="rd-mm__view is-active" data-view="root">';
204			h += '<div class="rd-mm__head"><div class="rd-mm__head-left"><button type="button" class="rd-mm__close" data-mm-close aria-label="Закрыть">' + I.close + '</button>' + (logo ? '<a href="' + esc(SITE_DIR_()) + '" class="rd-mm__logo"><img src="' + esc(logo.getAttribute('src')) + '" alt="MHAVE"></a>' : '') + '</div>' + cityHtml + '</div>';
205			h += '<div class="rd-mm__body">';
206			h += '<form class="rd-mm__search" id="rd-mm-search" action="' + esc(SITE_DIR_() + 'search/') + '" method="get" role="search">' + I.lupa + '<input type="text" name="q" id="rd-mm-search-input" placeholder="Поиск по товарам, брендам, категориям" autocomplete="off" maxlength="100"></form>';
207			h += '<div class="rd-mm__cols"><div class="rd-mm__col rd-mm__col--catalog"><div class="rd-mm__kicker rd-mm__kicker--first">Каталог</div><div class="rd-mm__list rd-mm__list--lg">';
208			var views = '';
209			var vi = 0;
210			Array.prototype.forEach.call(nav, function (a) {
211				var href = a.getAttribute('href');
212				var section = catalog[href];
213				if (section) {
214					vi++;
215					h += '<a href="' + esc(href) + '" class="rd-mm__row" data-mm-open="' + vi + '"><span>' + esc(txt(a)) + '</span><i>→</i></a>';
216					var v = '<div class="rd-mm__view" data-view="' + vi + '">';
217					v += '<div class="rd-mm__head"><div class="rd-mm__head-left"><button type="button" class="rd-mm__back" data-mm-back aria-label="Назад">' + I.back + '</button><span class="rd-mm__title">' + esc(section.text) + '</span></div><button type="button" class="rd-mm__close" data-mm-close aria-label="Закрыть">' + I.close + '</button></div>';
218					v += '<div class="rd-mm__body"><a href="' + esc(section.href) + '" class="rd-btn rd-btn--ghost rd-mm__all">Все товары раздела <span>→</span></a><div class="rd-mm__groups">';
219					section.groups.forEach(function (g) {
220						if (g.items.length) {
221							v += '<div class="rd-mm__group"><div class="rd-mm__kicker">' + esc(g.text) + '</div><div class="rd-mm__list">';
222							g.items.forEach(function (it) { v += row(it.href, it.text); });
223							v += '</div></div>';
224						} else {
225							v += '<div class="rd-mm__group rd-mm__group--single"><div class="rd-mm__list rd-mm__list--single">' + row(g.href, g.text) + '</div></div>';
226						}
227					});
228					v += '</div>';
229					v += '<div class="rd-mm__foot rd-mm__foot--line">' + I.pin + '<span>' + esc([city, txt(phone)].filter(Boolean).join(' · ')) + '</span></div>';
230					v += '</div></div>';
231					views += v;
232				} else {
233					h += row(href, txt(a));
234				}
235			});
236			h += '</div></div>';
237			h += '<div class="rd-mm__col rd-mm__col--personal"><div class="rd-mm__sep"></div><div class="rd-mm__kicker">Личное</div><div class="rd-mm__list">';
238			if (cabinet) { h += row(cabinet.getAttribute('href'), 'Личный кабинет', '', attrsOf(cabinet)); }
239			if (fav) { h += row(fav.getAttribute('href'), 'Избранное'); }
240			if (cart) { h += row(cart.getAttribute('href'), 'Корзина'); }
241			if (track) { h += row(track.getAttribute('href'), txt(track)); }
242			h += '</div></div><div class="rd-mm__col rd-mm__col--contacts"><div class="rd-mm__sep"></div><div class="rd-mm__kicker rd-mm__kicker--desk">Контакты</div>';
243			if (phone) { h += '<a href="' + esc(phone.getAttribute('href')) + '" class="rd-mm__phone">' + esc(txt(phone)) + '</a>'; }
244			if (call) { h += '<a href="javascript:void(0)" class="rd-mm__call"' + attrsOf(call) + '>' + esc(txt(call)) + '</a>'; }
245			if (top.length) {
246				h += '<div class="rd-mm__links">';
247				Array.prototype.forEach.call(top, function (a, i) { h += (i ? '<span> · </span>' : '') + '<a href="' + esc(a.getAttribute('href')) + '">' + esc(txt(a)) + '</a>'; });
248				h += '</div>';
249			}
250			h += '</div></div></div></div>';
251			panel.innerHTML = h + views;
252
253			initMenuSuggest();
254
255			/* штатные обработчики Aspro (jqm-попапы) навешиваются по .animate-load — возвращаем класс после сборки */
256			Array.prototype.forEach.call(panel.querySelectorAll('[data-rd-animate]'), function (a) { a.classList.add('animate-load'); });
257			if (window.jQuery && window.jQuery.fn && window.jQuery.fn.jqmEx) { /* jqm инициализируется делегированно, ничего не делаем */ }
258		}
259		function SITE_DIR_() { return (window.arAsproOptions && window.arAsproOptions.SITE_DIR) || '/'; }
260
261		/* подсказки в поле поиска меню: второй экземпляр штатного JCTitleSearch2 (шаблон search.title «corp»).
262		   Запрос уходит с INPUT_ID поля шапки — на него отвечает компонент на странице; список выводится под полем меню. */
263		function initMenuSuggest() {
264			var form = panel.querySelector('#rd-mm-search');
265			var input = panel.querySelector('#rd-mm-search-input');
266			if (!form || !input || typeof window.JCTitleSearch2 !== 'function' || !window.jsControl) { return; }
267			var ctl = new window.JCTitleSearch2({
268				AJAX_PAGE: window.jsControl.arParams.AJAX_PAGE,
269				CONTAINER_ID: 'rd-mm-search',
270				INPUT_ID: 'rd-mm-search-input',
271				INPUT_ID_TMP: window.jsControl.arParams.INPUT_ID_TMP || 'title-search-input',
272				MIN_QUERY_LEN: 2
273			});
274			input.addEventListener('input', function () { if (ctl.RESULT) { ctl.onChange(); } });
275			var tries = 0;
276			(function move() {
277				if (ctl.RESULT) {
278					ctl.RESULT.classList.add('rd-mm__suggest');
279					form.parentNode.insertBefore(ctl.RESULT, form.nextSibling);
280					return;
281				}
282				if (++tries < 40) { setTimeout(move, 50); }
283			})();
284		}
285
286		function showView(id) {
287			Array.prototype.forEach.call(panel.querySelectorAll('.rd-mm__view'), function (v) { v.classList.toggle('is-active', v.getAttribute('data-view') === String(id)); });
288			panel.scrollTop = 0;
289		}
290		function setToggles(open) {
291			[burger, catBtn].forEach(function (b) {
292				if (!b) { return; }
293				b.classList.toggle('is-open', open);
294				b.setAttribute('aria-expanded', open ? 'true' : 'false');
295			});
296		}
297		/* десктоп: панель выпадает под строкой разделов шапки на всю ширину */
298		var navRow = document.querySelector('.redesign .rd-nav');
299		function placePanel() {
300			var top = 0;
301			if (window.innerWidth > 900 && navRow) { top = Math.max(0, Math.round(navRow.getBoundingClientRect().bottom)); }
302			panel.style.setProperty('--rd-mm-top', top + 'px');
303		}
304		window.addEventListener('resize', function () { if (menuOpen) { placePanel(); } });
305		/* панель рассчитана на десктоп: при сужении окна до мобильной ширины закрываем */
306		var mqMob = window.matchMedia('(max-width: 900px)');
307		var onMob = function (m) { if (m.matches) { closeMenu(); } };
308		if (mqMob.addEventListener) { mqMob.addEventListener('change', onMob); } else if (mqMob.addListener) { mqMob.addListener(onMob); }
309		/* плавное появление/исчезание: .is-open держит панель в потоке, .is-visible включает переход (длительность — в CSS) */
310		var menuOpen = false;
311		var hideTimer = null;
312		function openMenu() {
313			if (!panel.dataset.built) { build(); panel.dataset.built = '1'; }
314			clearTimeout(hideTimer);
315			menuOpen = true;
316			placePanel();
317			showView('root');
318			panel.hidden = false;
319			panel.classList.add('is-open');
320			void panel.offsetWidth; /* фиксируем начальное состояние перед переходом */
321			panel.classList.add('is-visible');
322			setToggles(true);
323			document.documentElement.classList.add('rd-mm-lock');
324			if (search) { search.classList.remove('is-open'); }
325		}
326		function closeMenu() {
327			if (!menuOpen) { return; }
328			menuOpen = false;
329			panel.classList.remove('is-visible');
330			setToggles(false);
331			document.documentElement.classList.remove('rd-mm-lock');
332			clearTimeout(hideTimer);
333			hideTimer = setTimeout(function () {
334				if (menuOpen) { return; }
335				panel.classList.remove('is-open');
336				panel.hidden = true;
337			}, 260);
338		}
339
340		if (panel && (burger || catBtn)) {
341			[burger, catBtn].forEach(function (b) {
342				if (!b) { return; }
343				b.addEventListener('click', function (e) { e.preventDefault(); if (menuOpen) { closeMenu(); } else { openMenu(); } });
344			});
345			panel.addEventListener('click', function (e) {
346				var t = e.target;
347				if (t === panel) { closeMenu(); return; } /* клик по затемнению */
348				var open = t.closest('[data-mm-open]');
349				if (open) { e.preventDefault(); showView(open.getAttribute('data-mm-open')); return; }
350				if (t.closest('[data-mm-back]')) { showView('root'); return; }
351				if (t.closest('[data-mm-close]')) { closeMenu(); return; }
352				if (t.closest('a[data-event="jqm"]')) { closeMenu(); }
353			});
354			document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && menuOpen) { closeMenu(); } });
355		}
356		if (searchBtn && search) {
357			searchBtn.addEventListener('click', function () {
358				var open = search.classList.toggle('is-open');
359				if (open) {
360					if (panel && burger && menuOpen) { closeMenu(); }
361					var input = search.querySelector('input[name="q"]');
362					if (input) { input.focus(); }
363				}
364			});
365		}
366	}
367
368	/* ---------- мобильное меню-бургер #rd-mmenu (≤900px): макеты Figma 66:1791 / 66:1882, вёрстка redesigne/_shell.html + js/pages.js ----------
369	   Разметку выводит шаблон menu/rd_mmenu (header_redesign.php). Бургер [data-rd-toggle="mobile"] открывает меню,
370	   [data-rd-mm-close] (крестик, фон) и Esc — закрывают; [data-rd-mm-sub="id"] — экран подменю раздела, [data-rd-mm-back] — назад.
371	   Неактивные экраны получают inert (фокус не уходит в скрытое), фокус переносится в панель/подменю и возвращается на бургер.
372	   Атрибуты data-rd-* (а не data-rp-* из вёрстки), чтобы не пересекаться с redesign-pages.js на страницах rd-page. */
373	function initMobileMenu() {
374		var menu = document.getElementById('rd-mmenu');
375		var burger = document.querySelector('[data-rd-toggle="mobile"]');
376		if (!menu || !burger) { return; }
377		var panel = menu.querySelector('.rd-mmenu__panel');
378		var main = menu.querySelector('.rd-mmenu__screen--main');
379		var screens = menu.querySelectorAll('.rd-mmenu__screen');
380		var lastFocus = null;
381		var subOpener = null;
382		var resetTimer = null;
383
384		function showScreen(target) {
385			Array.prototype.forEach.call(screens, function (sc) {
386				var on = sc === target;
387				sc.classList.toggle('is-active', on);
388				sc.setAttribute('aria-hidden', on ? 'false' : 'true');
389				if (on) { sc.removeAttribute('inert'); } else { sc.setAttribute('inert', ''); }
390			});
391			main.classList.toggle('is-behind', target !== main);
392		}
393		function resetScreens() {
394			showScreen(main);
395			Array.prototype.forEach.call(screens, function (sc) { sc.scrollTop = 0; });
396		}
397		function isOpen() { return menu.classList.contains('is-open'); }
398
399		/* строки «Личное»: ссылка, атрибуты попапа входа Aspro и счётчик — от штатных ссылок шапки (они в композитных фреймах) */
400		var sources = {
401			cabinet: '.rd-header .rd-icon--cabinet',
402			favorite: '.rd-header .rd-icon--favorite',
403			basket: '.rd-header .rd-cart'
404		};
405		function syncPersonal() {
406			Array.prototype.forEach.call(menu.querySelectorAll('[data-rd-mm-sync]'), function (row) {
407				var box = document.querySelector(sources[row.getAttribute('data-rd-mm-sync')] || '');
408				if (!box) { return; }
409				var src = box.querySelector('a.rd-icon__link, a.rd-cart__link') || box.querySelector('a');
410				if (src) {
411					var href = src.getAttribute('href');
412					if (href && href.indexOf('javascript') !== 0) { row.setAttribute('href', href); }
413					Array.prototype.slice.call(row.attributes).forEach(function (a) {
414						if (/^data-(event|param-|name)/.test(a.name)) { row.removeAttribute(a.name); }
415					});
416					Array.prototype.forEach.call(src.attributes, function (a) {
417						if (/^data-(event|param-|name)/.test(a.name)) { row.setAttribute(a.name, a.value); }
418					});
419					row.classList.toggle('animate-load', src.classList.contains('animate-load'));
420				}
421				var cnt = box.querySelector('.icon-count, .mhave-unpaid-badge');
422				var n = cnt ? parseInt(cnt.textContent, 10) || 0 : 0;
423				var badge = row.querySelector('.rd-mmenu__count');
424				if (n > 0) {
425					if (!badge) {
426						badge = document.createElement('span');
427						badge.className = 'rd-mmenu__count';
428						row.insertBefore(badge, row.querySelector('.rd-mmenu__arr'));
429					}
430					badge.textContent = n > 99 ? '99+' : String(n);
431				} else if (badge) {
432					badge.parentNode.removeChild(badge);
433				}
434			});
435		}
436
437		function open() {
438			clearTimeout(resetTimer);
439			lastFocus = document.activeElement;
440			resetScreens();
441			syncPersonal();
442			menu.classList.add('is-open');
443			menu.setAttribute('aria-hidden', 'false');
444			document.documentElement.classList.add('rd-mm-lock');
445			burger.classList.add('is-open');
446			burger.setAttribute('aria-expanded', 'true');
447			setTimeout(function () { panel.focus({ preventScroll: true }); }, 50);
448		}
449		/* restoreFocus=false — при переходе по ссылке (фокус возвращать некуда) */
450		function close(restoreFocus) {
451			if (!isOpen()) { return; }
452			menu.classList.remove('is-open');
453			menu.setAttribute('aria-hidden', 'true');
454			document.documentElement.classList.remove('rd-mm-lock');
455			burger.classList.remove('is-open');
456			burger.setAttribute('aria-expanded', 'false');
457			subOpener = null;
458			clearTimeout(resetTimer);
459			resetTimer = setTimeout(function () { if (!isOpen()) { resetScreens(); } }, 300);
460			if (restoreFocus !== false && lastFocus && lastFocus.focus) { lastFocus.focus({ preventScroll: true }); }
461		}
462		function openSub(link) {
463			var target = document.getElementById(link.getAttribute('data-rd-mm-sub'));
464			if (!target) { return false; }
465			subOpener = link;
466			target.scrollTop = 0;
467			showScreen(target);
468			setTimeout(function () { target.focus({ preventScroll: true }); }, 50);
469			return true;
470		}
471		function back() {
472			showScreen(main);
473			if (subOpener) { var o = subOpener; setTimeout(function () { o.focus({ preventScroll: true }); }, 50); }
474			subOpener = null;
475		}
476
477		resetScreens();
478		burger.setAttribute('aria-controls', 'rd-mmenu');
479		burger.addEventListener('click', function (e) { e.preventDefault(); if (isOpen()) { close(); } else { open(); } });
480		menu.addEventListener('click', function (e) {
481			var t = e.target;
482			var el;
483			if (t.closest('[data-rd-mm-close]')) { e.preventDefault(); close(); return; }
484			if ((el = t.closest('[data-rd-mm-sub]'))) { if (openSub(el)) { e.preventDefault(); } return; }
485			if (t.closest('[data-rd-mm-back]')) { e.preventDefault(); back(); return; }
486			/* попапы Aspro (вход, обратный звонок) — закрываем меню, окно откроет штатный обработчик */
487			if (t.closest('a[data-event="jqm"]')) { close(false); }
488		});
489		document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && isOpen()) { close(); } });
490		/* при расширении окна до десктопа меню закрывается */
491		var mq = window.matchMedia('(min-width: 901px)');
492		var onMq = function (m) { if (m.matches) { close(false); } };
493		if (mq.addEventListener) { mq.addEventListener('change', onMq); } else if (mq.addListener) { mq.addListener(onMq); }
494		/* возврат по «Назад» браузера из bfcache: меню должно быть закрыто */
495		window.addEventListener('pageshow', function (e) { if (e.persisted) { close(false); } });
496
497		/* поиск: не отправлять пустой; подсказки — второй экземпляр штатного JCTitleSearch2 (как initMenuSuggest) */
498		var form = menu.querySelector('#rd-mmenu-search');
499		var input = menu.querySelector('#rd-mmenu-search-input');
500		if (form && input) {
501			form.addEventListener('submit', function (e) { if (!input.value.trim()) { e.preventDefault(); input.focus(); } });
502			if (typeof window.JCTitleSearch2 === 'function' && window.jsControl) {
503				var ctl = new window.JCTitleSearch2({
504					AJAX_PAGE: window.jsControl.arParams.AJAX_PAGE,
505					CONTAINER_ID: 'rd-mmenu-search',
506					INPUT_ID: 'rd-mmenu-search-input',
507					INPUT_ID_TMP: window.jsControl.arParams.INPUT_ID_TMP || 'title-search-input',
508					MIN_QUERY_LEN: 2
509				});
510				input.addEventListener('input', function () { if (ctl.RESULT) { ctl.onChange(); } });
511				var tries = 0;
512				(function move() {
513					if (ctl.RESULT) {
514						ctl.RESULT.classList.add('rd-mm__suggest');
515						form.parentNode.insertBefore(ctl.RESULT, form.nextSibling);
516						return;
517					}
518					if (++tries < 40) { setTimeout(move, 50); }
519				})();
520			}
521		}
522
523		window.rdMobileMenu = { open: open, close: close };
524	}
525
526	/* ---------- доставка Ozon в оформлении заказа: выбор ПВЗ на карте сразу выбирает и способ доставки Ozon ----------
527	   Виджет модуля ipol.ozondelivery после выбора пункта только записывает адрес и пересчитывает заказ,
528	   радиокнопку доставки не трогает — если был выбран «Самовывоз», он так и оставался.
529	   Файлы модуля не правим (затрутся обновлением): оборачиваем конструктор OzonPickupMapWidget и дополняем onChange. */
530	function selectOzonDelivery() {
531		var btn = document.querySelector('.ozon-pvz-select-btn');
532		var id = btn ? btn.getAttribute('data-service-id') : '';
533		if (!id) { return; }
534		var radio = document.querySelector('input[name="DELIVERY_ID"][value="' + id + '"]');
535		if (radio && !radio.checked) { radio.click(); }
536	}
537	function initOzonPickup() {
538		if (!document.querySelector('.ozon-pvz-select-btn, #bx-soa-order, #bx-soa-order-form')) { return; }
539		var tries = 0;
540		(function patch() {
541			var Orig = window.OzonPickupMapWidget;
542			if (typeof Orig !== 'function') { if (++tries < 60) { setTimeout(patch, 250); } return; }
543			if (Orig.__rdPatched) { return; }
544			var Wrapped = function (opts) {
545				if (opts && typeof opts.onChange === 'function') {
546					var onChange = opts.onChange;
547					opts.onChange = function () {
548						var res = onChange.apply(this, arguments);
549						selectOzonDelivery();
550						return res;
551					};
552				}
553				return new Orig(opts);
554			};
555			Wrapped.prototype = Orig.prototype;
556			for (var k in Orig) { if (Object.prototype.hasOwnProperty.call(Orig, k)) { Wrapped[k] = Orig[k]; } }
557			Wrapped.__rdPatched = true;
558			window.OzonPickupMapWidget = Wrapped;
559		})();
560	}
561
562	/* ---------- якорные ссылки (кружки над каталогом): плавная прокрутка к секции ---------- */
563	function initAnchors() {
564		document.addEventListener('click', function (e) {
565			var a = e.target.closest ? e.target.closest('.redesign a[href*="#"]') : null;
566			if (!a || a.hasAttribute('data-mm-open')) { return; }
567			var href = a.getAttribute('href') || '';
568			var hash = href.charAt(0) === '#' ? href : (href.indexOf('#') > 0 && a.pathname === window.location.pathname ? href.slice(href.indexOf('#')) : '');
569			if (!hash || hash === '#') { return; }
570			var target = document.getElementById(hash.slice(1));
571			if (!target) { return; }
572			e.preventDefault();
573			/* поправка на закреплённую шапку, если она есть */
574			var offset = 0;
575			var header = document.querySelector('.redesign .rd-header');
576			if (header) {
577				var pos = getComputedStyle(header).position;
578				if (pos === 'fixed' || pos === 'sticky') { offset = header.getBoundingClientRect().height; }
579			}
580			var top = Math.max(0, window.pageYOffset + target.getBoundingClientRect().top - offset - 12);
581			var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
582			window.scrollTo({ top: top, behavior: reduce ? 'auto' : 'smooth' });
583			if (history.replaceState) { history.replaceState(null, '', hash); }
584			/* меню (мобильное и десктопная панель) после перехода закрываем */
585			var panel = document.getElementById('rd-mobile-menu');
586			if (panel && !panel.hidden) {
587				var catBtn = document.querySelector('[data-rd-toggle="catalog"]');
588				if (catBtn) { catBtn.click(); }
589			}
590			if (window.rdMobileMenu) { window.rdMobileMenu.close(false); }
591		});
592	}
593
594	/* ---------- подсказки поиска Aspro: список создаётся в body, переносим его внутрь формы #title-search ---------- */
595	function initSearchSuggest() {
596		var form = document.getElementById('title-search');
597		if (!form) { return; }
598		/* штатный скрипт слушает только событие bxchange, которое на этой сборке ядра не срабатывает при вводе — вызываем его обработчик сами */
599		var input = document.getElementById('title-search-input');
600		if (input) {
601			input.addEventListener('input', function () {
602				if (window.jsControl && typeof window.jsControl.onChange === 'function') { window.jsControl.onChange(); }
603			});
604		}
605		var tries = 0;
606		(function move() {
607			var res = document.querySelector('body > .title-search-result.title-search-input');
608			if (res) { form.appendChild(res); return; }
609			if (!form.querySelector('.title-search-result') && ++tries < 40) { setTimeout(move, 150); }
610		})();
611	}
612
613	/* ---------- поиск: не отправлять пустой запрос ---------- */
614	function initSearch() {
615		var form = document.querySelector('.redesign .rd-search');
616		if (!form) { return; }
617		form.addEventListener('submit', function (e) {
618			var input = form.querySelector('input[name="q"]');
619			if (input && !input.value.trim()) { e.preventDefault(); input.focus(); }
620		});
621	}
622
623	/* ---------- наборы (mhave.set): в корзину попадают составляющие, ID самого набора в ответе нет —
624	   Aspro откатывает состояние кнопки и не показывает уведомление. Показываем его сами. ---------- */
625	function initSetNotice() {
626		var cls = window.JItemActionBasket;
627		if (typeof cls !== 'function' || !cls.prototype || typeof cls.prototype.onRequestSuccess !== 'function' || cls.prototype.__rdSetNotice) { return; }
628		var orig = cls.prototype.onRequestSuccess;
629		cls.prototype.onRequestSuccess = function (response) {
630			var wasAdding = false;
631			try { wasAdding = !!this.state; } catch (e) {}
632			var result = orig.apply(this, arguments);
633			try {
634				var data = (typeof response === 'object' && response) ? response : {};
635				var id = this.node ? this.node.getAttribute('data-id') : '';
636				var items = data.items && typeof data.items === 'object' ? data.items : {};
637				if (wasAdding && data.success && id && !(id in items) && !this.state && this.node.dataset.notice != 'false' && typeof window.JNoticeSurface === 'function') {
638					window.JNoticeSurface.get().onAdd2Cart([this.node]);
639				}
640			} catch (e) {}
641			return result;
642		};
643		cls.prototype.__rdSetNotice = true;
644	}
645
646	ready(function () {
647		initSwipers();
648		initCatalog();
649		initMobile();
650		initMobileMenu();
651		initSearch();
652		initSetNotice();
653		initSearchSuggest();
654		initAnchors();
655		initOzonPickup();
656	});
657})();

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.