1(function () { 2 function initSlider() { 3 const minRange = document.getElementById("minRange"); 4 const maxRange = document.getElementById("maxRange"); 5 const minLabel = document.getElementById("minLabel"); 6 const maxLabel = document.getElementById("maxLabel"); 7 const activeTrack = document.getElementById("activeTrack"); 8 9 // еÑли блока Ð½ÐµÑ Ð½Ð° ÑÑÑаниÑе â пÑоÑÑо вÑÑ Ð¾Ð´Ð¸Ð¼, оÑибок не бÑÐ´ÐµÑ 10 if (!minRange || !maxRange || !minLabel || !maxLabel || !activeTrack) { 11 return false; 12 } 13 14 // заÑиÑа Ð¾Ñ Ð¿Ð¾Ð²ÑоÑной иниÑиализаÑии, еÑли ÑÑнкÑÐ¸Ñ Ð²ÑзовеÑÑÑ ÐµÑÑ Ñаз 15 if (minRange.dataset.sliderInit) { 16 return true; 17 } 18 minRange.dataset.sliderInit = "true"; 19 20 const gap = 1; // минималÑное ÑаÑÑÑоÑние Ð¼ÐµÐ¶Ð´Ñ Ð¿Ð¾Ð»Ð·Ñнками 21 22 function updateSlider(e) { 23 let minVal = parseInt(minRange.value, 10); 24 let maxVal = parseInt(maxRange.value, 10); 25 26 const triggeredBy = e ? e.target : null; 27 28 // не даÑм ползÑнкам пеÑеÑекаÑÑÑÑ / менÑÑÑÑÑ Ð¼ÐµÑÑами 29 if (maxVal - minVal < gap) { 30 if (triggeredBy === minRange) { 31 minRange.value = maxVal - gap; 32 minVal = maxVal - gap; 33 } else { 34 maxRange.value = minVal + gap; 35 maxVal = minVal + gap; 36 } 37 } 38 39 minLabel.textContent = minVal; 40 maxLabel.textContent = maxVal; 41 42 const rangeMin = parseInt(minRange.min, 10); 43 const rangeMax = parseInt(minRange.max, 10); 44 45 const percentMin = ((minVal - rangeMin) / (rangeMax - rangeMin)) * 100; 46 const percentMax = ((maxVal - rangeMin) / (rangeMax - rangeMin)) * 100; 47 48 activeTrack.style.left = percentMin + "%"; 49 activeTrack.style.right = 100 - percentMax + "%"; 50 } 51 52 minRange.addEventListener("input", updateSlider); 53 maxRange.addEventListener("input", updateSlider); 54 55 updateSlider(); 56 return true; 57 } 58 59 // еÑли блок Ñже еÑÑÑ Ð¿Ñи загÑÑзке 60 function tryInit() { 61 if (document.readyState === "loading") { 62 document.addEventListener("DOMContentLoaded", initSlider); 63 } else { 64 initSlider(); 65 } 66 } 67 68 tryInit(); 69 70 const observer = new MutationObserver(() => { 71 initSlider(); 72 }); 73 74 observer.observe(document.body, { 75 childList: true, 76 subtree: true, 77 }); 78})(); 79 80(function () { 81 function initCatalogFilterCount() { 82 const form = document.querySelector(".catalog-filters-items"); 83 if (!form || form.dataset.countInit) return false; 84 85 const categoryInputs = Array.from( 86 form.querySelectorAll('input[name="categories"]'), 87 ); 88 const resultCount = form.querySelector("[data-filter-result-count]"); 89 if (!categoryInputs.length || !resultCount) return false; 90 91 const allResultCount = Number(form.dataset.allResultCount) || 0; 92 const numberFormatter = new Intl.NumberFormat("ru-RU"); 93 94 function updateResultCount() { 95 const selectedInputs = categoryInputs.filter((input) => input.checked); 96 const selectedTotal = selectedInputs.reduce( 97 (total, input) => total + (Number(input.dataset.resultCount) || 0), 98 0, 99 ); 100 101 resultCount.textContent = numberFormatter.format( 102 selectedInputs.length ? selectedTotal : allResultCount, 103 ); 104 } 105 106 categoryInputs.forEach((input) => 107 input.addEventListener("change", updateResultCount), 108 ); 109 form.dataset.countInit = "true"; 110 updateResultCount(); 111 return true; 112 } 113 114 if (document.readyState === "loading") { 115 document.addEventListener("DOMContentLoaded", initCatalogFilterCount); 116 } else {
117 initCatalogFilterCount(); 118 } 119})(); 120 121(function () { 122 function initFilterCategory() { 123 const scrollBox = document.getElementById("filterScroll"); 124 const nextBtn = document.getElementById("filterNextBtn"); 125 126 if (!scrollBox || !nextBtn) return false; 127 if (scrollBox.dataset.filterInit) return true; 128 scrollBox.dataset.filterInit = "true"; 129 130 const scrollStep = 200; 131 132 function updateNextButton() { 133 const maxScroll = scrollBox.scrollWidth - scrollBox.clientWidth; 134 nextBtn.classList.toggle( 135 "is-disabled", 136 scrollBox.scrollLeft >= maxScroll - 2, 137 ); 138 } 139 140 nextBtn.addEventListener("click", () => { 141 scrollBox.scrollBy({ left: scrollStep, behavior: "smooth" }); 142 }); 143 144 scrollBox.addEventListener("scroll", updateNextButton); 145 window.addEventListener("resize", updateNextButton); 146 updateNextButton(); 147 148 // --- drag-to-scroll мÑÑÑÑ --- 149 let isDragging = false; 150 let startX = 0; 151 let startScrollLeft = 0; 152 let hasMoved = false; 153 154 scrollBox.addEventListener("mousedown", (e) => { 155 isDragging = true; 156 hasMoved = false; 157 startX = e.pageX; 158 startScrollLeft = scrollBox.scrollLeft; 159 scrollBox.classList.add("is-dragging"); 160 }); 161 162 window.addEventListener("mousemove", (e) => { 163 if (!isDragging) return; 164 e.preventDefault(); 165 166 const delta = e.pageX - startX; 167 168 // еÑли ÑдвинÑли мÑÑÑ Ð±Ð¾Ð»ÑÑе Ñем на 3px â ÑÑиÑаем ÑÑо дÑагом, а не кликом 169 if (Math.abs(delta) > 3) hasMoved = true; 170 171 scrollBox.scrollLeft = startScrollLeft - delta; 172 }); 173 174 window.addEventListener("mouseup", () => { 175 if (!isDragging) return; 176 isDragging = false; 177 scrollBox.classList.remove("is-dragging"); 178 }); 179 180 // блокиÑÑем клик по кнопке category, еÑли ÑÑо бÑл дÑаг, а не клик 181 scrollBox.addEventListener( 182 "click", 183 (e) => { 184 if (hasMoved) { 185 e.preventDefault(); 186 e.stopPropagation(); 187 } 188 }, 189 true, 190 ); 191 192 return true; 193 } 194 195 if (document.readyState === "loading") { 196 document.addEventListener("DOMContentLoaded", initFilterCategory); 197 } else { 198 initFilterCategory(); 199 } 200 201 const observer = new MutationObserver(() => initFilterCategory()); 202 observer.observe(document.body, { childList: true, subtree: true }); 203})(); 204 205(function () { 206 const isCatalog = /^\/presentations\/catalog(?:\/|$)/.test( 207 window.location.pathname, 208 ); 209 210 if (!isCatalog) { 211 document.body.classList.remove("body-fon"); 212 return; 213 } 214 215 const panel = document.querySelector( 216 ".catalog-main-section .catalog-filters-main", 217 ); 218 const button = document.querySelector(".open-estate-filter"); 219 const handle = panel?.querySelector(".modal-drag-zone"); 220 if (!panel || !button || !handle) return; 221 222 const mobile = window.matchMedia("(max-width: 1020px)"); 223 const storageKey = "catalogFiltersOpen"; 224 let isOpen = false; 225 let drag = null; 226 227 try { 228 isOpen = sessionStorage.getItem(storageKey) === "true"; 229 } catch (_error) { 230 // The filter still works when browser storage is unavailable. 231 } 232 233 handle.tabIndex = 0; 234 handle.setAttribute("role", "button"); 235 236 function setOpen(value, save = true) { 237 isOpen = value; 238 panel.classList.toggle("touchstart-open", value); 239 button.classList.toggle("active", value); 240 document.body.classList.toggle("body-fon", value); 241 handle.setAttribute("aria-expanded", String(value)); 242 handle.setAttribute("aria-label", value ? "ÐакÑÑÑÑ ÑилÑÑÑÑ" : "ÐÑкÑÑÑÑ ÑилÑÑÑÑ"); 243 button.setAttribute("aria-expanded", String(value)); 244 245 for (const child of handle.parentElement.children) { 246 if (child !== handle) child.inert = !value; 247 } 248 249 if (save) { 250 try { 251 sessionStorage.setItem(storageKey, String(value)); 252 } catch (_error) { 253 // Opening and closing does not require storage access. 254 } 255 } 256 } 257 258 button.addEventListener("click", () => setOpen(!isOpen)); 259 handle.addEventListener("keydown", (event) => { 260 if (event.key !== "Enter" && event.key !== " ") return; 261 event.preventDefault(); 262 setOpen(!isOpen); 263 }); 264 265 handle.addEventListener("pointerdown", (event) => { 266 if (!mobile.matches || !event.isPrimary || event.button !== 0) return; 267 const distance = Math.max(0, panel.offsetHeight - handle.offsetHeight); 268 drag = { 269 id: event.pointerId, 270 startY: event.clientY, 271 distance, 272 initial: isOpen ? 0 : distance, 273 delta: 0, 274 wasOpen: isOpen, 275 }; 276 handle.setPointerCapture(event.pointerId); 277 panel.classList.add("is-dragging"); 278 }); 279 280 handle.addEventListener("pointermove", (event) => { 281 if (!drag || event.pointerId !== drag.id) return; 282 drag.delta = event.clientY - drag.startY; 283 const offset = Math.max(0, Math.min(drag.distance, drag.initial + drag.delta)); 284 panel.style.transform = `translate(-50%, ${offset}px)`; 285 }); 286 287 function finishDrag(event, cancelled = false) { 288 if (!drag || event.pointerId !== drag.id) return; 289 const current = drag; 290 drag = null; 291 const nextOpen = cancelled 292 ? current.wasOpen 293 : current.wasOpen 294 ? current.delta < 50 295 : current.delta < -50; 296 297 panel.classList.remove("is-dragging"); 298 setOpen(nextOpen, !cancelled); 299 panel.style.removeProperty("transform"); 300 if (handle.hasPointerCapture(current.id)) { 301 handle.releasePointerCapture(current.id); 302 } 303 } 304 305 handle.addEventListener("pointerup", (event) => finishDrag(event)); 306 handle.addEventListener("pointercancel", (event) => finishDrag(event, true)); 307 handle.addEventListener("lostpointercapture", (event) => finishDrag(event, true)); 308 309 document.addEventListener("click", (event) => { 310 if (!mobile.matches || !isOpen) return; 311 if (panel.contains(event.target) || button.contains(event.target)) return; 312 setOpen(false); 313 }); 314 315 mobile.addEventListener("change", () => { 316 if (drag) finishDrag({ pointerId: drag.id }, true); 317 }); 318 319 setOpen(isOpen, false); 320})(); 321 322( 323 // The catalog template uses <a> elements inside the custom sort options, 324 // while the legacy global handler expects <span>. Keep sort handling local 325 // to the catalog: sync the visual state from the URL and make the whole row 326 // reliably navigate without triggering the legacy handler's null access. 327 function () { 328 function initCatalogSort() { 329 const sortSelect = document.querySelector( 330 ".sort-select .c-categories-select", 331 ); 332 if (!sortSelect || sortSelect.dataset.sortInit) return false;
333 334 const headerText = sortSelect.querySelector(".c-select-header p"); 335 const optionsList = sortSelect.querySelector(".c-select-options"); 336 const options = Array.from( 337 sortSelect.querySelectorAll(".c-select-options li"), 338 ); 339 if (!headerText || !optionsList || !options.length) return false; 340 341 const requestedSort = new URLSearchParams(window.location.search).get( 342 "sort", 343 ); 344 const currentSort = requestedSort === "newest" ? "newest" : "popular"; 345 346 function getOptionData(option) { 347 const link = option.querySelector("a[href]"); 348 if (!link) return null; 349 350 const url = new URL(link.href, window.location.href); 351 const sort = 352 url.searchParams.get("sort") === "newest" ? "newest" : "popular"; 353 const label = link.textContent.trim(); 354 return { link, sort, label }; 355 } 356 357 let activeOption = null; 358 359 options.forEach((option) => { 360 const data = getOptionData(option); 361 const isActive = Boolean(data && data.sort === currentSort); 362 option.classList.toggle("active", isActive); 363 if (isActive) activeOption = data; 364 }); 365 366 if (activeOption) { 367 headerText.textContent = activeOption.label; 368 } 369 370 optionsList.addEventListener( 371 "click", 372 (event) => { 373 const option = event.target.closest("li"); 374 if (!option || !optionsList.contains(option)) return; 375 376 const data = getOptionData(option); 377 if (!data) return; 378 379 // Stop the legacy index.js LI handler before it tries 380 // querySelector('span').textContent on this anchor-based markup. 381 event.preventDefault(); 382 event.stopPropagation(); 383 384 window.location.assign(data.link.href); 385 }, 386 true, 387 ); 388 389 sortSelect.dataset.sortInit = "true"; 390 return true; 391 } 392 393 if (document.readyState === "loading") { 394 document.addEventListener("DOMContentLoaded", initCatalogSort); 395 } else { 396 initCatalogSort(); 397 } 398 } 399)();
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.