1document.addEventListener('DOMContentLoaded', () => { 2 const megaItems = Array.from(document.querySelectorAll('.nav-item--mega')); 3 let megaCloseTimer = null; 4 5 const clearMegaCloseTimer = () => { 6 if (megaCloseTimer) { 7 window.clearTimeout(megaCloseTimer); 8 megaCloseTimer = null; 9 } 10 }; 11 12 const closeMegaItems = (exceptItem = null) => { 13 clearMegaCloseTimer(); 14 15 megaItems.forEach((item) => { 16 if (item === exceptItem) { 17 return; 18 } 19 20 const itemToggle = item.querySelector('.nav-toggle'); 21 item.classList.remove('is-open'); 22 23 if (itemToggle) { 24 itemToggle.setAttribute('aria-expanded', 'false'); 25 } 26 }); 27 }; 28 29 const openMegaItem = (megaItem) => { 30 const toggle = megaItem.querySelector('.nav-toggle'); 31 32 clearMegaCloseTimer(); 33 closeMegaItems(megaItem); 34 megaItem.classList.add('is-open'); 35 36 if (toggle) { 37 toggle.setAttribute('aria-expanded', 'true'); 38 } 39 }; 40 41 const scheduleMegaClose = () => { 42 clearMegaCloseTimer(); 43 megaCloseTimer = window.setTimeout(() => { 44 closeMegaItems(); 45 }, 420); 46 }; 47 48 megaItems.forEach((megaItem) => { 49 const toggle = megaItem.querySelector('.nav-toggle'); 50 51 if (!toggle) { 52 return; 53 } 54 55 megaItem.addEventListener('pointerenter', () => { 56 openMegaItem(megaItem); 57 }); 58 59 megaItem.addEventListener('pointerleave', () => { 60 scheduleMegaClose(); 61 }); 62 63 toggle.addEventListener('click', (e) => { 64 e.preventDefault(); 65 const shouldOpen = !megaItem.classList.contains('is-open'); 66 67 if (shouldOpen) { 68 openMegaItem(megaItem); 69 } else { 70 closeMegaItems(); 71 } 72 }); 73 }); 74 75 if (megaItems.length) { 76 document.addEventListener('click', (e) => { 77 const clickInsideMega = megaItems.some((item) => item.contains(e.target)); 78 79 if (!clickInsideMega) { 80 closeMegaItems(); 81 } 82 }); 83 84 document.addEventListener('keydown', (e) => { 85 if (e.key === 'Escape') { 86 closeMegaItems(); 87 } 88 }); 89 } 90 91 const languageMenus = Array.from(document.querySelectorAll('.header-lang')); 92 93 const closeLanguageMenus = (exceptMenu = null) => { 94 languageMenus.forEach((menu) => { 95 if (menu === exceptMenu) { 96 return; 97 } 98 99 menu.classList.remove('is-open'); 100 const button = menu.querySelector('.header-lang__current'); 101 102 if (button) { 103 button.setAttribute('aria-expanded', 'false'); 104 } 105 }); 106 }; 107 108 languageMenus.forEach((menu) => { 109 const button = menu.querySelector('.header-lang__current'); 110 111 if (!button) { 112 return; 113 } 114 115 button.setAttribute('aria-expanded', 'false'); 116 117 button.addEventListener('click', (e) => { 118 e.preventDefault(); 119 e.stopPropagation(); 120 121 const shouldOpen = !menu.classList.contains('is-open'); 122 closeLanguageMenus(menu); 123 menu.classList.toggle('is-open', shouldOpen); 124 button.setAttribute('aria-expanded', shouldOpen ? 'true' : 'false'); 125 }); 126 }); 127 128 if (languageMenus.length) { 129 document.addEventListener('click', (e) => { 130 const insideLanguage = languageMenus.some((menu) => menu.contains(e.target)); 131 132 if (!insideLanguage) { 133 closeLanguageMenus(); 134 } 135 }); 136 137 document.addEventListener('keydown', (e) => { 138 if (e.key === 'Escape') { 139 closeLanguageMenus(); 140 } 141 }); 142 } 143 144 document.querySelectorAll('.wpml-ls [role="menu"], .wpml-ls [role="menuitem"], .wpml-ls [role="none"]').forEach((element) => { 145 element.removeAttribute('role'); 146 }); 147 148 const categoriesPanel = document.getElementById('mobile-categories-panel'); 149 const openCategories = document.querySelectorAll('.js-open-categories'); 150 const closeCategories = document.querySelectorAll('.js-close-categories'); 151 152 if (categoriesPanel) { 153 openCategories.forEach((btn) => { 154 btn.addEventListener('click', () => { 155 categoriesPanel.hidden = false; 156 categoriesPanel.classList.add('is-open'); 157 categoriesPanel.removeAttribute('aria-hidden'); 158 document.body.classList.add('panel-open'); 159 }); 160 }); 161
162 closeCategories.forEach((btn) => { 163 btn.addEventListener('click', () => { 164 categoriesPanel.classList.remove('is-open'); 165 categoriesPanel.removeAttribute('aria-hidden'); 166 categoriesPanel.hidden = true; 167 document.body.classList.remove('panel-open'); 168 }); 169 }); 170 } 171 172 const mobileSearchPanel = document.getElementById('mobile-search-panel'); 173 const desktopSearchPanel = document.getElementById('search-overlay'); 174 const searchPanels = [mobileSearchPanel, desktopSearchPanel].filter(Boolean); 175 const openSearch = document.querySelectorAll('.js-open-search'); 176 const closeSearch = document.querySelectorAll('.js-close-search'); 177 178 const getSearchPanel = () => { 179 if (window.matchMedia('(min-width: 1025px)').matches && desktopSearchPanel) { 180 return desktopSearchPanel; 181 } 182 183 return mobileSearchPanel || desktopSearchPanel; 184 }; 185 186 const closeSearchPanels = () => { 187 searchPanels.forEach((panel) => { 188 panel.classList.remove('is-open'); 189 panel.removeAttribute('aria-hidden'); 190 panel.hidden = true; 191 }); 192 document.body.classList.remove('panel-open'); 193 }; 194 195 if (searchPanels.length) { 196 openSearch.forEach((btn) => { 197 btn.addEventListener('click', () => { 198 const panel = getSearchPanel(); 199 200 if (!panel) { 201 return; 202 } 203 204 closeSearchPanels(); 205 panel.hidden = false; 206 panel.classList.add('is-open'); 207 panel.removeAttribute('aria-hidden'); 208 document.body.classList.add('panel-open'); 209 210 const input = panel.querySelector('[data-ai-search-input], input[type="search"]'); 211 212 if (input) { 213 window.setTimeout(() => input.focus(), 80); 214 } 215 }); 216 }); 217 218 closeSearch.forEach((btn) => { 219 btn.addEventListener('click', () => { 220 closeSearchPanels(); 221 }); 222 }); 223 224 document.addEventListener('keydown', (e) => { 225 if (e.key === 'Escape') { 226 closeSearchPanels(); 227 } 228 }); 229 } 230 231 const productGalleries = Array.from(document.querySelectorAll('[data-product-gallery]')); 232 233 productGalleries.forEach((gallery) => { 234 const mainImage = gallery.querySelector('[data-product-gallery-main]'); 235 const mainFrame = mainImage ? mainImage.closest('.product-gallery__main') : null; 236 const thumbs = Array.from(gallery.querySelectorAll('[data-product-gallery-thumb]')); 237 238 if (!mainImage) { 239 return; 240 } 241 242 const originalImage = { 243 src: mainImage.getAttribute('src') || '', 244 alt: mainImage.getAttribute('alt') || '', 245 thumb: thumbs.find((thumb) => thumb.classList.contains('is-active')) || null 246 }; 247 248 const setActiveThumb = (activeThumb) => { 249 thumbs.forEach((thumb) => { 250 const isActive = thumb === activeThumb; 251 thumb.classList.toggle('is-active', isActive); 252 thumb.setAttribute('aria-pressed', isActive ? 'true' : 'false'); 253 }); 254 }; 255 256 const normalizeImageUrl = (url) => (url || '').split('?')[0]; 257 258 const findThumbBySrc = (src) => { 259 const normalizedSrc = normalizeImageUrl(src); 260 261 return thumbs.find((thumb) => normalizeImageUrl(thumb.getAttribute('data-large')) === normalizedSrc) || null; 262 }; 263 264 const switchMainImage = (nextSrc, nextAlt, activeThumb = null) => { 265 if (!nextSrc || mainImage.getAttribute('src') === nextSrc) { 266 setActiveThumb(activeThumb); 267 return; 268 } 269 270 const applyImage = () => { 271 mainImage.setAttribute('src', nextSrc); 272 mainImage.setAttribute('alt', nextAlt); 273 mainImage.removeAttribute('srcset'); 274 mainImage.removeAttribute('sizes'); 275 setActiveThumb(activeThumb); 276 277 if (mainFrame) { 278 window.requestAnimationFrame(() => { 279 mainFrame.classList.remove('is-switching'); 280 }); 281 } 282 }; 283 284 if (mainFrame) { 285 mainFrame.classList.add('is-switching'); 286 } 287 288 const preload = new Image(); 289 preload.onload = applyImage; 290 preload.onerror = applyImage; 291 preload.src = nextSrc; 292 }; 293 294 const switchProductImage = (thumb) => { 295 const nextSrc = thumb.getAttribute('data-large'); 296 const nextAlt = thumb.getAttribute('data-alt') || mainImage.getAttribute('alt') || ''; 297 298 switchMainImage(nextSrc, nextAlt, thumb); 299 }; 300
301 thumbs.forEach((thumb) => { 302 thumb.addEventListener('click', () => { 303 switchProductImage(thumb); 304 }); 305 }); 306 307 const productLayout = gallery.closest('.product-layout'); 308 const variationForm = productLayout ? productLayout.querySelector('.variations_form') : document.querySelector('.variations_form'); 309 310 if (variationForm && window.jQuery) { 311 window.jQuery(variationForm) 312 .on('found_variation', (event, variation) => { 313 const variationImage = variation && variation.image ? variation.image : null; 314 const variationSrc = variationImage ? (variationImage.full_src || variationImage.src || variationImage.url) : ''; 315 316 if (!variationSrc) { 317 return; 318 } 319 320 switchMainImage( 321 variationSrc, 322 variationImage.alt || variationImage.title || originalImage.alt, 323 findThumbBySrc(variationSrc) 324 ); 325 }) 326 .on('reset_data hide_variation', () => { 327 switchMainImage(originalImage.src, originalImage.alt, originalImage.thumb); 328 }); 329 } 330 }); 331 332 const shopGrid = document.querySelector('[data-shop-grid]'); 333 const shopPagination = document.querySelector('[data-shop-pagination]'); 334 335 if (shopGrid && shopPagination) { 336 let isLoadingMoreProducts = false; 337 338 const getNextPageLink = () => shopPagination.querySelector('a.next'); 339 340 const setShopLoading = (isLoading) => { 341 shopPagination.classList.toggle('is-loading', isLoading); 342 shopPagination.setAttribute('aria-busy', isLoading ? 'true' : 'false'); 343 }; 344
345 const loadMoreProducts = async () => { 346 const nextLink = getNextPageLink(); 347 348 if (!nextLink || isLoadingMoreProducts) { 349 return; 350 } 351 352 isLoadingMoreProducts = true; 353 setShopLoading(true); 354 355 try { 356 const response = await fetch(nextLink.href, { 357 credentials: 'same-origin' 358 }); 359 360 if (!response.ok) { 361 throw new Error('Shop page request failed'); 362 } 363 364 const html = await response.text(); 365 const doc = new DOMParser().parseFromString(html, 'text/html'); 366 const nextProducts = Array.from(doc.querySelectorAll('[data-shop-grid] .shop-product-card')); 367 const nextPagination = doc.querySelector('[data-shop-pagination]'); 368 369 nextProducts.forEach((productCard) => { 370 shopGrid.appendChild(productCard); 371 }); 372 373 if (nextPagination) { 374 shopPagination.innerHTML = nextPagination.innerHTML; 375 } else { 376 shopPagination.innerHTML = ''; 377 } 378 } catch (error) { 379 shopPagination.classList.add('has-error'); 380 } finally { 381 isLoadingMoreProducts = false; 382 setShopLoading(false); 383 } 384 }; 385 386 const observeShopPagination = () => { 387 if (!('IntersectionObserver' in window)) { 388 return; 389 } 390 391 const observer = new IntersectionObserver((entries) => { 392 entries.forEach((entry) => { 393 if (entry.isIntersecting) { 394 loadMoreProducts(); 395 } 396 }); 397 }, { 398 rootMargin: '520px 0px' 399 }); 400 401 observer.observe(shopPagination); 402 }; 403 404 observeShopPagination(); 405 } 406 407 const cookieBanner = document.querySelector('[data-cookie-banner]'); 408 const cookieModal = document.querySelector('[data-cookie-modal]'); 409 const cookieManage = document.querySelector('[data-cookie-manage]'); 410 const cookieButtons = document.querySelectorAll('[data-cookie-action]'); 411 const cookieCloseButtons = document.querySelectorAll('[data-cookie-close]'); 412 const cookieAnalytics = document.querySelector('[data-cookie-preference="analytics"]'); 413 const cookieName = 'decorfood_cookie_consent_v2'; 414 415 const setSpokiHidden = (hidden) => { 416 const spokiButton = document.querySelector('#spoki-shadowed-fixed-button'); 417 if (!spokiButton) { 418 return false; 419 } 420 421 if (hidden) { 422 spokiButton.style.setProperty('display', 'none', 'important'); 423 } else { 424 spokiButton.style.removeProperty('display'); 425 } 426 427 return true; 428 }; 429 430 const waitForSpoki = (hidden) => { 431 if (setSpokiHidden(hidden)) { 432 return; 433 } 434 435 const observer = new MutationObserver(() => { 436 if (setSpokiHidden(hidden)) { 437 observer.disconnect(); 438 } 439 }); 440 observer.observe(document.body, { childList: true, subtree: true }); 441 window.setTimeout(() => observer.disconnect(), 10000); 442 }; 443 444 const getCookieConsent = () => { 445 const match = document.cookie.match(new RegExp(`(?:^|; )${cookieName}=([^;]*)`)); 446 447 if (!match) { 448 return null; 449 } 450 451 try { 452 return JSON.parse(decodeURIComponent(match[1])); 453 } catch (error) { 454 return null; 455 } 456 }; 457
458 const setCookieConsent = (preferences) => { 459 const consent = { 460 necessary: true, 461 analytics: Boolean(preferences.analytics), 462 marketing: false, 463 version: 2, 464 savedAt: new Date().toISOString() 465 }; 466 467 document.cookie = `${cookieName}=${encodeURIComponent(JSON.stringify(consent))}; path=/; max-age=15552000; SameSite=Lax`; 468 window.dispatchEvent(new CustomEvent('decorfood:cookie-consent', { detail: consent })); 469 470 if (typeof window.gtag === 'function') { 471 window.gtag('consent', 'update', { 472 analytics_storage: consent.analytics ? 'granted' : 'denied', 473 ad_storage: 'denied', 474 ad_user_data: 'denied', 475 ad_personalization: 'denied', 476 personalization_storage: 'denied', 477 functionality_storage: 'granted', 478 security_storage: 'granted' 479 }); 480 } 481 482 return consent; 483 }; 484 485 const syncCookieInputs = (consent = {}) => { 486 if (cookieAnalytics) { 487 cookieAnalytics.checked = Boolean(consent.analytics); 488 } 489 490 }; 491 492 const showCookieBanner = () => { 493 if (cookieBanner) { 494 cookieBanner.hidden = false; 495 } 496 497 if (cookieManage) { 498 cookieManage.hidden = true; 499 } 500 501 waitForSpoki(true); 502 }; 503 504 const hideCookieBanner = () => { 505 if (cookieBanner) { 506 cookieBanner.hidden = true; 507 } 508 509 if (cookieManage) { 510 cookieManage.hidden = false; 511 } 512 513 setSpokiHidden(false); 514 }; 515 516 const openCookieModal = () => { 517 const consent = getCookieConsent(); 518 519 syncCookieInputs(consent || {}); 520 521 if (cookieModal) { 522 cookieModal.hidden = false; 523 cookieModal.setAttribute('aria-hidden', 'false'); 524 document.body.classList.add('panel-open'); 525 const firstControl = cookieModal.querySelector('button, input'); 526 if (firstControl) { 527 firstControl.focus(); 528 } 529 } 530 531 waitForSpoki(true); 532 }; 533 534 const closeCookieModal = () => { 535 if (cookieModal) { 536 cookieModal.hidden = true; 537 cookieModal.setAttribute('aria-hidden', 'true'); 538 document.body.classList.remove('panel-open'); 539 } 540 541 542 if (!cookieBanner || cookieBanner.hidden) { 543 setSpokiHidden(false); 544 } 545 }; 546 547 const saveCookieChoice = (action) => {
548 let consent; 549 550 if (action === 'accept') { 551 consent = setCookieConsent({ analytics: true }); 552 } else if (action === 'save') { 553 consent = setCookieConsent({ 554 analytics: cookieAnalytics ? cookieAnalytics.checked : false 555 }); 556 } else { 557 consent = setCookieConsent({ analytics: false }); 558 } 559 560 syncCookieInputs(consent); 561 hideCookieBanner(); 562 closeCookieModal(); 563 }; 564 565 if (cookieBanner || cookieModal || cookieManage) { 566 const currentConsent = getCookieConsent(); 567 568 if (currentConsent) { 569 syncCookieInputs(currentConsent); 570 if (typeof window.gtag === 'function') { 571 window.gtag('consent', 'update', { 572 analytics_storage: currentConsent.analytics ? 'granted' : 'denied', 573 ad_storage: 'denied', 574 ad_user_data: 'denied', 575 ad_personalization: 'denied', 576 personalization_storage: 'denied', 577 functionality_storage: 'granted', 578 security_storage: 'granted' 579 }); 580 } 581 hideCookieBanner(); 582 } else { 583 showCookieBanner(); 584 } 585 586 cookieButtons.forEach((button) => { 587 button.addEventListener('click', () => { 588 const action = button.getAttribute('data-cookie-action'); 589 590 if (action === 'customize') { 591 openCookieModal(); 592 return; 593 } 594 595 saveCookieChoice(action); 596 }); 597 }); 598 599 cookieCloseButtons.forEach((button) => { 600 button.addEventListener('click', closeCookieModal); 601 }); 602 603 if (cookieManage) { 604 cookieManage.addEventListener('click', openCookieModal); 605 } 606 607 document.addEventListener('keydown', (event) => { 608 if (event.key === 'Escape' && cookieModal && !cookieModal.hidden) { 609 closeCookieModal(); 610 } 611 }); 612 } 613});
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.