PageSourceSearch

https://carrera-toys.com/cdn/shop/t/4/assets/bundle-builder.js?v=4201164905447069591781725286

js carrera-toys.com collected 2026-10-02 10:33:07 UTC 23,415 bytes, 80 lines download raw bytes

1const DEBUG_MODE=!0;function debugLog(...args){console&&console.log&&console.log("[Bundle Builder]",...args)}function debugWarn(...args){console&&console.warn&&console.warn("[Bundle Builder]",...args)}function debugError(...args){console&&console.error&&console.error("[Bundle Builder]",...args)}class BundleBuilder{constructor(options={}){this.pageId=options.pageId||"default",this.pageHandle=options.pageHandle||"bundle-builder",this.discountPercentage=options.discountPercentage||15,this.discountCode=options.discountCode||`BUNDLE${options.discountPercentage||15}`,this.minProducts=options.minProducts||2,this.maxProducts=options.maxProducts||5,this.moneyFormat=options.moneyFormat||"{{amount_with_comma_separator}} \u20AC",this.translations=options.translations||this.getDefaultTranslations(),this.selectedProducts=new Map,this.categoryLimits=new Map,this.activeLine=null,this.lineButtons=[],this.categoryElements=[],this.categoryMeta=new Map,this.categorySteps=["sets","cars","accessories"],this.storageKey=`revell-bundle-builder-${this.pageHandle}`,this.init()}getDefaultTranslations(){return{select:"Select",deselect:"Deselect",empty:"Choose your products to create your bundle",add_to_cart:"Add Bundle to Cart",adding:"Adding...",remaining:"Select {{ count }} more product{{ plural }}",unavailable:"This product is {{ status }} and cannot be selected.",status_unavailable:"unavailable",status_sold_out:"sold out",category_max:"Maximum selection reached ({{ max }})",products_max:"Maximum of {{ max }} products reached",products_min:"Please select at least {{ min }} products",success:"Bundle successfully added!",error:"Error adding. Please try again."}}init(){this.cacheDom(),this.setupLineSwitcher(),this.bindEvents(),this.setupCategoryLimits(),this.setupSearch(),this.setupCategoryAccordions(),this.updateUI(),this.loadFromStorage()}cacheDom(){this.lineButtons=Array.from(document.querySelectorAll(".bundle-line-button")),this.categoryElements=Array.from(document.querySelectorAll(".bundle-category"))}bindEvents(){document.addEventListener("change",e=>{e.target.classList.contains("product-checkbox")&&this.handleProductSelection(e.target)});const clearButton=document.querySelector(".clear-bundle");clearButton&&clearButton.addEventListener("click",()=>this.clearBundle());const addToCartButton=document.querySelector(".add-bundle-to-cart");addToCartButton&&addToCartButton.addEventListener("click",()=>this.addBundleToCart()),document.addEventListener("click",e=>{if(e.target.classList.contains("remove-product")){const productId=e.target.dataset.productId;this.removeProduct(productId)}})}setupCategoryLimits(){const categories=document.querySelectorAll(".bundle-category");debugLog(`\u{1F3D7}\uFE0F Setup f\xFCr ${categories.length} Kategorien:`),categories.forEach((category,index)=>{const maxSelection=parseInt(category.dataset.maxSelection)||1,categoryId=parseInt(category.dataset.categoryId),unlockThreshold=parseInt(category.dataset.unlockThreshold)||1,categoryRole=category.dataset.categoryRole||"sets",trackLine=category.dataset.trackLine||"";debugLog(`\u{1F4CB} Kategorie ${categoryId} (Index ${index}): Max = ${maxSelection}`),this.categoryLimits.set(categoryId,{max:maxSelection,selected:0,threshold:unlockThreshold}),this.categoryMeta.set(categoryId,{role:categoryRole,line:trackLine,element:category})}),debugLog("\u{1F5C2}\uFE0F Alle Kategorie-Limits:",this.categoryLimits),this.evaluateStepProgress()}setupLineSwitcher(){if(!this.lineButtons.length)return;const preselected=this.lineButtons.find(button=>button.classList.contains("is-active"));this.activeLine=preselected?preselected.dataset.line:this.lineButtons[0].dataset.line,this.applyLineFilter(this.activeLine),this.lineButtons.forEach(button=>{button.addEventListener("click",()=>{const selectedLine=button.dataset.line;selectedLine!==this.activeLine&&this.setActiveLine(selectedLine)})})}setActiveLine(line,options={}){this.activeLine=line,this.lineButtons.forEach(button=>{const isActive=button.dataset.line===line;button.classList.toggle("is-active",isActive),button.setAttribute("aria-pressed",isActive?"true":"false")}),this.applyLineFilter(line),options.skipReset||this.clearBundle({skipSave:!0}),this.saveToStorage()}applyLineFilter(line){document.querySelectorAll(".bundle-category").forEach(category=>{const categoryLine=category.dataset.trackLine;!line||categoryLine===line?category.style.display="":category.style.display="none"}),this.categoryLimits.size&&this.evaluateStepProgress()}setupSearch(){document.querySelectorAll(".bundle-category").forEach(category=>{const input=category.querySelector(".category-search-input"),productsContainer=category.querySelector(".category-products");!input||!productsContainer||input.addEventListener("input",()=>{const query=input.value.trim().toLowerCase();productsContainer.querySelectorAll(".bundle-product-card").forEach(card=>{const titleEl=card.querySelector(".product-title"),title=titleEl?titleEl.textContent.toLowerCase():"";!query||title.includes(query)?card.style.display="":card.style.display="none"})})})}setupCategoryAccordions(){this.categoryElements.length||(this.categoryElements=Array.from(document.querySelectorAll(".bundle-category"))),this.categoryElements.forEac
1h(category=>{const toggle=category.querySelector(".category-toggle"),products=category.querySelector(".category-products"),header=category.querySelector(".category-header");!toggle||!products||(toggle.addEventListener("click",()=>{const shouldCollapse=!category.classList.contains("is-collapsed");this.setCategoryCollapsed(category,shouldCollapse)}),header&&header.addEventListener("click",event=>{if(event.target.closest(".category-toggle")||event.target.closest(".category-search-input"))return;const shouldCollapse=!category.classList.contains("is-collapsed");this.setCategoryCollapsed(category,shouldCollapse)}))})}setCategoryCollapsed(category,collapsed){if(!category)return;const toggle=category.querySelector(".category-toggle"),products=category.querySelector(".category-products"),nextState=typeof collapsed=="boolean"?collapsed:!category.classList.contains("is-collapsed");if(category.classList.toggle("is-collapsed",nextState),toggle){toggle.setAttribute("aria-expanded",(!nextState).toString());const label=toggle.querySelector(".toggle-label");label&&(label.textContent=nextState?"Kategorie \xF6ffnen":"Kategorie schlie\xDFen");const icon=toggle.querySelector(".toggle-icon");icon&&(icon.textContent=nextState?"+":"\u2212")}products&&products.setAttribute("aria-hidden",nextState?"true":"false")}getCategoryElementByRole(line,role){return!line||!role?null:this.categoryElements.find(category=>category.dataset.trackLine===line&&category.dataset.categoryRole===role)||null}getOrderedCategoriesForLine(line){if(!line)return[];const ordered=[];return this.categorySteps.forEach(role=>{const category=this.getCategoryElementByRole(line,role);category&&ordered.push(category)}),ordered}evaluateStepProgress(){if(!this.activeLine||!this.categoryLimits.size)return;const orderedCategories=this.getOrderedCategoriesForLine(this.activeLine);if(!orderedCategories.length)return;let currentStepIndex=0;for(let i=0;i<orderedCategories.length;i++){const category=orderedCategories[i],categoryId=parseInt(category.dataset.categoryId),limit=this.categoryLimits.get(categoryId),threshold=limit&&typeof limit.threshold=="number"?limit.threshold:1,selected=limit?limit.selected:0;if(threshold<=0||selected>=threshold)currentStepIndex=i+1;else{currentStepIndex=i;break}}currentStepIndex>=orderedCategories.length&&(currentStepIndex=orderedCategories.length-1),orderedCategories.forEach((category,index)=>{this.setCategoryCollapsed(category,index!==currentStepIndex)})}handleProductSelection(checkbox){const productCard=checkbox.closest(".bundle-product-card"),toBool=value=>value==null?!1:String(value).trim().toLowerCase()==="true",isAvailable=toBool(productCard.dataset.available),isInStock=toBool(productCard.dataset.inStock);if(debugLog("\u{1F9EA} Availability-Check",{productId:productCard.dataset.productId,availableAttr:productCard.dataset.available,inStockAttr:productCard.dataset.inStock,isAvailable,isInStock}),checkbox.checked&&(!isAvailable||!isInStock)){debugLog("\u274C Produkt nicht verf\xFCgbar oder ausverkauft - Auswahl verhindert"),checkbox.checked=!1;const status=isAvailable?this.translations.status_sold_out:this.translations.status_unavailable,message=this.translations.unavailable.replace("{{ status }}",status);this.showNotification(message,"warning");return}const productId=productCard.dataset.productId,variantId=productCard.dataset.variantId,price=parseInt(checkbox.dataset.price),title=checkbox.dataset.title,categoryIndex=this.getCategoryIndex(productCard);if(debugLog(`\u{1F50D} Produktauswahl - Kategorie: ${categoryIndex}, Produkt: ${title}`),checkbox.checked){const categoryLimit=this.categoryLimits.get(categoryIndex);if(debugLog(`\u{1F4CA} Kategorie ${categoryIndex} - Aktuell: ${categoryLimit?.selected}, Maximum: ${categoryLimit?.max}`),categoryLimit&&categoryLimit.selected>=categoryLimit.max){checkbox.checked=!1;const message=this.translations.category_max.replace("{{ max }}",categoryLimit.max);this.showNotification(message,"warning");return}if(this.selectedProducts.size>=this.maxProducts){checkbox.checked=!1;const message=this.translations.products_max.replace("{{ max }}",this.maxProducts);this.showNotification(message,"warning");return}this.selectedProducts.set(productId,{variantId,price,title,categoryIndex,element:productCard}),categoryLimit&&categoryLimit.selected++,productCard.classList.add("selected");const label=productCard.querySelector(".selection-label");label&&(label.textContent=this.translations.deselect)}else{const product=this.selectedProducts.get(productId);if(product){this.selectedProducts.delete(productId);const categoryLimit=this.categoryLimits.get(product.categoryIndex);categoryLimit&&categoryLimit.selected--,productCard.classList.remove("selected");const label=productCard.querySelector(".selection-label");label&&(label.textContent=this.translations.select)}
1}this.updateUI(),this.saveToStorage()}getCategoryIndex(productCard){const categoryId=productCard.closest(".bundle-category").dataset.categoryId;return parseInt(categoryId)}removeProduct(productId){const product=this.selectedProducts.get(productId);if(product){const checkbox=product.element.querySelector(".product-checkbox");checkbox&&(checkbox.checked=!1),this.selectedProducts.delete(productId);const categoryLimit=this.categoryLimits.get(product.categoryIndex);categoryLimit&&categoryLimit.selected--,product.element.classList.remove("selected");const label=product.element.querySelector(".selection-label");label&&(label.textContent=this.translations.select),this.updateUI(),this.saveToStorage()}}clearBundle(options={}){document.querySelectorAll(".product-checkbox:checked").forEach(checkbox=>{checkbox.checked=!1}),document.querySelectorAll(".bundle-product-card.selected").forEach(card=>{card.classList.remove("selected");const label=card.querySelector(".selection-label");label&&(label.textContent=this.translations.select)}),this.selectedProducts.clear(),this.categoryLimits.forEach(limit=>{limit.selected=0}),this.updateUI(),options.skipSave||this.saveToStorage()}updateUI(){this.updateCategoryCounts(),this.updateSelectedProductsList(),this.updatePricing(),this.updateProgress(),this.updateAddToCartButton(),this.evaluateStepProgress()}updateCategoryCounts(){document.querySelectorAll(".bundle-category").forEach(category=>{const selectedCountEl=category.querySelector(".selected-count"),categoryId=parseInt(category.dataset.categoryId),limit=this.categoryLimits.get(categoryId);selectedCountEl&&limit&&(selectedCountEl.textContent=limit.selected)})}updateSelectedProductsList(){const listContainer=document.querySelector(".selected-products-list");if(!listContainer)return;if(this.selectedProducts.size===0){listContainer.innerHTML=`
2        <div class="empty-state">
3          <p>${this.translations.empty}</p>
4        </div>
5      `;return}const productsList=Array.from(this.selectedProducts.entries()).map(([productId,product])=>`
6      <div class="selected-product-item">
7        <div class="selected-product-info">
8          <div class="selected-product-name">${product.title}</div>
9          <div class="selected-product-price">${this.formatMoney(this.calculateDiscountedPrice(product.price))}<br><span class="bundle-price-note">inkl. ${this.discountPercentage}% Bundle-Rabatt</span></div>
10        </div>
11        <button type="button" class="remove-product" data-product-id="${productId}">
12          \u2715
13        </button>
14      </div>
15    `).join("");listContainer.innerHTML=productsList}updatePricing(){const originalTotal=this.calculateOriginalTotal(),discountedTotal=this.calculateDiscountedTotal(),discountAmount=originalTotal-discountedTotal,bundleTotal=discountedTotal,originalTotalEl=document.querySelector(".original-total-price"),discountAmountEl=document.querySelector(".discount-amount-price"),bundleTotalEl=document.querySelector(".bundle-total-price"),savingsAmountEl=document.querySelector(".savings-amount");originalTotalEl&&(originalTotalEl.textContent=this.formatMoney(originalTotal)),discountAmountEl&&(discountAmountEl.textContent="-"+this.formatMoney(discountAmount)),bundleTotalEl&&(bundleTotalEl.textContent=this.formatMoney(bundleTotal)),savingsAmountEl&&(savingsAmountEl.textContent=this.formatMoney(discountAmount))}updateProgress(){const selectedCount=this.selectedProducts.size,progressFill=document.querySelector(".progress-fill"),selectedCountEl=document.querySelector(".selected-products-count");if(progressFill){const progressPercentage=Math.min(selectedCount/this.minProducts*100,100);progressFill.style.width=`${progressPercentage}%`}selectedCountEl&&(selectedCountEl.textContent=selectedCount)}updateAddToCartButton(){const addToCartButton=document.querySelector(".add-bundle-to-cart");if(!addToCartButton)return;const canAddToCart=this.selectedProducts.size>=this.minProducts;if(addToCartButton.disabled=!canAddToCart,canAddToCart)addToCartButton.textContent=this.translations.add_to_cart;else{const remaining=this.minProducts-this.selectedProducts.size,plural=remaining===1?"":"e";addToCartButton.textContent=this.translations.remaining.replace("{{ count }}",remaining).replace("{{ plural }}",plural)}}calculateOriginalTotal(){return Array.from(this.selectedProducts.values()).reduce((total,product)=>total+product.price,0)}calculateDiscountedTotal(){return Array.from(this.selectedProducts.values()).reduce((total,product)=>total+this.calculateDiscountedPrice(product.price),0)}calculateDiscountAmount(originalTotal){const discountedTotal=this.calculateDiscountedTotal();return originalTotal-discountedTotal}calculateDiscountedPrice(originalPrice){const discount=Math.floor(originalPrice*(this.discountPercentage/100));return originalPrice-discount}formatMoney(cents){const amount=(cents/100).toFixed(2);return this.moneyFormat.replace("{{amount_with_comma_separator}}",amount.replace(".",","))}async addBundleToCart(){if(this.selectedProducts.size<this.minProducts){const message=this.translations.products_min.replace("{{ min }}",this.minProducts);this.showNotification(message,"error");return}const addToCartButton=document.querySelector(".add-bundle-to-cart"),originalText=addToCartButton.textContent;try{addToCartButton.textContent=this.translations.adding,addToCartButton.disabled=!0;const bundleId=`BUNDLE-${Date.now()}-${Math.random().toString(36).substr(2,9)}
15`,productCount=this.selectedProducts.size,originalTotal=this.calculateOriginalTotal(),discountedTotal=this.calculateDiscountedTotal(),discountAmount=originalTotal-discountedTotal,bundleTotal=discountedTotal,discountCode=this.discountCode;debugLog("\u{1F4B0} Discount Code:",discountCode);const items=Array.from(this.selectedProducts.values()).map(product=>({id:product.variantId,quantity:1,properties:{_bundle_id:bundleId,_bundle_count:productCount.toString(),_bundle_discount:this.discountPercentage.toString(),_original_price:product.price.toString(),_discounted_price:this.calculateDiscountedPrice(product.price).toString(),_discount_code:discountCode,"inkl.":`${this.discountPercentage}% Bundle-Rabatt`}})),miniCartSection=document.querySelector("cart-drawer[section]"),sectionId=miniCartSection?miniCartSection.getAttribute("section"):"mini-cart";debugLog("\u{1F6D2} Mini-Cart Section ID:",sectionId);const response=await fetch("/cart/add.js",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({items,sections:sectionId})});if(response.ok){const responseData=await response.json();debugLog("\u2705 Bundle zum Warenkorb hinzugef\xFCgt:",responseData),this.showNotification(this.translations.success,"success"),setTimeout(()=>{this.clearBundle()},1e3);const cartContent=await(await fetch(`${window.themeVariables?.routes?.cartUrl||"/cart"}.js`)).json();debugLog("\u{1F4E6} Aktualisierter Warenkorb:",cartContent),debugLog("\u{1F4E6} Cart Item Count:",cartContent.item_count),debugLog("\u{1F3A8} Sections in Response:",responseData.sections?"JA":"NEIN"),document.documentElement.dispatchEvent(new CustomEvent("cart:updated",{bubbles:!0,detail:{cart:cartContent}})),debugLog("\u2705 cart:updated dispatched"),responseData.sections&&(cartContent.sections=responseData.sections,debugLog("\u2705 Sections hinzugef\xFCgt zu cartContent"));const cartType=window.themeVariables?.settings?.cartType,shouldOpenDrawer=cartType==="drawer";debugLog("\u{1F6D2} Cart Type:",cartType),debugLog("\u{1F6AA} Should Open Drawer:",shouldOpenDrawer),document.documentElement.dispatchEvent(new CustomEvent("cart:refresh",{bubbles:!0,detail:{cart:cartContent,openMiniCart:shouldOpenDrawer}})),debugLog("\u2705 cart:refresh dispatched mit openMiniCart:",shouldOpenDrawer),this.initBundleMonitoring()}else{const errorData=await response.json();throw debugError("\u274C Fehler beim Hinzuf\xFCgen:",errorData),new Error(errorData.description||"Failed to add to cart")}}catch(error){debugError("Error adding bundle to cart:",error),this.showNotification(this.translations.error,"error")}finally{addToCartButton.textContent=originalText,addToCartButton.disabled=this.selectedProducts.size<this.minProducts}}initBundleMonitoring(){typeof window.BundleMonitor>"u"&&debugLog("\u26A0\uFE0F Bundle Monitor nicht geladen - Bundle-Validierung deaktiviert")}showNotification(message,type="info",duration=5e3){const existingNotification=document.querySelector(".bundle-notification");existingNotification&&existingNotification.remove();const notification=document.createElement("div");notification.className=`bundle-notification bundle-notification--${type}`,notification.innerHTML=`
16      <div class="bundle-notification__content">
17        <span class="bundle-notification__message">${message}</span>
18        <button type="button" class="bundle-notification__close">\u2715</button>
19      </div>
20    `,notification.style.cssText=`
21      position: fixed;
22      top: 20px;
23      right: 20px;
24      background: ${type==="success"?"#10b981":type==="error"?"#ef4444":"#3b82f6"};
25      color: white;
26      padding: 16px 20px;
27      border-radius: 8px;
28      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
29      z-index: 1000;
30      max-width: 400px;
31      animation: slideInRight 0.3s ease;
32    `,document.body.appendChild(notification),setTimeout(()=>{notification.parentNode&&(notification.style.animation="slideOutRight 0.3s ease",setTimeout(()=>notification.remove(),300))},duration),notification.querySelector(".bundle-notification__close").addEventListener("click",()=>{notification.style.animation="slideOutRight 0.3s ease",setTimeout(()=>notification.remove(),300)})}saveToStorage(){const bundleData={pageId:this.pageId,pageHandle:this.pageHandle,selectedProducts:Array.from(this.selectedProducts.entries()),categoryLimits:Array.from(this.categoryLimits.entries()),activeLine:this.activeLine,timestamp:Date.now(),version:this.getConfigVersion()};try{localStorage.setItem(this.storageKey,JSON.stringify(bundleData)),debugLog(`\u{1F4BE}
32 Bundle gespeichert unter Key: ${this.storageKey}`)}catch(error){debugWarn("Failed to save bundle to localStorage:",error)}}loadFromStorage(){try{const savedData=localStorage.getItem(this.storageKey);if(!savedData){debugLog(`\u{1F4C2} Keine gespeicherten Daten f\xFCr Key: ${this.storageKey}`);return}const bundleData=JSON.parse(savedData);if(debugLog(`\u{1F4C2} Lade Bundle-Daten f\xFCr Key: ${this.storageKey}`,bundleData),bundleData.pageId&&bundleData.pageId!==this.pageId){debugLog(`\u26A0\uFE0F Page ID stimmt nicht \xFCberein. Gespeichert: ${bundleData.pageId}, Aktuell: ${this.pageId}`),localStorage.removeItem(this.storageKey);return}if(Date.now()-bundleData.timestamp>1440*60*1e3){debugLog("\u23F0 Gespeicherte Daten sind \xE4lter als 24 Stunden"),localStorage.removeItem(this.storageKey);return}if(bundleData.version!==this.getConfigVersion()){debugLog("\u{1F504} Konfiguration hat sich ge\xE4ndert"),localStorage.removeItem(this.storageKey);return}bundleData.activeLine&&this.lineButtons.length&&this.setActiveLine(bundleData.activeLine,{skipReset:!0}),bundleData.selectedProducts&&bundleData.selectedProducts.forEach(([productId,productData])=>
32{const productCard=document.querySelector(`[data-product-id="${productId}"]`);if(productCard){const checkbox=productCard.querySelector(".product-checkbox");if(checkbox){checkbox.checked=!0,productCard.classList.add("selected");const label=productCard.querySelector(".selection-label");label&&(label.textContent=this.translations.deselect),this.selectedProducts.set(productId,{...productData,element:productCard})}}}),bundleData.categoryLimits&&bundleData.categoryLimits.forEach(([categoryId,savedLimit])=>{const currentLimit=this.categoryLimits.get(categoryId);currentLimit&&(currentLimit.selected=savedLimit.selected||0)}),this.updateUI()}catch(error){debugWarn("Failed to load bundle from localStorage:",error),localStorage.removeItem(this.storageKey)}}getConfigVersion(){const categories=document.querySelectorAll(".bundle-category");let configString="";categories.forEach((category,index)=>{const maxSelection=category.dataset.maxSelection,productCount=category.querySelectorAll(".bundle-product-card").length;configString+=`${index}-${maxSelection}-${productCount}`});let hash=0;for(let i=0;i<configString.length;i++){const char=configString.charCodeAt(i);hash=(hash<<5)-hash+char,hash=hash&hash}return hash.toString()}}const style=document.createElement("style");style.textContent=`
33  @keyframes slideInRight {
34    from {
35      transform: translateX(100%);
36      opacity: 0;
37    }
38    to {
39      transform: translateX(0);
40      opacity: 1;
41    }
42  }
43  
44  @keyframes slideOutRight {
45    from {
46      transform: translateX(0);
47      opacity: 1;
48    }
49    to {
50      transform: translateX(100%);
51      opacity: 0;
52    }
53  }
54  
55  .bundle-notification__content {
56    display: flex;
57    align-items: center;
58    justify-content: space-between;
59    gap: 12px;
60  }
61  
62  .bundle-notification__close {
63    background: none;
64    border: none;
65    color: white;
66    cursor: pointer;
67    font-size: 18px;
68    padding: 0;
69    width: 20px;
70    height: 20px;
71    display: flex;
72    align-items: center;
73    justify-content: center;
74  }
75  
76  .bundle-notification__close:hover {
77    opacity: 0.8;
78  }
79`,document.head.appendChild(style),window.BundleBuilder=BundleBuilder;
80//# sourceMappingURL=/cdn/shop/t/4/assets/bundle-builder.js.map?v=4201164905447069591781725286

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.