1(function () { 2 function isUnavailableProductCard(card) { 3 if (!card) return false; 4 const availabilityText = (card.querySelector('.card__availability')?.textContent || '').trim().toLowerCase(); 5 return card.classList.contains('not-available') 6 || /Ð½ÐµÑ Ð² налиÑии|оÑÑÑÑÑÑв|под заказ/.test(availabilityText); 7 } 8 9 function Ajax(module, method, callback) { 10 return function (data) { 11 if (method === 'catalog.basket.add' && data?.id) { 12 const card = document.querySelector(`.card[data-product-id="${data.id}"]`); 13 if (isUnavailableProductCard(card)) { 14 showUnavailableProductNotice(card); 15 return Promise.resolve({status: 'unavailable'}); 16 } 17 } 18 19 return new Promise(function (resolve) { 20 21 BX.ajax.runAction(module + '.' + method, { 22 data: data 23 }).then(function (response) { 24 //коÑÑÑÐ»Ñ 25 if (method == 'catalog.basket.update') { 26 if (data['id'] && data['quantity'] == 0) { 27 $("[data-product-id=" + data['id'] + "]").removeClass('added-to-cart'); 28 } 29 } 30 31 if (callback) callback(response); 32 resolve(response); 33 }); 34 }) 35 } 36 } 37 38 let bu_core = function (method, callback) { 39 return Ajax('bu:tools.api', method, callback); 40 } 41 42 window.bu_ajax = { 43 44 discount: { 45 rent: bu_core('catalog.discount.rent') 46 }, 47 reviews: { 48 add: bu_core('reviews.add'), 49 }, 50 rent: { 51 create: bu_core('rent.create') 52 }, 53 oneclick: { 54 create: bu_core('oneclick.create') 55 }, 56 basket: { 57 quick: bu_core('catalog.basket.add', function () { 58 location.href = '/quick-order/'; 59 }), 60 add: bu_core('catalog.basket.add', function (response) { 61 bu_livecircle.update_small_basket(response); 62 if (response && response.status === 'success' && window.SiteMetrika) { 63 window.SiteMetrika.reach('cart_add', {}, {dedupeMs: 1000, dedupeKey: 'cart_add'}); 64 } 65 }), 66 update: bu_core('catalog.basket.update', bu_livecircle.update_small_basket), 67 clear: bu_core('catalog.basket.clear', bu_livecircle.update_small_basket), 68 property: bu_core('catalog.basket.property') 69 }, 70 faq: { 71 add: bu_core('faq.add') 72 }, 73 subscribe: { 74 add: bu_core('subscription.add') 75 }, 76 user: { 77 auth: bu_core('user.auth'), 78 register: bu_core('user.register'), 79 registerCompany: bu_core('user.registerCompany'), 80 registerFast: bu_core('user.registerFast'), 81 requestPassword: bu_core('user.requestPassword'), 82 changePassword: bu_core('user.changePassword') 83 } 84 } 85 86})(); 87 88// ÐбÑÐ°Ñ Ð¿ÑовеÑка доÑÑÑпноÑÑи Ð´Ð»Ñ Ð¾Ð±ÑабоÑÑиков каÑÑоÑек и inline-кнопок. 89function isUnavailableProductCard(card) { 90 if (!card) return false; 91 const availabilityText = (card.querySelector('.card__availability')?.textContent || '').trim().toLowerCase(); 92 return card.classList.contains('not-available') 93 || /Ð½ÐµÑ Ð² налиÑии|оÑÑÑÑÑÑв|под заказ/.test(availabilityText); 94} 95 96//ÐзбÑанное 97 98document.addEventListener('DOMContentLoaded', function () { 99 BX.ajax({ 100 url: '/local/ajax/favorites_handler.php', 101 data: { 'action': 'getFavorites' }, 102 method: 'POST', 103 dataType: 'json', 104 onsuccess: function (response) { 105 if (response.status === 'success') { 106 const favorites = response.favorites; 107 updateFavoriteIcons(favorites); 108 updateCounter(response.favorites.length); 109 } 110 }, 111 onfailure: function () { 112 console.error("ÐÑоизоÑла оÑибка пÑи полÑÑении избÑаннÑÑ "); 113 } 114 }); 115}); 116 117function updateFavoriteIcons(favorites) { 118 updateButtons('.card__save', 'data-favid', favorites); 119 updateButtons('.product-buttons__item_favorites', 'data-compid', favorites); 120 updateButtons('.catalog-label.liked', 'data-favid', favorites, true); 121} 122 123function updateButtons(selector, attrName, favorites, isCatalogLabel = false) { 124 document.querySelectorAll(selector).forEach((button) => { 125 const productId = button.getAttribute(attrName); 126 const svgUse = button.querySelector('use'); 127 const textElement = button.querySelector('.hide'); 128 129 if (favorites.includes(parseInt(productId, 10))) { 130 button.classList.add('in-favorite'); 131 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#saved-icon'); 132 if (textElement) { 133 textElement.innerHTML = 'УбÑаÑÑ Ð¸Ð· избÑанного'; 134 } 135 136 if (isCatalogLabel) { 137 const pElement = button.querySelector('p'); 138 if (pElement) { 139 pElement.textContent = 'УбÑаÑÑ Ð¸Ð·'; 140 } 141 } 142 } else { 143 button.classList.remove('in-favorite'); 144 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#liked-icon'); 145 if (textElement) { 146 textElement.innerHTML = 'ÐобавиÑÑ Ð² избÑанное'; 147 } 148 149 if (isCatalogLabel) { 150 const pElement = button.querySelector('p'); 151 if (pElement) { 152 pElement.textContent = 'РизбÑанное'; 153 } 154 } 155 } 156 }); 157} 158 159const favoriteButtons = document.querySelectorAll('.catalog-label.liked'); 160 161favoriteButtons.forEach((favoriteButton) => { 162 favoriteButton.addEventListener('click', function () { 163 const productId = this.getAttribute('data-favid'); 164 toggleFavorite(productId, this); 165 }); 166}); 167 168function toggleFavorite(productId, buttonElement) { 169 const svgUse = buttonElement.querySelector('use'); 170 const isCatalogLabel = buttonElement.classList.contains('catalog-label'); 171 172 BX.ajax({ 173 url: '/local/ajax/favorites_handler.php', 174 data: { 175 'action': 'toggle', 176 'product_id': productId 177 }, 178 method: 'POST', 179 dataType: 'json', 180 onsuccess: function (response) { 181 if (response.status === 'success') { 182 const pElement = buttonElement.querySelector('p'); 183 184 if (response.action === 'added') { 185 window.SiteMetrika?.reach('favorite_add', {product_id: productId}, { 186 dedupeMs: 1000, 187 dedupeKey: 'favorite_add:' + productId 188 }); 189 buttonElement.classList.add('in-favorite'); 190 if (svgUse) { 191 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#saved-icon'); 192 } 193 194 if (isCatalogLabel && pElement) { 195 pElement.textContent = 'УбÑаÑÑ Ð¸Ð·'; 196 } 197 198 let addedBlockFav = document.querySelector('.addedFav'); 199 addedBlockFav.classList.add('show'); 200 setTimeout(function () { addedBlockFav.classList.remove('show') }, 3000); 201 202 203 } else { 204 buttonElement.classList.remove('in-favorite'); 205 if (svgUse) { 206 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#liked-icon'); 207 } 208 209 if (isCatalogLabel && pElement) { 210 pElement.textContent = 'РизбÑанное'; 211 } 212 213 214 let removedBlockFav = document.querySelector('.removedFav'); 215 removedBlockFav.classList.add('show'); 216 setTimeout(function () { removedBlockFav.classList.remove('show') }, 3000); 217 218 } 219 220 updateCounter(response.favorites.length); 221 222 } else { 223 console.error('ÐÑибка пÑи добавлении/Ñдалении ÑоваÑа из избÑанного: ', response.message); 224 } 225 }, 226 onfailure: function (error) { 227 console.error('ÐÑоизоÑла оÑибка: ', error); 228 } 229 }); 230} 231 232 233function updateCounter(count) { 234 const counterElements = document.querySelectorAll('.count-saved'); 235 236 if (counterElements.length === 0) { 237 return; 238 } 239
240 counterElements.forEach((element) => { 241 element.textContent = count; 242 if (count > 0) { 243 element.style.opacity = '1'; 244 } else { 245 element.style.opacity = '0'; 246 } 247 }); 248} 249 250function addClickListener(selector, attrName) { 251 document.querySelectorAll(selector).forEach((button) => { 252 button.addEventListener('click', function (e) { 253 const productId = this.getAttribute(attrName); 254 const svgUse = this.querySelector('use'); 255 const textElement = this.querySelector('.hide'); 256 const isFavorited = svgUse.getAttribute('xlink:href') === '/local/templates/main/img/sprite.svg#liked-icon'; 257 258 BX.ajax({ 259 url: '/local/ajax/favorites_handler.php', 260 data: { 261 'action': 'toggle', 262 'product_id': productId 263 }, 264 method: 'POST', 265 dataType: 'json', 266 onsuccess: function (response) { 267 if (response.status === 'success') { 268 if (response.action === 'added') { 269 window.SiteMetrika?.reach('favorite_add', {product_id: productId}, { 270 dedupeMs: 1000, 271 dedupeKey: 'favorite_add:' + productId 272 }); 273 button.classList.add('in-favorite'); 274 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#saved-icon'); 275 if (textElement) { 276 textElement.innerHTML = 'УбÑаÑÑ Ð¸Ð· избÑанного'; 277 } 278 let addedBlockFav = document.querySelector('.addedFav'); 279 addedBlockFav.classList.add('show'); 280 setTimeout(function () { addedBlockFav.classList.remove('show') }, 3000); 281 } else { 282 button.classList.remove('in-favorite'); 283 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#liked-icon'); 284 if (textElement) { 285 textElement.innerHTML = 'ÐобавиÑÑ Ð² избÑанное'; 286 } 287 let removedBlockFav = document.querySelector('.removedFav'); 288 removedBlockFav.classList.add('show'); 289 setTimeout(function () { removedBlockFav.classList.remove('show') }, 3000); 290 } 291 292 if (document.getElementById('favorite')) { 293 const cardElement = document.querySelector(`[data-
293product-id="${productId}"]`); 294 console.log(productId); 295 if (cardElement) { 296 cardElement.remove(); 297 } 298 } 299 updateCounter(response.favorites.length); 300 301 if (response.favorites.length === 0) { 302 const savedSection = document.querySelector('.saved'); 303 if (savedSection) { 304 savedSection.innerHTML = ` 305 <div class="saved-empty"> 306 <p class="saved-empty__desc">ÐÑ ÐµÑе не добавили ни одного ÑоваÑа в избÑанное</p> 307 <a href="/catalog/mezhkomnatnye_dveri/massiv/" class="saved-empty__link btn">ÐеÑейÑи в каÑалог</a> 308 </div> 309 `; 310 } 311 } 312 } else { 313 if (isFavorited) { 314 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#liked-icon'); 315 } else { 316 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#saved-icon'); 317 } 318 updateCounter(response.favorites.length); 319 } 320 }, 321 onfailure: function () { 322 alert("ÐÑоизоÑла оÑибка"); 323 if (isFavorited) { 324 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#liked-icon'); 325 } else { 326 svgUse.setAttribute('xlink:href', '/local/templates/main/img/sprite.svg#saved-icon'); 327 } 328 } 329 }); 330 }); 331 }); 332} 333 334addClickListener('.card__save', 'data-favid'); 335addClickListener('.product-buttons__item_favorites', 'data-compid'); 336 337 338 339 340 341//СÑавнение 342 343document.addEventListener('DOMContentLoaded', function () { 344 BX.ajax({ 345 url: '/local/ajax/compare_handler.php', 346 data: { 'action': 'init' }, 347 method: 'POST', 348 dataType: 'json', 349 onsuccess: function (response) { 350 if (response.status === 'success') { 351 const compareList = Object.values(response.compare).map(Number); 352 updateCompareIcons(compareList); 353 updateCounterCompare(compareList.length); 354 } 355 }, 356 onfailure: function () { 357 console.error("ÐÑоизоÑла оÑибка пÑи ÑабоÑе Ñ ÑÑавнением"); 358 } 359 }); 360 361 362 const clearButton = document.querySelector('.catalog-label.delete'); 363 364 if (clearButton) { 365 clearButton.addEventListener('click', function () { 366 BX.ajax({ 367 url: '/local/ajax/compare_handler.php', 368 data: { 369 'action': 'clear' 370 }, 371 method: 'POST', 372 dataType: 'json', 373 onsuccess: function (response) { 374 if (response.status === 'success') { 375 const compareSection = document.querySelector('.compare'); 376 377 if (compareSection) { 378 compareSection.innerHTML = ` 379 <div class="saved-empty"><p class="saved-empty__desc">ÐÑ ÐµÑе не добавили ни одного ÑоваÑа в ÑÑавнение</p> 380 <a href="/catalog/mezhkomnatnye_dveri/massiv/" class="saved-empty__link btn">ÐеÑейÑи в каÑалог</a></div>`; 381 } 382 let removedBlockCompare = document.querySelector('.removedCompare'); 383 removedBlockCompare.classList.add('show'); 384 setTimeout(function () { removedBlockCompare.classList.remove('show') }, 3000); 385 386 updateCompareIcons([]); 387 updateCounterCompare(0); 388 } else { 389 console.error('ÐÑибка пÑи оÑиÑÑке ÑпиÑка: ', response.message); 390 } 391 }, 392 onfailure: function (error) { 393 console.error('ÐÑоизоÑла оÑибка: ', error); 394 } 395 }); 396 }); 397 } 398}); 399 400const deleteButtons = document.querySelectorAll('.del-item'); 401 402deleteButtons.forEach((deleteButton) => { 403 deleteButton.addEventListener('click', function () { 404 const productId = this.getAttribute('data-id'); 405 removeProductFromFavorites(productId, this); 406 }); 407}); 408 409function removeProductFromFavorites(productId, buttonElement) { 410 BX.ajax({ 411 url: '/local/ajax/compare_handler.php', 412 data: { 413 'action': 'remove', 414 'product_id': productId 415 }, 416 method: 'POST', 417 dataType: 'json', 418 onsuccess: function (response) { 419 if (response.status === 'success') { 420 window.location.reload(); 421 } else { 422 console.error('ÐÑибка пÑи Ñдалении ÑоваÑа: ', response.message); 423 } 424 }, 425 onfailure: function (error) { 426 console.error('ÐÑоизоÑла оÑибка: ', error); 427 } 428 }); 429} 430 431 432function updateCompareIcons(compareList) { 433 if (!Array.isArray(compareList)) { 434 console.error('compareList должен бÑÑÑ Ð¼Ð°ÑÑивом. ÐолÑÑено:', compareList); 435 return; 436 } 437 438 document.querySelectorAll('.card__compare').forEach((button) => { 439 updateButton(button, compareList, 'data-compid'); 440 }); 441 442 document.querySelectorAll('.product-buttons__item-compare').forEach((button) => { 443 updateButton(button, compareList, 'data-favid'); 444 }); 445} 446 447function updateButton(button, compareList, dataAttribute) { 448 const productId = parseInt(button.getAttribute(dataAttribute), 10); 449 const textElement = button.querySelector('span'); 450 451 if (compareList.includes(productId)) { 452 button.classList.add('in-compare'); 453 if (textElement) { 454 textElement.textContent = 'УбÑаÑÑ Ð¸Ð· ÑÑавнениÑ'; 455 } 456 } else { 457 button.classList.remove('in-compare'); 458 if (textElement) { 459 textElement.textContent = 'ÐобавиÑÑ Ð² ÑÑавнение'; 460 } 461 } 462} 463 464function updateCounterCompare(count) { 465 const counterElement = document.getElementById('count-compare'); 466 const counterElementMob = document.querySelector('.count-compare'); 467 468 if (!counterElement) { 469 return; 470 } 471 472 counterElement.textContent = count; 473 counterElement.style.opacity = count > 0 ? '1' : '0'; 474 counterElementMob.textContent = count; 475 counterElementMob.style.opacity = count > 0 ? '1' : '0'; 476} 477 478document.querySelectorAll('.card__compare, .product-buttons__item-compare').forEach((button) => { 479 button.addEventListener('click', function (e) { 480 const productId = this.getAttribute('data-favid') || this.getAttribute('data-compid'); 481 482 BX.ajax({ 483 url: '/local/ajax/compare_handler.php', 484 data: { 485 'action': 'toggle', 486 'product_id': productId 487 }, 488 method: 'POST', 489 dataType: 'json', 490 onsuccess: function (response) { 491 if (response.status === 'success') { 492 const compareList = Object.values(response.compare).map(Number); 493 if (compareList.includes(parseInt(productId, 10))) { 494 window.SiteMetrika?.reach('compare_add', {product_id: productId}, { 495 dedupeMs: 1000, 496 dedupeKey: 'compare_add:' + productId 497 }); 498 } 499 updateCompareIcons(compareList); 500 updateCounterCompare(compareList.length); 501 if (!button.classList.contains('in-compare')) { 502 let removedBlockCompare = document.querySelector('.removedCompare'); 503 removedBlockCompare.classList.add('show'); 504 setTimeout(function () { removedBlockCompare.classList.remove('show') }, 3000); 505 } 506 else { 507 let addedBlockCompare = document.querySelector('.addedCompare'); 508 addedBlockCompare.classList.add('show'); 509 setTimeout(function () { addedBlockCompare.classList.remove('show') }, 3000); 510 } 511 } 512 }, 513 onfailure: function (error) { 514 console.error("ÐÑоизоÑла оÑибка: ", JSON.stringify(error)); 515 alert("ÐÑоизоÑла оÑибка: " + JSON.stringify(error)); 516 } 517 }); 518 }); 519}); 520 521 522 523 524 525//РабоÑа Ñ ÐºÐ¾Ñзиной 526 527 528//ÐÑделение и Ñдаление ÑоваÑов 529document.addEventListener('DOMContentLoaded', function () { 530 const selectAllCheckbox = document.querySelector('input[name="basketAll"]'); 531 const itemCheckboxes = document.querySelectorAll('input[name="basketChooseItem"]'); 532 533 if (selectAllCheckbox) { 534 selectAllCheckbox.addEventListener('change', function () {
535 itemCheckboxes.forEach(function (checkbox) { 536 checkbox.checked = selectAllCheckbox.checked; 537 }); 538 }); 539 } 540}); 541 542 543// Удаление вÑбÑаннÑÑ ÑоваÑов 544const deleteButton = document.querySelector('.basket-content__delete'); 545 546if (deleteButton) { 547 deleteButton.addEventListener('click', function () { 548 const selectedIds = Array.from(document.querySelectorAll('input[name="basketChooseItem"]:checked')).map(input => input.getAttribute('data-id')); 549 if (selectedIds.length === 0) return; 550 551 BX.ajax({ 552 url: '/local/ajax/dell_cart.php', 553 data: { 554 'action': 'delete', 555 'idsToDelete': selectedIds 556 }, 557 method: 'POST', 558 dataType: 'json', 559 onsuccess: function (response) { 560 if (response.status === 'success') { 561 selectedIds.forEach(id => { 562 const item = document.getElementById(`basket-item-${id}`); 563 if (response.deletedCount === response.remainingCount) { 564 replaceBasketBlock(); 565 } 566 if (item) { 567 item.remove(); 568 } 569 recalculateBasket(); 570 }); 571 } else { 572 console.error('ÐÑибка ÑдалениÑ: ', response.message); 573 } 574 }, 575 onfailure: function (error) { 576 console.error('ÐÑоизоÑла оÑибка: ', error); 577 } 578 }); 579 }); 580} 581 582 583function recalculateBasket() { 584 const basketItems = document.querySelectorAll('.basket-card[data-entity="basket-item"]'); 585 let totalItemsSum = 0; 586 587 basketItems.forEach(item => { 588 // Ð¢Ð¾Ð²Ð°Ñ ÑÑÑановки оÑÑаеÑÑÑ Ð² заказе как оÑмеÑка клиенÑа, но никогда 589 // не ÑÑаÑÑвÑÐµÑ Ð² визÑалÑном пеÑеÑÑеÑе коÑзинÑ. 590 if (item.getAttribute('data-product-id') === '11542') { 591 return; 592 } 593 const priceElement = item.querySelector('.basket-item-price-current-text'); 594 const price = priceElement ? parseFloat(priceElement.textContent.replace(/\s+/g, '').replace('â½', '')) : 0; 595 596 totalItemsSum += price; 597 }); 598 599 const totalItemsElement = document.querySelector('.basket-total__item-right'); 600 if (totalItemsElement && totalItemsSum !== 0) { 601 totalItemsElement.textContent = totalItemsSum.toLocaleString('ru-RU') + ' â½'; 602 } 603 604 const totalPriceElement = document.querySelector('.basket-total__price'); 605 if (totalPriceElement && totalItemsSum !== 0) { 606 totalPriceElement.textContent = totalItemsSum.toLocaleString('ru-RU') + ' â½'; 607 } 608} 609 610document.addEventListener('DOMContentLoaded', recalculateBasket); 611 612 613//ÐаÑÑоÑка ÑоваÑа// 614 615 616//Ðобавление в коÑÐ·Ð¸Ð½Ñ 617 618function getProductPrice() { 619 function parsePriceText(el) { 620 const t = (el?.textContent || el?.innerText || '') 621 const parsed = parseFloat(t.replace(/\u00A0/g, ' ').replace(/\s+/g, '').replace(/[^\d.,]/g, '').replace(',', '.')) || 0 622 return parsed 623 } 624 625 const checked = document.querySelector('input[name="productType"]:checked') 626 627 if (checked) { 628 if (checked.id === 'productType1') { 629 const checkedSetItems = document.querySelectorAll( 630 '.product-set__blocks input[type="checkbox"]:checked' 631 ); 632 const setTotal = Array.from(checkedSetItems).reduce((sum, input) => { 633 return sum + parsePriceText(input.dataset.price); 634 }, 0); 635 636 if (setTotal > 0) { 637 return setTotal; 638 } 639 } 640 641 const wrap = checked.closest('.product-sides__item') 642 const priceEl = wrap ? wrap.querySelector('.product-choose__price, .product-sides__price') : null 643 if (priceEl) { 644 const val = parsePriceText(priceEl) 645 return val 646 } 647 } 648 649 const single = document.querySelector('.product-sides .product-choose__price, .product-sides__price, .product-choose__prices .product-choose__price') 650 651 if (single) { 652 const val = parsePriceText(single) 653 return val 654 } 655 656 return 0 657} 658 659function parseProductPrice(value) { 660 return parseFloat(String(value || '') 661 .replace(/\u00A0/g, ' ') 662 .replace(/\s+/g, '') 663 .replace(/[^\d,.-]/g, '') 664 .replace(',', '.')) || 0; 665} 666 667function formatProductPrice(price) { 668 return new Intl.NumberFormat('ru-RU', { 669 maximumFractionDigits: 0 670 }).format(price) + ' â½'; 671} 672 673function updateSetPrice(fallbackPrice) { 674 const setPrice = document.getElementById('set_price'); 675 if (!setPrice) { 676 return; 677 } 678 679 const offerPrice = Number(fallbackPrice) || 0; 680 const mainSetCheckbox = document.getElementById('setParams_main'); 681 const mainSetBlock = mainSetCheckbox?.closest('.product-set__block'); 682 const mainSetTotal = mainSetBlock?.querySelector('.total'); 683 684 if (offerPrice > 0 && mainSetCheckbox) { 685 mainSetCheckbox.dataset.price = String(offerPrice); 686 687 // СÑаÑÑй обÑабоÑÑик комплекÑа ÑиÑÐ°ÐµÑ Ð·Ð½Ð°Ñение ÑеÑез jQuery.data(), 688 // поÑÑÐ¾Ð¼Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»Ñем и DOM-аÑÑибÑÑ, и внÑÑÑенний ÐºÐµÑ jQuery. 689 if (window.jQuery) { 690 window.jQuery(mainSetCheckbox).data('price', offerPrice); 691 } 692 } 693 694 if (offerPrice > 0 && mainSetTotal) { 695 mainSetTotal.textContent = formatProductPrice(offerPrice); 696 } 697 698 const basicTotalElement = document.getElementById('total_price_tele'); 699 const basicSetContent = basicTotalElement?.closest('.product-set__content'); 700 const itemPrices = Array.from( 701 basicSetContent?.querySelectorAll('.product-set__item .right') || [] 702 ); 703 const calculatedPrice = itemPrices.reduce((sum, item) => sum + parseProductPrice(item.textContent), 0); 704 const basicTotal = calculatedPrice > 0 ? calculatedPrice : offerPrice; 705 706 const customTotal = Array.from( 707 document.querySelectorAll('.product-set__blocks input[type="checkbox"]:checked') 708 ).reduce((sum, input) => sum + parseProductPrice(input.dataset.price), 0); 709 const activeSetTotal = customTotal > 0 ? customTotal : basicTotal; 710 711 if (activeSetTotal > 0) { 712 setPrice.textContent = formatProductPrice(activeSetTotal); 713 setPrice.classList.add('is-price-ready'); 714 } 715 716 if (basicTotalElement && basicTotal > 0) { 717 basicTotalElement.textContent = formatProductPrice(basicTotal); 718 } 719 720 const customTotalElement = document.getElementById('costum_summ'); 721 if (customTotalElement && activeSetTotal > 0) { 722 customTotalElement.textContent = formatProductPrice(activeSetTotal); 723 } 724 725 ['pricebase', 'pbase'].forEach(id => { 726 const input = document.getElementById(id); 727 if (input && activeSetTotal > 0) {
728 input.value = String(activeSetTotal); 729 } 730 }); 731} 732 733function setProductCartAvailability(isUnavailable) { 734 const cartButton = document.querySelector('.product-sides__cart'); 735 if (!cartButton) { 736 return; 737 } 738 739 cartButton.classList.toggle('is-cart-price-unavailable', isUnavailable); 740 cartButton.dataset.priceUnavailable = isUnavailable ? 'Y' : 'N'; 741 cartButton.setAttribute('aria-disabled', isUnavailable ? 'true' : 'false'); 742} 743 744function updateOfferPrice() { 745 const selectedOffer = document.querySelector('.sizesku input[name="productSize"]:checked'); 746 const canvasPrice = document.getElementById('polotno'); 747 const telescopicPrice = document.getElementById('price_teles'); 748 749 if (!selectedOffer || (!canvasPrice && !telescopicPrice)) { 750 return; 751 } 752 753 const offerId = selectedOffer.value; 754 if (!offerId) { 755 return; 756 } 757 758 [canvasPrice, telescopicPrice].filter(Boolean).forEach(priceElement => { 759 priceElement.classList.add('is-loading-price'); 760 priceElement.classList.remove('is-price-ready', 'is-price-unavailable'); 761 }); 762 setProductCartAvailability(true); 763 764 fetch('/local/ajax/get_offer_price.php', { 765 method: 'POST', 766 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, 767 body: 'offerId=' + encodeURIComponent(offerId) 768 }) 769 .then(response => { 770 if (!response.ok) { 771 throw new Error('HTTP ' + response.status); 772 } 773 774 return response.json(); 775 }) 776 .then(data => { 777 if (!data.success) { 778 throw new Error(data.message || 'Ðе ÑдалоÑÑ Ð¿Ð¾Ð»ÑÑиÑÑ ÑÐµÐ½Ñ ÑоваÑа.'); 779 } 780 781 selectedOffer.dataset.price = String(data.price); 782 selectedOffer.dataset.printPrice = data.formattedPrice; 783 [canvasPrice, telescopicPrice].filter(Boolean).forEach(priceElement => { 784 priceElement.textContent = data.formattedPrice; 785 priceElement.classList.add('is-price-ready'); 786 }); 787 updateSetPrice(Number(data.price)); 788 setProductCartAvailability(false); 789 }) 790 .catch(error => { 791 console.error('Ðе ÑдалоÑÑ Ð¾Ð±Ð½Ð¾Ð²Ð¸ÑÑ ÑÐµÐ½Ñ ÑоваÑа.', error); 792 [canvasPrice, telescopicPrice].filter(Boolean).forEach(priceElement => { 793 priceElement.textContent = 'Цена ÑÑоÑнÑеÑÑÑ'; 794 priceElement.classList.add('is-price-ready', 'is-price-unavailable'); 795 }); 796 setProductCartAvailability(true); 797 }) 798 .finally(() => { 799 [canvasPrice, telescopicPrice].filter(Boolean).forEach(priceElement => { 800 priceElement.classList.remove('is-loading-price'); 801 }); 802 }); 803} 804 805document.addEventListener('change', function (event) { 806 if (event.target.matches('.sizesku input[name="productSize"]')) { 807 updateOfferPrice(); 808 } 809}); 810 811document.addEventListener('DOMContentLoaded', updateOfferPrice); 812 813 814 815function parseQty(v) { 816 const m = String(v).match(/[\d]+(?:[.,]\d+)?/); 817 return m ? parseFloat(m[0].replace(',', '.')) : 1; 818} 819 820 821let cartButton = document.querySelector('.product-sides__cart'); 822 823if (cartButton) { 824 cartButton.addEventListener('click', function () { 825 if (cartButton.dataset.priceUnavailable === 'Y') { 826 return; 827 } 828 829 let selectedItems = []; 830 let productID, productName; 831 832 let activeSize = document.querySelector('.sizesku input[type="radio"]:checked'); 833 if (activeSize) { 834 productID = activeSize.value; 835 productName = activeSize.getAttribute('data-name'); 836 } else { 837 productID = cartButton.dataset.id; 838 let productNameElement = document.querySelector('.product-title, .product-sides__title, .card__name'); 839 productName = productNameElement ? productNameElement.innerText.trim() : "ТоваÑ"; 840 } 841 842 let productPrice = getProductPrice(activeSize); 843 844 selectedItems.push({ 845 name: productName, 846 price: productPrice, 847 id: productID 848 }); 849 850 let productTypeRadio = document.querySelector('input[name="productType"]:checked'); 851 if (productTypeRadio && productTypeRadio.id === "productType1") { 852 let productSetContainer = document.querySelector('.product-set__blocks'); 853 let checkboxes = productSetContainer.querySelectorAll('input[type="checkbox"]:checked');
854 checkboxes.forEach(function (checkbox) { 855 let block = checkbox.closest('.product-set__block'); 856 let itemName = block.querySelector('.name span').innerText; 857 let itemPrice = parseFloat(block.querySelector('.total').innerHTML.replace(' â½', '').replace(/ /g, '').trim()); 858 const isMoulding = block.classList.contains('long-block'); 859 let itemCount = isMoulding 860 ? parseQty(block.querySelector('.basket-card-total__complect')?.innerText) 861 : parseQty(block.querySelector('.count')?.innerText); 862 863 let item = { 864 NAME: itemName, 865 PRICE: itemPrice, 866 COUNT: itemCount, 867 ID: checkbox.getAttribute('data-id'), 868 SPEC: block.classList.contains('long-block') ? "" : "Y" 869 }; 870 selectedItems.push(item); 871 }); 872 } 873 874 875 let productType = productTypeRadio 876 ? (productTypeRadio.id === "productType2" ? "canvas" : "set") 877 : "catalog"; 878 879 addToCart(selectedItems, productType, productID, 'add'); 880 }); 881} 882 883 884 885 886 887let additionalItems = []; 888 889document.querySelectorAll('.access-block__add').forEach(button => { 890 button.addEventListener('click', function () { 891 const id = this.closest('.access-block__item').getAttribute('data-id'); 892 const name = this.closest('.access-block__item').getAttribute('data-name'); 893 const price = this.closest('.access-block__item').getAttribute('data-price'); 894 895 this.classList.add('show', 'active'); 896 897 addToCart([{ id: id, name: name, price: price }], 'additional', id); 898 }); 899}); 900 901 902document.addEventListener('click', function (event) { 903 const button = event.target.closest('.card__to-cart, .card__plus, .card__minus'); 904 if (!button) return; 905 906 907 908 let card, productID, productName, productPriceElement, productPriceText, productPrice, cardActions, cardTotalElement, cardTotal; 909 910 card = button.closest('.card'); 911 912 if (card) { 913 if (isUnavailableProductCard(card) && (button.classList.contains('card__to-cart') || button.classList.contains('card__plus'))) { 914 event.preventDefault(); 915 showUnavailableProductNotice(card); 916 return; 917 } 918 919 productID = card.getAttribute('data-product-id'); 920 productName = card.querySelector('.card__name').textContent.trim(); 921 productPriceElement = card.querySelector('.card__price'); 922 productPriceText = productPriceElement.textContent.replace('â½', '').replace(/\s/g, '').trim(); 923 productPrice = parseFloat(productPriceText); 924 cardActions = card.querySelector('.card__actions'); 925 cardTotalElement = card.querySelector('.card__total'); 926 cardTotal = parseInt(cardTotalElement.textContent, 10); 927 } else { 928 card = button.closest('.product-nav__container'); 929 if (!card) return; 930 931 productID = card.querySelector('[data-id]').getAttribute('data-id'); 932 productName = card.querySelector('.product-nav__title h3').textContent.trim(); 933 productPriceElement = card.querySelector('.card__price'); 934 productPriceText = productPriceElement.textContent.replace('â½', '').replace(/\s/g, '').trim(); 935 productPrice = parseFloat(productPriceText); 936 } 937 938 let items = [{ 939 id: productID, 940 name: productName, 941 price: productPrice 942 }]; 943 944 if (button.classList.contains('card__to-cart')) { 945 addToCart(items, 'catalog', productID); 946 card.classList.add('added-to-cart'); 947 if (cardActions) cardActions.classList.add('up'); 948 } else if (button.classList.contains('card__plus')) { 949 cardTotal++; 950 addToCart(items, 'catalog', productID, 'add'); 951 cardTotalElement.textContent = cardTotal; 952 } else if (button.classList.contains('card__minus')) { 953 if (cardTotal > 1) { 954 cardTotal--; 955 addToCart(items, 'catalog', productID, 'remove'); 956 cardTotalElement.textContent = cardTotal; 957 } else { 958 addToCart(items, 'catalog', productID, 'remove'); 959 card.classList.remove('added-to-cart'); 960 if (cardActions) cardActions.classList.remove('up'); 961 } 962 } 963}); 964 965function showUnavailableProductNotice(card) { 966 const message = 'ÐаннÑй ÑÐ¾Ð²Ð°Ñ Ð²Ñеменно оÑÑÑÑÑÑвÑеÑ. ÐÑÑавÑÑе заÑвкÑ, и Ð¼Ñ Ð¿Ð¾Ð´Ð±ÐµÑÑм Ð´Ð»Ñ Ð²Ð°Ñ Ð°Ð½Ð°Ð»Ð¾Ð³Ð¸ÑнÑÑ Ð¼Ð¾Ð´ÐµÐ»Ñ'; 967 let notice = document.querySelector('.unavailable-product-notice'); 968 969 if (!notice) { 970 notice = document.createElement('div'); 971 notice.className = 'unavailable-product-notice'; 972 document.body.appendChild(notice); 973 } 974 975 notice.textContent = message; 976 notice.classList.add('show'); 977 clearTimeout(notice.hideTimer); 978 notice.hideTimer = setTimeout(() => notice.classList.remove('show'), 5000); 979 980 openUnavailableRequestModal(card); 981} 982 983function openUnavailableRequestModal(card) { 984 const productId = card?.getAttribute('data-product-id') || ''; 985 const productName = card?.querySelector('.card__name')?.textContent.trim() || ''; 986 let modal = document.querySelector('.unavailable-request-modal'); 987 988 if (!modal) { 989 modal = document.createElement('div'); 990 modal.className = 'modal unavailable-request-modal'; 991 document.body.appendChild(modal); 992 } 993 994 const escapeHtml = (value) => String(value).replace(/[&<>'"]/g, (char) => ({ 995 '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' 996 }[char])); 997 998 modal.innerHTML = ` 999 <div class="modal__container container"> 1000 <form class="modal-form unavailable-request-form"> 1001 <button class="modal__close btn-reset" data-modal-close type="button" aria-label="ÐакÑÑÑÑ"> 1002 <svg><use xlink:href="/local/templates/main/img/sprite.svg#reset-icon"></use></svg> 1003 </button>
1004 <span class="h2 modal-form__title">ÐÑÑавÑÑе заÑвкÑ</span> 1005 <p class="modal-form__subtitle">ÐодбеÑÑм аналогиÑнÑÑ Ð¼Ð¾Ð´ÐµÐ»Ñ Ð²Ð¼ÐµÑÑо ÑоваÑа:</p> 1006 <p class="unavailable-request-product">${escapeHtml(productName)}</p> 1007 <div class="modal-form__inputs"> 1008 <input class="input modal-form__input" name="name" type="text" placeholder="ÐаÑе имÑ*" required> 1009 <input class="input modal-form__input" name="email" type="email" placeholder="E-mail*" required> 1010 <input class="input modal-form__input" name="phone" type="tel" placeholder="ТелеÑон"> 1011 </div> 1012 <label class="order-form__checkbox checkbox-block"> 1013 <span>СоглаÑаÑÑÑ Ð½Ð° обÑабоÑÐºÑ Ð¿ÐµÑÑоналÑнÑÑ Ð´Ð°Ð½Ð½ÑÑ </span> 1014 <input type="checkbox" name="terms" value="Y" required> 1015 <span class="checkmark"></span> 1016 </label> 1017 <input type="hidden" name="product_id" value="${escapeHtml(productId)}"> 1018 <input type="hidden" name="product_name" value="${escapeHtml(productName)}"> 1019 <input type="hidden" name="sessid" value="${escapeHtml(window.BX?.bitrix_sessid?.() || '')}"> 1020 <button class="modal-form__submit btn btn-reset" type="submit">ÐÑпÑавиÑÑ Ð·Ð°ÑвкÑ</button> 1021 <p class="unavailable-request-error" role="alert"></p> 1022 </form> 1023 </div>`; 1024 1025 modal.classList.add('show'); 1026 document.body.classList.add('no-scrolling'); 1027 1028 const close = () => { 1029 modal.classList.remove('show'); 1030 document.body.classList.remove('no-scrolling'); 1031 }; 1032 1033 modal.querySelector('[data-modal-close]').addEventListener('click', close); 1034 modal.onclick = (event) => { 1035 if (event.target === modal || event.target.classList.contains('modal__container')) close(); 1036 }; 1037 1038 modal.querySelector('form').addEventListener('submit', (event) => { 1039 event.preventDefault(); 1040 const form = event.currentTarget; 1041 const submit = form.querySelector('button[type="submit"]'); 1042 const error = form.querySelector('.unavailable-request-error'); 1043 submit.disabled = true; 1044 error.textContent = ''; 1045 1046 fetch('/local/ajax/unavailable_request.php', { 1047 method: 'POST', 1048 body: new FormData(form) 1049 }) 1050 .then(response => response.json()) 1051 .then(data => { 1052 if (data.status !== 'success') throw new Error(data.message || 'Ðе ÑдалоÑÑ Ð¾ÑпÑавиÑÑ Ð·Ð°ÑвкÑ'); 1053 form.innerHTML = ` 1054 <button class="modal__close btn-reset" data-modal-close type="button" aria-label="ÐакÑÑÑÑ"> 1055 <svg><use xlink:href="/local/templates/main/img/sprite.svg#reset-icon"></use></svg> 1056 </button> 1057 <span class="h2 modal-form__title">СпаÑибо за заÑвкÑ!</span> 1058 <p class="modal-form__subtitle">ÐÑ Ð¿Ð¾Ð´Ð±ÐµÑÑм аналогиÑнÑÑ Ð¼Ð¾Ð´ÐµÐ»Ñ Ð¸ оÑпÑавим подÑвеÑждение на ваÑÑ Ð¿Ð¾ÑÑÑ.</p>`; 1059 form.querySelector('[data-modal-close]').addEventListener('click', close); 1060 }) 1061 .catch((requestError) => { 1062 error.textContent = requestError.message; 1063 submit.disabled = false; 1064 }); 1065 }, {once: true}); 1066} 1067 1068 1069document.addEventListener('click', function (event) { 1070 const button = event.target.closest('.basket-card__plus, .basket-card__minus, .basket-card__delete, .basket-card__delete-mobile'); 1071 if (!button) return; 1072 1073 if ((button.classList.contains('basket-card__plus') && button.classList.contains('basket-card-plus__complect')) || (button.classList.contains('basket-card__minus') && button.classList.contains('basket-card-minus__complect'))) { 1074 return; 1075 } 1076 1077 if (button.classList.contains('basket-card__delete') || button.classList.contains('basket-card__delete-mobile')) { 1078 // СоÑÑавлÑÑÑÐ°Ñ ÐºÐ¾Ð¼Ð¿Ð»ÐµÐºÑа ÑдалÑеÑÑÑ Ñвоим оÑделÑнÑм обÑабоÑÑиком. 1079 if (button.closest('.basket-card__block')) { 1080 return; 1081 } 1082 1083 const basketCard = button.closest('.basket-card'); 1084 const basketItemId = basketCard?.getAttribute('data-id'); 1085 if (!basketItemId) return; 1086 1087 event.preventDefault(); 1088 BX.ajax({ 1089 url: '/local/ajax/dell_cart.php', 1090 data: { 1091 action: 'delete', 1092 idsToDelete: [basketItemId] 1093 }, 1094 method: 'POST', 1095 dataType: 'json', 1096 onsuccess: function (response) { 1097 if (response.status === 'success') { 1098 // ÐеÑеÑиÑовÑваем коÑзин
1098Ñ, ÑÑÐ¾Ð±Ñ ÑÐ¸Ð½Ñ Ñонно обновилиÑÑ ÑоÑÑÐ°Ð²Ñ Ð¸ иÑоги. 1099 window.location.reload(); 1100 } else { 1101 console.error('ÐÑибка ÑдалениÑ: ', response.message); 1102 } 1103 }, 1104 onfailure: function (error) { 1105 console.error('ÐÑоизоÑла оÑибка: ', error); 1106 } 1107 }); 1108 return; 1109 } 1110 1111 if (button.classList.contains('basket-card__plus') || button.classList.contains('basket-card__minus')) { 1112 const basketCard = button.closest('.basket-card'); 1113 1114 if (!basketCard) return; 1115 const productId = basketCard.getAttribute('data-product-id'); 1116 const productName = basketCard.querySelector('.basket-card__name').textContent.trim(); 1117 const productPrice = parseFloat(basketCard.getAttribute('data-product-price')); 1118 const quantityField1 = basketCard.querySelector('.costum_total'); 1119 const quantityField2 = basketCard.querySelector('.costum_total2'); 1120 1121 if (!quantityField1 && !quantityField2) return; 1122 1123 let quantity1 = quantityField1 ? parseInt(quantityField1.value, 10) : 0; 1124 let quantity2 = quantityField2 ? parseInt(quantityField2.value, 10) : 0; 1125 1126 1127 let action; 1128 if (button.classList.contains('basket-card__plus')) { 1129 quantity1++; 1130 quantity2++; 1131 action = 'add'; 1132 } else if (button.classList.contains('basket-card__minus')) { 1133 if (quantity1 > 1) { 1134 quantity1--; 1135 } else { 1136 removeFromCart(productId); 1137 } 1138 if (quantity2 > 1) { 1139 quantity2--; 1140 } 1141 action = 'remove'; 1142 } 1143 1144 const priceElement = basketCard.querySelector('.basket-card__price'); 1145 if (priceElement) { 1146 const currentPriceText = priceElement.textContent.trim(); 1147 const currentPrice = parseFloat(currentPriceText.replace(/\s+/g, '').replace('â½', '')); 1148 let newPrice; 1149 1150 if (action === 'add') { 1151 newPrice = currentPrice + productPrice; 1152 } else if (action === 'remove') { 1153 newPrice = currentPrice - productPrice; 1154 } 1155 1156 priceElement.textContent = newPrice.toLocaleString('ru-RU') + ' â½'; 1157 } 1158 1159 1160 1161 addToCart([{ id: productId, name: productName, price: productPrice }], 'catalog', productId, action, quantity1, quantity2); 1162 1163 1164 1165 if (quantityField1) { 1166 quantityField1.value = quantity1; 1167 } 1168 1169 if (quantityField2) { 1170 quantityField2.value = quantity2; 1171 } 1172 recalculateBasket(); 1173 } else if (button.classList.contains('basket-card__delete')) { 1174 const basketCard = button.closest('.basket-card'); 1175 if (basketCard) { 1176 const productId = basketCard.getAttribute('data-product-id'); 1177 const productName = basketCard.querySelector('.basket-card__name').textContent.trim(); 1178 const productPrice = parseFloat(basketCard.getAttribute('data-product-price')); 1179 const isDveriProduct = basketCard.getAttribute('data-dveri') === 'Y'; 1180 1181 1182 const itemPriceElement = document.getElementById('basket-item-sum-price-3118'); 1183 if (itemPriceElement) { 1184 const currentItemPriceText = itemPriceElement.textContent.trim(); 1185 const currentItemPrice = parseFloat(currentItemPriceText.replace(/\s+/g, '').replace('â½', '')); 1186 const newItemPrice = currentItemPrice - productPrice; 1187 itemPriceElement.textContent = newItemPrice.toLocaleString('ru-RU') + ' â½'; 1188 } 1189 1190 const totalElement = document.querySelector('.basket-total__price'); 1191 if (totalElement) { 1192 const currentTotalText = totalElement.textContent.trim(); 1193 let currentTotal = parseFloat(currentTotalText.replace(/\s+/g, '').replace('â½', '')); 1194 if (isDveriProduct) { 1195 currentTotal -= 1700; 1196 } 1197 const newTotal = currentTotal - productPrice;
1198 totalElement.textContent = newTotal.toLocaleString('ru-RU') + ' â½'; 1199 } 1200 1201 const totalItemRightElement = document.querySelector('.basket-total__item-right'); 1202 if (totalItemRightElement) { 1203 const currentTotalItemRightText = totalItemRightElement.textContent.trim(); 1204 const currentTotalItemRight = parseFloat(currentTotalItemRightText.replace(/\s+/g, '').replace('â½', '')); 1205 const newTotalItemRight = currentTotalItemRight - productPrice; 1206 totalItemRightElement.textContent = newTotalItemRight.toLocaleString('ru-RU') + ' â½'; 1207 } 1208 1209 recalculateBasket(); 1210 1211 addToCart([{ id: productId, name: productName, price: productPrice }], 'catalog', productId, 'remove_all', 10); 1212 removeFromCart(productId); 1213 } 1214 } else if (button.classList.contains('basket-card__delete-mobile')) { 1215 const basketCard = button.closest('.basket-card'); 1216 if (basketCard) { 1217 const productId = basketCard.getAttribute('data-product-id'); 1218 const productName = basketCard.querySelector('.basket-card__name').textContent.trim(); 1219 const productPrice = parseFloat(basketCard.getAttribute('data-product-price')); 1220 1221 1222 const itemPriceElement = document.getElementById('basket-item-sum-price-3118'); 1223 if (itemPriceElement) { 1224 const currentItemPriceText = itemPriceElement.textContent.trim(); 1225 const currentItemPrice = parseFloat(currentItemPriceText.replace(/\s+/g, '').replace('â½', '')); 1226 const newItemPrice = currentItemPrice - productPrice; 1227 itemPriceElement.textContent = newItemPrice.toLocaleString('ru-RU') + ' â½'; 1228 } 1229 1230 const totalElement = document.querySelector('.basket-total__price'); 1231 if (totalElement) { 1232 const currentTotalText = totalElement.textContent.trim(); 1233 const currentTotal = parseFloat(currentTotalText.replace(/\s+/g, '').replace('â½', '')); 1234 const newTotal = currentTotal - productPrice; 1235 totalElement.textContent = newTotal.toLocaleString('ru-RU') + ' â½'; 1236 } 1237 1238 const totalItemRightElement = document.querySelector('.basket-total__item-right'); 1239 if (totalItemRightElement) { 1240 const currentTotalItemRightText = totalItemRightElement.textContent.trim(); 1241 const currentTotalItemRight = parseFloat(currentTotalItemRightText.replace(/\s+/g, '').replace('â½', '')); 1242 const newTotalItemRight = currentTotalItemRight - productPrice; 1243 totalItemRightElement.textContent = newTotalItemRight.toLocaleString('ru-RU') + ' â½'; 1244 } 1245 1246 addToCart([{ id: productId, name: productName, price: productPrice }], 'catalog', productId, 'remove_all', 10); 1247 removeFromCart(productId); 1248 } 1249 } 1250}); 1251 1252function removeFromCart(productId) { 1253 const basketCard = document.querySelector(`.basket-card[data-product-id="${productId}"]`); 1254 if (basketCard) { 1255 basketCard.remove(); 1256 } 1257} 1258 1259 1260function addToCart(items, type, mainProductId, action = 'add') { 1261 let formData = new FormData(); 1262 1263 formData.append('mainProduct', mainProductId); 1264 1265 if (type === 'catalog') { 1266 formData.append('action', action); 1267 formData.append('productType', 'catalog'); 1268 formData.append('notes', JSON.stringify(items)); 1269 } else { 1270 if (type !== 'additional') { 1271 let customProductID = document.querySelector('input[name="productSize"]:checked')?.value || ''; 1272 let customProductName = document.querySelector('input[name="productSize"]:checked')?.getAttribute('data-name') || ''; 1273 let customProductPriceEl; 1274 1275 const checkedType = document.querySelector('input[name="productType"]:checked'); 1276 1277 if (checkedType) { 1278 const wrap = checkedType.closest('.product-sides__item'); 1279 customProductPriceEl = wrap 1280 ? wrap.querySelector('.product-choose__price, .product-sides__price') 1281 : null; 1282 } 1283 1284 let customProductPrice = customProductPriceEl 1285 ? customProductPriceEl.textContent.replace(/\D/g, '') 1286 : 0; 1287 1288 formData.append('customProductPrice', customProductPrice); 1289 formData.append('customProductID', customProductID); 1290 formData.append('customProductName', customProductName); 1291 } 1292 1293 let setProducts = items.map(item => item.id); 1294 1295 formData.append('setProducts', JSON.stringify(setProducts)); 1296 formData.append('productType', type); 1297 formData.append('action', action); 1298 1299 if (type !== 'additional') { 1300 items = items.slice(1); 1301 } 1302 1303 formData.append('notes', JSON.stringify(items)); 1304 } 1305 1306 fetch('/local/ajax/add_cart.php', { 1307 method: 'POST', 1308 body: formData 1309 }) 1310 .then(response => { 1311 if (!response.ok) { 1312 throw new Error('ÐÑибка ÑеÑвеÑа: ' + response.statusText); 1313 } 1314 1315 return response.json(); 1316 }) 1317 .then(data => { 1318 if (data.status !== 'success') { 1319 throw new Error(data.message || 'ÐÑибка пÑи изменении коÑзин
1319Ñ.'); 1320 } 1321 1322 if (action === 'add') { 1323 window.SiteMetrika?.reach('cart_add', {product_id: mainProductId, product_type: type}, { 1324 dedupeMs: 1000, 1325 dedupeKey: 'cart_add:' + mainProductId 1326 }); 1327 } 1328 1329 const notification = action === 'remove' 1330 ? document.querySelector('.removedCart') 1331 : document.querySelector('.addedCart'); 1332 1333 notification?.classList.add('show'); 1334 1335 $('#cart').load('/local/ajax/mini_basket.php', { ajax: 'Y' }, function () { 1336 checkHiddenProduct().catch(function (error) { 1337 console.error('Ðе ÑдалоÑÑ Ð¿ÑовеÑиÑÑ ÑлÑжебнÑÑ Ð¿Ð¾Ð·Ð¸ÑÐ¸Ñ ÐºÐ¾ÑзинÑ:', error); 1338 }); 1339 }); 1340 1341 setTimeout(() => { 1342 notification?.classList.remove('show'); 1343 }, 3000); 1344 }) 1345 .catch(error => { 1346 console.error('ÐÑибка:', error); 1347 alert(error.message || 'ÐÑоизоÑла оÑибка пÑи изменении коÑзинÑ.'); 1348 }); 1349} 1350 1351 1352 1353 1354document.addEventListener("DOMContentLoaded", function () { 1355 let deleteButtons = document.querySelectorAll('.basket-card__block-delete'); 1356 1357 deleteButtons.forEach(function (button) { 1358 button.addEventListener('click', function () { 1359 let card = button.closest('.basket-card'); 1360 let productId = card.dataset.id; 1361 1362 let partId = button.closest('.basket-card__block').dataset.id; 1363 1364 let partPrice = parseFloat(button.closest('.basket-card__block').querySelector('.basket-card__block-total').textContent.replace(/\D/g, '')); 1365 1366 fetch('/local/ajax/delete_complect.php', { 1367 method: 'POST', 1368 body: JSON.stringify({ 1369 action: 'delete_product_part', 1370 product_id: productId, 1371 part_id: partId, 1372 partPrice: partPrice 1373 }), 1374 headers: { 1375 'Content-Type': 'application/json' 1376 } 1377 }) 1378 .then(response => response.json()) 1379 .then(data => { 1380 if (data.status === 'success') { 1381 button.closest('.basket-card__block').remove(); 1382 1383 let totalSpan = card.querySelector('.basket-item-price-current-text'); 1384 let currentTotal = parseFloat(totalSpan.textContent.replace(/\D/g, '')); 1385 totalSpan.textContent = formatPrice(currentTotal - partPrice) + ' â½'; 1386 1387 let overallTotalSpans = document.querySelectorAll('.basket-total__price, .basket-total__item-right'); 1388 overallTotalSpans.forEach(span => { 1389 let overallTotal = parseFloat(span.textContent.replace(/\D/g, '')); 1390 span.textContent = formatPrice(overallTotal - partPrice) + ' â½'; 1391 }); 1392 } else { 1393 alert('ÐÑибка пÑи Ñдалении ÑаÑÑи ÑоваÑа.'); 1394 } 1395 }) 1396 .catch(error => { 1397 console.error('ÐÑибка:', error); 1398 alert('ÐÑоизоÑла оÑибка пÑи Ñдалении ÑаÑÑи ÑоваÑа.'); 1399 }); 1400 }); 1401 }); 1402 1403 1404 1405 document.querySelectorAll('.basket-card__block-actions button').forEach(function (button) { 1406 button.addEventListener('click', function () { 1407 let actionContainer = button.closest('.basket-card__block-actions'); 1408 let totalSpan = actionContainer.querySelector('.basket-card-total__complect'); 1409 let currentQuantity = parseInt(totalSpan.textContent); 1410 let priceSpan = button.closest('.basket-card__block').querySelector('.basket-card__block-total'); 1411 let unitPrice = parseFloat(priceSpan.textContent.replace(/\D/g, '')) / currentQuantity; 1412 1413 if (button.classList.contains('card__minus') && currentQuantity > 1) { 1414 currentQuantity -= 1; 1415 } else if (button.classList.contains('card__plus')) { 1416 currentQuantity += 1; 1417 } else { 1418 return; 1419 } 1420 1421 totalSpan.textContent = currentQuantity; 1422 priceSpan.textContent = formatPrice(unitPrice * currentQuantity) + ' â½'; 1423 1424 let card = button.closest('.basket-card'); 1425 let totalCardSpan = card.querySelector('.basket-item-price-current-text'); 1426 let currentTotalCard = parseFloat(totalCardSpan.textContent.replace(/\D/g, '')); 1427 totalCardSpan.textContent = formatPrice(currentTotalCard + (button.classList.contains('card__minus') ? -unitPrice : unitPrice)) + ' â½'; 1428 1429 let overallTotalSpans = document.querySelectorAll('.basket-total__price, .basket-total__item-right'); 1430 overallTotalSpans.forEach(span => { 1431 let overallTotal = parseFloat(span.textContent.replace(/\D/g, '')); 1432 span.textContent = formatPrice(overallTotal + (button.classList.contains('card__minus') ? -unitPrice : unitPrice)) + ' â½'; 1433 }); 1434 1435 let productId = card.dataset.id; 1436 let partId = button.closest('.basket-card__block').dataset.id; 1437 let updatedQuantity = currentQuantity; 1438 1439 fetch('/local/ajax/delete_complect.php', { 1440 method: 'POST', 1441 body: JSON.stringify({ 1442 action: 'update_product_part_quantity', 1443 product_id: productId, 1444 part_id: partId, 1445 quantity: updatedQuantity 1446 }), 1447 headers: { 1448 'Content-Type': 'application/json' 1449 } 1450 }) 1451 .then(response => response.json()) 1452 .then(data => { 1453 if (data.status !== 'success') { 1454 alert('ÐÑибка пÑи обновлении колиÑеÑÑва ÑоваÑа на ÑеÑвеÑе.'); 1455 } 1456 }) 1457 .catch(error => { 1458 console.error('ÐÑибка:', error); 1459 alert('ÐÑоизоÑла оÑибка пÑи обновлении колиÑеÑÑва ÑоваÑа.'); 1460 }); 1461 }); 1462 }); 1463 1464 1465 1466 1467 1468}); 1469 1470function formatPrice(price) { 1471 return price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " "); 1472} 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483//ÐаÑенка в коÑзине 1484 1485let setAllDoorsCheckbox = $("input[name='setAllDoors']"); 1486let ustanovkaBlock = $('.ustanovka'); 1487let hiddenProductSum = 0; 1488 1489let requestInProgress = false;
1490let hiddenProductCheckPromise = null; 1491 1492// ФÑнкÑÐ¸Ñ Ð´Ð»Ñ ÑпÑÐ°Ð²Ð»ÐµÐ½Ð¸Ñ ÑкÑÑÑÑм ÑоваÑом 1493function manageHiddenProduct(action) { 1494 return new Promise((resolve, reject) => { 1495 if (requestInProgress) { 1496 return reject('Request is already in progress'); 1497 } 1498 1499 requestInProgress = true; 1500 1501 $.ajax({ 1502 url: "/local/ajax/pr.php", 1503 type: "POST", 1504 data: { action: action }, 1505 dataType: "json", 1506 success: function (response) { 1507 resolve(response); 1508 }, 1509 error: function (xhr) { 1510 reject(xhr.responseText); 1511 }, 1512 complete: function () { 1513 requestInProgress = false; 1514 } 1515 }); 1516 }); 1517} 1518 1519 1520function checkHiddenProduct() { 1521 if (hiddenProductCheckPromise) { 1522 return hiddenProductCheckPromise; 1523 } 1524 1525 hiddenProductCheckPromise = new Promise((resolve, reject) => { 1526 $.ajax({ 1527 url: "/local/ajax/check_hidden_product.php", 1528 type: "POST", 1529 dataType: "json", 1530 success: function (response) { 1531 if (!response || response.success === false) { 1532 reject(response?.error || 'Ðе ÑдалоÑÑ Ð¿ÑовеÑиÑÑ ÑлÑжебнÑÑ Ð¿Ð¾Ð·Ð¸ÑÐ¸Ñ ÐºÐ¾ÑзинÑ'); 1533 return; 1534 } 1535 1536 hiddenProductSum = 0; 1537 updateUstanovkaBlock('0 â½'); 1538 1539 updateBasketCount(response.positionsCountAll); 1540 1541 setAllDoorsCheckbox.prop('checked', response.hasHiddenProduct); 1542 response.hasHiddenProduct ? ustanovkaBlock.addClass('show') : ustanovkaBlock.removeClass('show'); 1543 resolve(hiddenProductSum); 1544 recalculateBasket(); 1545 }, 1546 error: function (xhr) { 1547 const responseMessage = xhr.responseJSON?.error || xhr.responseText || 'ÐÑибка пÑовеÑки коÑзинÑ'; 1548 console.error("ÐÑоизоÑла оÑибка: " + responseMessage); 1549 reject(responseMessage); 1550 }, 1551 complete: function () { 1552 hiddenProductCheckPromise = null; 1553 } 1554 }); 1555 }); 1556 1557 return hiddenProductCheckPromise; 1558} 1559 1560 1561function updateBasketCount(count) { 1562 let basketCountElements = document.querySelectorAll('.actions__label.basket-head-count'); 1563 1564 basketCountElements.forEach(function (basketCountElement) { 1565 basketCountElement.textContent = count; 1566 1567 if (count === 0) { 1568 replaceBasketBlock(); 1569 } 1570 }); 1571} 1572 1573function replaceBasketBlock() { 1574 const newBasketBlock = document.createElement('section'); 1575 newBasketBlock.className = 'basket'; 1576 newBasketBlock.innerHTML = ` 1577 <div class="basket__container"> 1578 <div class="breadcrumbs"> 1579 <ul class="breadcrumbs__list list-reset"> 1580 <li><a href="/">ÐлавнаÑ</a></li> 1581 <li>ÐоÑзина</li> 1582 </ul> 1583 </div> 1584 <div class="basket__head" id="basket-items-list-wrapper" data-entity="basket-items-list-header"> 1585 <h1 class="basket__title section-title to-left">ÐоÑзина</h1> 1586 </div> 1587 <section class="compare compare-page"> 1588 <div class="saved-empty cart-empty"> 1589 <svg> 1590 <use xlink:href="/local/templates/main/img/sprite.svg#cart-icon"></use> 1591 </svg> 1592 <p class="saved-empty__desc">ÐÑ ÐµÑе не добавили ни одного ÑоваÑа в коÑзинÑ</p> 1593 <a href="/product-category/mezhkomnatnye-dveri/" class="saved-empty__link btn">ÐеÑейÑи в каÑалог</a> 1594 </div> 1595 </section> 1596 </div> 1597 `; 1598 1599 const oldBasketBlock = document.querySelector('.basket'); 1600 1601 if (oldBasketBlock) { 1602 oldBasketBlock.parentNode.replaceChild(newBasketBlock, oldBasketBlock); 1603 } 1604} 1605 1606 1607function updateUstanovkaBlock(sum) { 1608 if (!sum || sum === "0 â½") { 1609 ustanovkaBlock.removeClass('show'); 1610 return; 1611 } 1612 ustanovkaBlock.find('.basket-total__item-right').html(sum); 1613} 1614 1615 1616setAllDoorsCheckbox.on("change", function () { 1617 let isChecked = $(this).is(":checked"); 1618 1619 if (isChecked) { 1620 manageHiddenProduct('add') 1621 .then(() => checkHiddenProduct()) 1622 .then((sum) => { 1623 ustanovkaBlock.addClass('show'); 1624 recalculateBasket(); 1625 }) 1626 .catch(error => { 1627 console.error("ÐÑоизоÑла оÑибка:", error); 1628 }); 1629 } else { 1630 manageHiddenProduct('remove') 1631 .then(() => { 1632 ustanovkaBlock.removeClass('show'); 1633 recalculateBasket(); 1634 }) 1635 .catch(error => { 1636 console.error("ÐÑоизоÑла оÑибка:", error); 1637 }); 1638 } 1639}); 1640 1641 1642 1643 1644$(document).ready(function () { 1645 checkHiddenProduct().catch(function (error) { 1646 console.error('Ðе ÑдалоÑÑ Ð¿ÑовеÑиÑÑ ÑлÑжебнÑÑ Ð¿Ð¾Ð·Ð¸ÑÐ¸Ñ ÐºÐ¾Ñзин
1646Ñ:', error); 1647 }); 1648}); 1649 1650 1651 1652 1653//ÐÐ°Ð»Ñ ÐºÐ¾Ñзина 1654 1655 1656 1657 1658 1659 1660 1661 1662//ÐокÑпка в 1 клик 1663let buyOneClickButton = $('.basket-total__one, .product-sides__click'); 1664let modalByOneClick = $('#modalByOneClick'); 1665 1666buyOneClickButton.on('click', function () { 1667 modalByOneClick.addClass('show'); 1668 1669 modalByOneClick.attr('aria-hidden', 'false').focus(); 1670}); 1671 1672$('.card__one-click').on('click', function () { 1673 let $this = $(this); 1674 modalByOneClick.addClass('show'); 1675 modalByOneClick.attr('aria-hidden', 'false').focus(); 1676 1677 modalByOneClick.data('productID', $this.data('id')); 1678 modalByOneClick.data('productType', $this.data('product_type')); 1679}); 1680 1681$(document).on('click', '.js-onelick-submit', function (e) { 1682 e.preventDefault(); 1683 1684 let $button = $(this); 1685 let form = document.getElementById('modal-form3'); 1686 1687 if (!form) { 1688 alert('ФоÑма не найдена'); 1689 return; 1690 } 1691 1692 if (!form.checkValidity()) { 1693 form.reportValidity(); 1694 return; 1695 } 1696 1697 if ($button.data('requestRunning')) { 1698 return; 1699 } 1700 1701 $button.data('requestRunning', true); 1702 $button.prop('disabled', true); 1703 1704 let productID; 1705 let productType; 1706 1707 let selectedProductSize = $('input[name="productSize"]:checked').val(); 1708 let selectedProductType = $('input[name="productType"]:checked').val(); 1709 1710 if (selectedProductSize && selectedProductType) { 1711 productID = selectedProductSize; 1712 productType = selectedProductType; 1713 } else { 1714 productID = modalByOneClick.data('productID'); 1715 productType = modalByOneClick.data('productType'); 1716 } 1717 1718 let formData = $('#modal-form3').serialize(); 1719 1720 formData += '&PRODUCT_TYPE=' + encodeURIComponent(productType || ''); 1721 formData += '&PRODUCT_ID=' + encodeURIComponent(productID || ''); 1722 1723 let basketExists = $('.basket__container').length > 0 ? 'Y' : 'N'; 1724 1725 formData += '&BASKET=' + basketExists; 1726 1727 $.ajax({ 1728 url: '/local/ajax/one_order.php', 1729 type: 'POST', 1730 data: formData, 1731 dataType: 'json', 1732 success: function (response) { 1733 if (response.success) { 1734 window.SiteMetrika?.formSuccess('oneclick_ok', { 1735 product_id: productID, 1736 order_id: response.orderID || '' 1737 }); 1738 $('#modalByOneClick').removeClass('show'); 1739 $('#thanksOrderProduct').addClass('show'); 1740 1741 $('#thanksOrderProduct .modal-form__subtitle').text( 1742 'â заказа ' + response.orderID + ' вÑÑлан на ваÑÑ ÑлекÑÑоннÑÑ Ð¿Ð¾ÑÑÑ' 1743 ); 1744 1745 form.reset(); 1746 } else { 1747 alert( 1748 'ÐÑибка пÑи Ñоздании заказа: ' + 1749 (response.error || 'ÐеизвеÑÑÐ½Ð°Ñ Ð¾Ñибка') 1750 ); 1751 } 1752 }, 1753 error: function (jqXHR, textStatus) { 1754 alert('ÐÑибка AJAX-запÑоÑа: ' + textStatus); 1755 }, 1756 complete: function () { 1757 $button.data('requestRunning', false); 1758 $button.prop('disabled', false); 1759 } 1760 }); 1761}); 1762 1763 1764//ÐÑÑкое 1765const editButton = document.querySelector('.checkout-basket__edit'); 1766 1767if (editButton) { 1768 editButton.addEventListener('click', function () { 1769 window.location.href = '/personal/basket/'; 1770 }); 1771} 1772 1773$('.product-sides__cart.btn.btn-reset.close-bt-n').on('click', function () { 1774 $('#productMore').removeClass('show'); 1775 $('input[type="radio"][name="productType"][id="productType1"]').prop("checked", true); 1776}); 1777 1778 1779function transferErrorMessage() { 1780 1781 let userAuth = document.getElementById('userAuth'); 1782 1783 if (userAuth.innerHTML.includes('ÐевеÑнÑй логин или паÑолÑ')) { 1784 let registerError = document.getElementById('bx_register_error'); 1785 registerError.style.display = ''; 1786 registerError.querySelector('font.errortext').textContent = 'ÐевеÑнÑй логин или паÑолÑ.'; 1787 } 1788} 1789 1790document.addEventListener('DOMContentLoaded', transferErrorMessage); 1791 1792ymaps.ready(initMapForPickup); 1793 1794function initMapForPickup() { 1795 var mapElement = document.getElementById('mapNoDev2'); 1796 if (mapElement) { 1797 let myMap = new ymaps.Map('mapNoDev2', { 1798 center: [56.318276, 43.920090], 1799 zoom: 16, 1800 controls: [] 1801 }); 1802 1803 let myPlacemark = new ymaps.Placemark([56.318276, 43.920090], {}, { 1804 iconLayout: 'default#image', 1805 iconImageHref: '/local/templates/main/img/marker.png', 1806 iconImageSize: [24, 40] 1807 }); 1808 myMap.geoObjects.add(myPlacemark); 1809 1810 var CustomControlClass = function (options) { 1811 CustomControlClass.superclass.constructor.call(this, options); 1812 this._$content = null; 1813 this._geocoderDeferred = null; 1814 }; 1815 1816 ymaps.util.augment(CustomControlClass, ymaps.collection.Item, { 1817 onAddToMap: function (map) { 1818 CustomControlClass.superclass.onAddToMap.call(this, map); 1819 this._lastCenter = null; 1820 this.getParent().getChildElement(this).then(this._onGetChildElement, this); 1821 }, 1822 1823 onRemoveFromMap: function (oldMap) { 1824 if (this._$content) { 1825 this._$content.remove(); 1826 this._mapEventGroup.removeAll(); 1827 } 1828 CustomControlClass.superclass.onRemoveFromMap.call(this, oldMap); 1829 }, 1830 1831 _onGetChildElement: function (parentDomContainer) { 1832 this._$content = $( 1833 '<div class="info-card">' + 1834 '<div class="info-card-header">' + 1835 '<span>ÐоÑковÑкое ÑоÑÑе, 83</span>' + 1836 '</div>' + 1837 '<div class="info-card-body">' + 1838 '<div class="hz">' + 1839 '<div>ÐÑÐµÐ¼Ñ ÑабоÑÑ:</div>' + 1840 '<div class="mapcont">ÐнâÐÑ:<br>9:00 â 18:00<br><br>Сб:<br>10:00 â 15:00</div>' + 1841 '</div>' + 1842 '<div class="hz">' + 1843 '<div>ТелеÑон:</div>' + 1844 '<div class="mapcont">8 (831) 2915763</div>' + 1845 '</div>' + 1846 '</div>' + 1847 '</div>' 1848 ).appendTo(parentDomContainer); 1849 this._mapEventGroup = this.getMap().events.group(); 1850 this._$content.find('.close-button').on('click', this._onCloseClick.bind(this)); 1851 }, 1852 1853 }); 1854 1855 1856 var customControl = new CustomControlClass(); 1857 myMap.controls.add(customControl, { 1858 float: 'none', 1859 position: { 1860 top: 10, 1861 left: 10 1862 } 1863 }); 1864 } 1865} 1866document.addEventListener("DOMContentLoaded", function () { 1867 let phoneInputs = document.getElementsByClassName('phoneNum'); 1868
1869 Array.from(phoneInputs).forEach(function (phoneInput) { 1870 phoneInput.addEventListener('input', function (e) { 1871 let currentValue = e.target.value.replace(/\D/g, ''); 1872 let formattedValue = '+7 '; 1873 1874 if (e.inputType !== 'deleteContentBackward') { 1875 if (currentValue.length > 1) { 1876 formattedValue += '(' + currentValue.slice(1, 4); 1877 } 1878 if (currentValue.length >= 4) { 1879 formattedValue += ') '; 1880 } 1881 if (currentValue.length > 4) { 1882 formattedValue += currentValue.slice(4, 7); 1883 } 1884 if (currentValue.length >= 7) { 1885 formattedValue += '-'; 1886 } 1887 if (currentValue.length > 7) { 1888 formattedValue += currentValue.slice(7, 9); 1889 } 1890 if (currentValue.length >= 9) { 1891 formattedValue += '-'; 1892 } 1893 if (currentValue.length > 9) { 1894 formattedValue += currentValue.slice(9, 11); 1895 } 1896 } else { 1897 formattedValue = '+7 ' + currentValue.slice(1).replace(/(\d{3})(\d{3})(\d{2})(\d{2})/, '($1) $2-$3-$4').trim(); 1898 } 1899 1900 e.target.value = formattedValue; 1901 }); 1902 }); 1903}); 1904 1905 1906document.addEventListener('DOMContentLoaded', function () { 1907 const buttons = document.querySelectorAll('.card__btn[data-modal="custom-product"]'); 1908 buttons.forEach(function (button) { 1909 button.addEventListener('click', function () { 1910 const cardElement = this.closest('.card'); 1911 1912 const productName = cardElement.querySelector('.card__name').textContent; 1913 1914 const inputComment = document.querySelector('input[name="COMMNT"]'); 1915 if (inputComment) { 1916 inputComment.value = productName; 1917 } 1918 }); 1919 }); 1920 1921 const fileInput = document.getElementById('modalFile'); 1922 const fileLabel = fileInput.nextElementSibling; 1923 1924 fileInput.addEventListener('change', function () { 1925 let fileName = this.files[0] ? this.files[0].name : ''; 1926 fileName = truncateString(fileName, 30); 1927 fileLabel.textContent = fileName; 1928 }); 1929}); 1930 1931 1932function truncateString(str, num) { 1933 if (str.length > num) { 1934 return str.slice(0, num > 3 ? num - 3 : num) + '...'; 1935 } 1936 return str; 1937}
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.