1(function () { 2 "use strict"; 3 4 var CONFIG = { 5 shopBase: { 6 de: "/shop", 7 en: "/shop/en" 8 }, 9 themeCssStorageKey: "phos_shop_theme_css", 10 changeQuantityDelay: 800, 11 fetchOptions: { 12 credentials: "include", 13 cache: "no-store" 14 }, 15 ajaxHeaders: { 16 "X-Requested-With": "XMLHttpRequest", 17 Accept: "text/html, application/xhtml+xml" 18 } 19 }; 20 21 var dom = {}; 22 var state = { 23 lang: "de", 24 cartActionPending: false, 25 cartEventsBound: false, 26 quantityDebounceTimer: null 27 }; 28 29 function getShopBase() { 30 return CONFIG.shopBase[state.lang] || CONFIG.shopBase.de; 31 } 32 33 function getCartInfoUrl() { 34 return getShopBase() + "/widgets/checkout/info"; 35 } 36 37 function getCartOffcanvasUrl() { 38 return getShopBase() + "/checkout/offcanvas"; 39 } 40 41 function getShopHomeUrl() { 42 return getShopBase() + "/"; 43 } 44 45 function detectLang() { 46 if (dom.offcanvas) { 47 var attr = dom.offcanvas.getAttribute("data-gawel-shop-lang"); 48 if (attr === "en") { 49 return "en"; 50 } 51 } 52 53 if (document.documentElement.lang === "en") { 54 return "en"; 55 } 56 57 return "de"; 58 } 59 60 function getLoadingText() { 61 return state.lang === "en" ? "Loading â¦" : "Wird geladen â¦"; 62 } 63 64 function getErrorText() { 65 return state.lang === "en" 66 ? "Cart could not be loaded." 67 : "Warenkorb konnte nicht geladen werden."; 68 } 69 70 function cacheDom() { 71 dom.trigger = document.querySelectorAll("[data-gawel-cart-trigger]"); 72 dom.count = document.querySelectorAll("[data-gawel-cart-count]"); 73 dom.offcanvas = document.getElementById("gawelCartOffcanvas"); 74 dom.content = document.querySelector("[data-gawel-cart-content]"); 75 state.lang = detectLang(); 76 } 77 78 function parseHtml(html) { 79 return new DOMParser().parseFromString(html, "text/html"); 80 } 81 82 function resolveShopUrl(url) { 83 if (!url) { 84 return getCartOffcanvasUrl(); 85 } 86 87 if (/^https?:\/\//i.test(url)) { 88 try { 89 var parsed = new URL(url); 90 if (parsed.origin === window.location.origin) { 91 return parsed.pathname + parsed.search; 92 } 93 } catch (error) { 94 return url; 95 } 96 return url; 97 } 98 99 if (url.charAt(0) === "/") { 100 if (/^\/shop(\/en)?\//i.test(url) || url === "/shop" || url === "/shop/en") { 101 return url; 102 } 103 if (/^\/(checkout|widgets|wishlist)\//i.test(url)) { 104 return getShopBase() + url; 105 } 106 return url; 107 } 108 109 return getShopBase() + "/" + url; 110 } 111 112 function parseCartCount(doc) { 113 var countEl = doc.querySelector(".offcanvas-cart-header-count"); 114 if (countEl) { 115 var match = countEl.textContent.match(/(\d+)/); 116 if (match) { 117 return parseInt(match[1], 10); 118 } 119 } 120 121 var items = doc.querySelectorAll( 122 ".offcanvas-cart-items > .line-item, .offcanvas-cart-items > li, .offcanvas-cart .line-item, .offcanvas-cart .cart-item" 123 ); 124 var count = 0; 125 126 items.forEach(function (item) { 127 if (item.classList.contains("is-child") || item.classList.contains("is-nested")) { 128 return; 129 } 130 count += 1; 131 }); 132 133 if (count > 0) { 134 return count; 135 } 136 137 var hidden = doc.querySelector(".hidden-line-items-information"); 138 if (hidden) { 139 var hiddenItems = hidden.querySelectorAll("[data-line-item-id], .hidden-line-item"); 140 if (hiddenItems.length > 0) { 141 return hiddenItems.length; 142 } 143 } 144 145 var emptyAlert = doc.querySelector(".offcanvas-cart .alert"); 146 if (emptyAlert && /leer|empty/i.test(emptyAlert.textContent)) { 147 return 0; 148 } 149 150 return 0; 151 } 152 153 function updateBadge(count) { 154 if (!dom.count || !dom.count.length) { 155 return; 156 } 157 158 var safeCount = Math.max(0, Number(count) || 0); 159 160 dom.count.forEach(function (badge) { 161 badge.textContent = String(safeCount); 162 163 if (safeCount <= 0) { 164 badge.classList.add("is-empty"); 165 badge.setAttribute("hidden", ""); 166 } else { 167 badge.classList.remove("is-empty"); 168 badge.removeAttribute("hidden"); 169 } 170 }); 171 } 172 173 async function fetchCartOffcanvasHtml() { 174 var response = await fetch(getCartOffcanvasUrl(), CONFIG.fetchOptions); 175 if (!response.ok) { 176 throw new Error("Cart offcanvas request failed"); 177 } 178 return response.text(); 179 } 180 181 async function getShopThemeCssUrl() { 182 var storageKey = CONFIG.themeCssStorageKey + "_" + state.lang; 183 184 try { 185 var cached = sessionStorage.getItem(storageKey); 186 if (cached) { 187 return cached; 188 } 189 } catch (error) { 190 /* ignore */ 191 } 192 193 var response = await fetch(getShopHomeUrl(), CONFIG.fetchOptions); 194 if (!response.ok) { 195 throw new Error("Shop theme discovery failed"); 196 } 197 198 var html = await response.text(); 199 var match = html.match(/\/shop\/theme\/[a-f0-9]+\/css\/all\.css[^"'\\s]*/i); 200 if (!match) { 201 throw new Error("Shop theme CSS not found"); 202 } 203 204 try { 205 sessionStorage.setItem(storageKey, match[0]); 206 } catch (error) { 207 /* ignore */ 208 } 209 210 return match[0]; 211 } 212 213 function loadStylesheet(shadow, href) { 214 return new Promise(function (resolve, reject) { 215 var existing = shadow.querySelector('link[data-shop-theme="true"]'); 216 if (existing) { 217 if (existing.sheet) { 218 resolve(existing); 219 return; 220 } 221 existing.addEventListener("load", function () { 222 resolve(existing); 223 }); 224 existing.addEventListener("error", reject); 225 return; 226 } 227 228 var link = document.createElement("link"); 229 link.rel = "stylesheet"; 230 link.href = href; 231 link.setAttribute("data-shop-theme", "true"); 232 link.addEventListener("load", function () { 233 resolve(link); 234 }); 235 link.addEventListener("error", reject); 236 shadow.appendChild(link); 237 }); 238 } 239 240 function ensureThemeStyles(shadow) { 241 var existing = shadow.querySelector('link[data-shop-theme="true"]'); 242 if (existing && existing.sheet) { 243 return Promise.resolve(existing); 244 } 245 246 return getShopThemeCssUrl() 247 .then(function (url) { 248 return loadStylesheet(shadow, url); 249 }) 250 .catch(function (error) { 251 console.warn("Gawel cart theme CSS skipped:", error); 252 return null; 253 }); 254 } 255 256 function getShadowHost() { 257 var host = dom.content.querySelector("[data-gawel-cart-shadow-host]"); 258 if (!host) { 259 dom.content.innerHTML = 260 '<div data-gawel-cart-shadow-host class="phos-gawel-cart-offcanvas__host"></div>'; 261 host = dom.content.querySelector("[data-gawel-cart-shadow-host]"); 262 } 263 return host; 264 } 265 266 function getShadowRoot() { 267 var host = getShadowHost(); 268 if (!host.shadowRoot) { 269 host.attachShadow({ mode: "open" }); 270 ensureShellStyles(host.shadowRoot); 271 } 272 return host.shadowRoot; 273 } 274 275 function ensureShellStyles(shadow) { 276 if (shadow.querySelector("style[data-gawel-cart-shell]")) { 277 return; 278 } 279 280 var style = document.createElement("style"); 281 style.setAttribute("data-gawel-cart-shell", ""); 282 style.textContent = 283 ":host{display:block;font-family:Inter,system-ui,sans-serif;font-size:16px;line-height:1.5;color:#2b3136}" + 284 ".phos-gawel-cart-offcanvas__loading{padding:2rem 1.25rem;text-align:center;color:#6b7280;font-size:.875rem}" + 285 ".phos-gawel-cart-offcanvas__error{padding:2rem 1.25rem;text-align:center;color:#b42318;font-size:.875rem;margin:0}" + 286 ".phos-gawel-cart-offcanvas__updating{opacity:.55;pointer-events:none}"; 287 shadow.appendChild(style); 288 } 289 290 function loadShadowStylesheet(shadow, href, marker) { 291 return new Promise(function (resolve, reject) { 292 var existing = shadow.querySelector('link[data-gawel-asset="' + marker + '"]'); 293 if (existing) { 294 if (existing.sheet) { 295 resolve(existing); 296 return; 297 } 298 existing.addEventListener("load", function () { 299 resolve(existing); 300 }); 301 existing.addEventListener("error", reject); 302 return; 303 } 304 305 var link = document.createElement("link"); 306 link.rel = "stylesheet"; 307 link.href = href; 308 link.setAttribute("data-gawel-asset", marker); 309 link.addEventListener("load", function () { 310 resolve(link); 311 }); 312 link.addEventListener("error", reject); 313 shadow.appendChild(link); 314 }); 315 } 316 317 function ensureEmbedAssets(shadow) { 318 return Promise.all([ 319 loadShadowStylesheet( 320 shadow, 321 "https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap", 322 "font-inter" 323 ), 324 loadShadowStylesheet(shadow, "/css/phos-gawel-shop-cart-embed.css?20260626b", "embed-css") 325 ]).catch(function (error) { 326 console.warn("Gawel cart embed CSS skipped:", error); 327 return null; 328 }); 329 } 330 331 function getShadowMount(shadow) { 332 var mount = shadow.querySelector("[data-gawel-cart-mount]"); 333 if (!mount) { 334 mount = document.createElement("div"); 335 mount.setAttribute("data-gawel-cart-mount", ""); 336 mount.className = 337 "phos-shop-cart-embed is-active-route-frontend-cart-offcanvas"; 338 shadow.appendChild(mount); 339 } 340 return mount; 341 } 342 343 function wrapOffcanvasContent(innerHtml) { 344 return ( 345 '<div class="offcanvas cart-offcanvas">' + 346 '<div class="offcanvas-body">' + 347 innerHtml + 348 "</div></div>" 349 ); 350 } 351 352 function showLoadingInShadow(shadow) { 353 var mount = getShadowMount(shadow); 354 mount.innerHTML = 355 '<div class="phos-gawel-cart-offcanvas__loading">' + getLoadingText() + "</div>"; 356 } 357 358 function showError(message) { 359 var shadow = getShadowRoot(); 360 var mount = getShadowMount(shadow); 361 mount.innerHTML = 362 '<p class="phos-gawel-cart-offcanvas__error">' + message + "</p>"; 363 } 364 365 function setCartUpdating(isUpdating) { 366 var shadow = getShadowRoot(); 367 var mount = getShadowMount(shadow); 368 if (!mount) { 369 return; 370 } 371 372 if (isUpdating) { 373 mount.classList.add("phos-gawel-cart-offcanvas__updating"); 374 return; 375 } 376 377 mount.classList.remove("phos-gawel-cart-offcanvas__updating"); 378 } 379 380 async function refreshCartBadge() { 381 try { 382 var infoResponse = await fetch(getCartInfoUrl(), CONFIG.fetchOptions); 383 384 if (infoResponse.status === 204) { 385 updateBadge(0); 386 return; 387 } 388 389 if (infoResponse.ok) { 390 var infoHtml = await infoResponse.text(); 391 if (infoHtml && infoHtml.trim()) { 392 var infoDoc = parseHtml(infoHtml); 393 var badge = infoDoc.querySelector(".header-cart-badge"); 394 if (badge) { 395 updateBadge(parseInt(badge.textContent, 10) || 0); 396 return; 397 } 398 } 399 } 400 401 var html = await fetchCartOffcanvasHtml(); 402 updateBadge(parseCartCount(parseHtml(html))); 403 } catch (error) { 404 console.error("Gawel cart badge refresh failed:", error); 405 } 406 } 407 408 function extractOffcanvasContent(doc, rawHtml) { 409 var cart = doc.querySelector(".offcanvas-cart"); 410 if (cart) { 411 return cart.outerHTML; 412 } 413 414 var body = doc.querySelector(".offcanvas-body"); 415 if (body) { 416 return body.innerHTML; 417 } 418 419 if (doc.body && doc.body.firstElementChild) { 420 var root = doc.body.firstElementChild; 421 if ( 422 root.classList && 423 (root.classList.contains("offcanvas-cart") || 424 root.classList.contains("offcanvas") || 425 root.querySelector(".offcanvas-cart")) 426 ) { 427 var nestedCart = root.querySelector(".offcanvas-cart"); 428 if (nestedCart) { 429 return nestedCart.outerHTML; 430 } 431 return root.outerHTML; 432 } 433 } 434 435 if (rawHtml && /offcanvas-cart/i.test(rawHtml)) { 436 return rawHtml; 437 } 438 439 return ""; 440 } 441 442 function getFormAction(form) { 443 return (form && form.getAttribute("action")) || ""; 444 } 445 446 function isCartActionForm(form) { 447 if (!form || form.tagName !== "FORM") { 448 return false;
449 } 450 451 if (form.classList.contains("js-offcanvas-cart-remove-product")) { 452 return true; 453 } 454 455 if (form.classList.contains("js-offcanvas-cart-add-promotion")) { 456 return true; 457 } 458 459 var action = getFormAction(form); 460 return /line-item\/(delete|change-quantity)|promotion\/add/i.test(action); 461 } 462 463 function isQuantityControl(element) { 464 if (!element) { 465 return false; 466 } 467 468 return ( 469 element.matches(".js-offcanvas-cart-change-quantity") || 470 element.matches(".js-offcanvas-cart-change-quantity-number") || 471 (element.matches('input[name="quantity"]') && 472 /line-item\/change-quantity/i.test(getFormAction(element.closest("form")))) 473 ); 474 } 475 476 function findQuantityForm(element) { 477 if (!element) { 478 return null; 479 } 480 481 if (element.tagName === "FORM" && /line-item\/change-quantity/i.test(getFormAction(element))) { 482 return element; 483 } 484 485 return element.closest("form"); 486 } 487 488 function appendFormCsrf(formData, form) { 489 if (!form) { 490 return formData; 491 } 492 493 form.querySelectorAll('input[type="hidden"]').forEach(function (input) { 494 if (!input.name) { 495 return; 496 } 497 498 if (!formData.has(input.name)) { 499 formData.append(input.name, input.value); 500 } 501 }); 502 503 return formData; 504 } 505 506 async function postCartForm(form) { 507 var url = resolveShopUrl(getFormAction(form)); 508 var formData = appendFormCsrf(new FormData(form), form); 509 510 var response = await fetch(url, { 511 method: "POST", 512 body: formData, 513 credentials: "include", 514 cache: "no-store", 515 headers: CONFIG.ajaxHeaders 516 }); 517 518 if (!response.ok) { 519 throw new Error("Cart action failed with status " + response.status); 520 } 521 522 return response.text(); 523 } 524 525 function renderOffcanvasHtml(html) { 526 var doc = parseHtml(html); 527 var content = extractOffcanvasContent(doc, html); 528 529 if (!content) { 530 return false; 531 } 532 533 var shadow = getShadowRoot(); 534 var mount = getShadowMount(shadow); 535 mount.innerHTML = wrapOffcanvasContent(content); 536 537 dom.content.classList.add("phos-gawel-cart-offcanvas__content--ready"); 538 updateBadge(parseCartCount(doc)); 539 fixShadowLinks(shadow); 540 541 ensureThemeStyles(shadow); 542 ensureEmbedAssets(shadow); 543 544 return true; 545 } 546 547 async function submitCartForm(form) { 548 if (!form || state.cartActionPending) { 549 return false; 550 } 551 552 state.cartActionPending = true; 553 setCartUpdating(true); 554 555 try { 556 var html = await postCartForm(form); 557 if (!renderOffcanvasHtml(html)) { 558 await reloadOffcanvasContent({ showLoading: false }); 559 } 560 return true; 561 } catch (error) { 562 console.error("Gawel cart form submit failed:", error);
563 try { 564 await reloadOffcanvasContent({ showLoading: false }); 565 } catch (reloadError) { 566 console.error("Gawel cart reload after action failed:", reloadError); 567 } 568 return false; 569 } finally { 570 state.cartActionPending = false; 571 setCartUpdating(false); 572 } 573 } 574 575 function debouncedSubmitCartForm(form) { 576 if (!form) { 577 return; 578 } 579 580 if (state.quantityDebounceTimer) { 581 window.clearTimeout(state.quantityDebounceTimer); 582 } 583 584 state.quantityDebounceTimer = window.setTimeout(function () { 585 state.quantityDebounceTimer = null; 586 submitCartForm(form); 587 }, CONFIG.changeQuantityDelay); 588 } 589 590 function adjustQuantityFromButton(button, direction) { 591 var group = button.closest(".quantity-selector-group, [data-quantity-selector]"); 592 if (!group) { 593 return null; 594 } 595 596 var input = group.querySelector('input[name="quantity"]'); 597 var form = findQuantityForm(group); 598 if (!input || !form) { 599 return null; 600 } 601 602 var step = parseInt(input.getAttribute("step"), 10) || 1; 603 var min = parseInt(input.getAttribute("min"), 10) || 1; 604 var max = parseInt(input.getAttribute("max"), 10) || 9999; 605 var current = parseInt(input.value, 10) || min; 606 var next = direction === "plus" ? current + step : current - step; 607 608 next = Math.max(min, Math.min(max, next)); 609 610 if (next === current) { 611 return null; 612 } 613 614 input.value = String(next); 615 return form; 616 } 617 618 function onShadowSubmit(event) { 619 var form = event.target; 620 if (!isCartActionForm(form)) { 621 return; 622 } 623 624 event.preventDefault(); 625 submitCartForm(form); 626 } 627 628 function onShadowChange(event) { 629 var target = event.target; 630 631 if (!isQuantityControl(target)) { 632 return; 633 } 634 635 var form = findQuantityForm(target); 636 if (!form) { 637 return; 638 } 639 640 if (target.matches(".js-offcanvas-cart-change-quantity")) { 641 submitCartForm(form); 642 return; 643 } 644 645 debouncedSubmitCartForm(form); 646 } 647 648 function onShadowClick(event) { 649 var plusButton = event.target.closest(".btn-plus"); 650 var minusButton = event.target.closest(".btn-minus"); 651 var quantityButton = plusButton || minusButton; 652 653 if (!quantityButton) { 654 return; 655 } 656 657 event.preventDefault(); 658 659 var form = adjustQuantityFromButton( 660 quantityButton, 661 plusButton ? "plus" : "minus" 662 ); 663 664 if (form) { 665 submitCartForm(form); 666 } 667 } 668 669 function fixShadowLinks(shadow) { 670 if (!shadow) { 671 return; 672 } 673 674 shadow.querySelectorAll("a[href]").forEach(function (link) { 675 var href = link.getAttribute("href"); 676 if (!href || href.indexOf("javascript:") === 0 || href === "#") { 677 return; 678 } 679 680 if (/^\/(checkout|wishlist)\//i.test(href)) { 681 link.setAttribute("href", resolveShopUrl(href)); 682 } 683 684 if (/^\/shop\//i.test(href)) { 685 link.setAttribute("target", "_self"); 686 } 687 }); 688 689 shadow.querySelectorAll("[data-bs-dismiss='offcanvas'], .js-offcanvas-close").forEach(function (button) { 690 if (button.getAttribute("data-gawel-bound") === "true") { 691 return; 692 } 693 694 button.setAttribute("data-gawel-bound", "true"); 695 button.addEventListener("click", function (event) { 696 event.preventDefault(); 697 closeOffcanvas(); 698 }); 699 }); 700 } 701 702 function bindOffcanvasInteractions(shadow) { 703 if (!shadow || state.cartEventsBound) { 704 fixShadowLinks(shadow); 705 return; 706 } 707 708 shadow.addEventListener("submit", onShadowSubmit); 709 shadow.addEventListener("change", onShadowChange); 710 shadow.addEventListener("click", onShadowClick); 711 state.cartEventsBound = true; 712 fixShadowLinks(shadow); 713 } 714 715 async function reloadOffcanvasContent(options) { 716 var showLoading = !options || options.showLoading !== false; 717 718 if (!dom.content) { 719 return null; 720 } 721 722 var shadow = getShadowRoot(); 723 724 if (showLoading) { 725 showLoadingInShadow(shadow); 726 } 727 728 var html = await fetchCartOffcanvasHtml(); 729 var doc = parseHtml(html); 730 var content = extractOffcanvasContent(doc, html); 731 732 if (!content) { 733 showError(getErrorText()); 734 return null; 735 } 736 737 var mount = getShadowMount(shadow); 738 mount.innerHTML = wrapOffcanvasContent(content); 739 740 dom.content.classList.add("phos-gawel-cart-offcanvas__content--ready"); 741 updateBadge(parseCartCount(doc)); 742 bindOffcanvasInteractions(shadow); 743 744 ensureThemeStyles(shadow); 745 ensureEmbedAssets(shadow); 746 747 return doc; 748 } 749 750 async function loadOffcanvasContent() { 751 try { 752 return await reloadOffcanvasContent({ showLoading: true }); 753 } catch (error) { 754 console.error("Gawel cart offcanvas load failed:", error); 755 showError(getErrorText()); 756 return null; 757 } 758 } 759 760 function openOffcanvas() { 761 if (!dom.offcanvas) { 762 return; 763 } 764 765 loadOffcanvasContent(); 766 767 if (typeof bootstrap !== "undefined" && bootstrap.Offcanvas) { 768 bootstrap.Offcanvas.getOrCreateInstance(dom.offcanvas).show(); 769 return; 770 } 771 772 dom.offcanvas.classList.add("show"); 773 } 774 775 function closeOffcanvas() { 776 if (!dom.offcanvas) { 777 return; 778 } 779 780 if (typeof bootstrap !== "undefined" && bootstrap.Offcanvas) { 781 var instance = bootstrap.Offcanvas.getInstance(dom.offcanvas); 782 if (instance) { 783 instance.hide(); 784 } 785 return; 786 } 787 788 dom.offcanvas.classList.remove("show"); 789 } 790 791 function setupRefreshOnFocus() { 792 window.addEventListener("focus", refreshCartBadge); 793 document.addEventListener("visibilitychange", function () { 794 if (document.visibilityState === "visible") { 795 refreshCartBadge(); 796 } 797 }); 798 } 799
800 function init() { 801 if (window.__PHOS_GAWEL_SHOP_INIT__) { 802 return; 803 } 804 window.__PHOS_GAWEL_SHOP_INIT__ = true; 805 806 cacheDom(); 807 808 if (!dom.trigger || !dom.trigger.length) { 809 return; 810 } 811 812 dom.trigger.forEach(function (trigger) { 813 trigger.addEventListener("click", function (event) { 814 event.preventDefault(); 815 openOffcanvas(); 816 }); 817 }); 818 819 if (dom.offcanvas) { 820 dom.offcanvas.addEventListener("hidden.bs.offcanvas", refreshCartBadge); 821 } 822 823 setupRefreshOnFocus(); 824 refreshCartBadge(); 825 } 826 827 if (document.readyState === "loading") { 828 document.addEventListener("DOMContentLoaded", init); 829 } else { 830 init(); 831 } 832})();
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.