1// from: /Resources/Private/Layouts/Page/Default.html 2const pathsElement = document.getElementById('js-paths'); 3const modulePath = pathsElement.dataset.module; 4const vendorPath = pathsElement.dataset.vendor; 5const imagePath = pathsElement.dataset.images; 6const lottiePath = pathsElement.dataset.lottie; 7 8 9// ----- MOBILE MENU -------------------------------------------------------// 10 11const initMenu = async () => { 12 13 const menu = document.querySelector('#navDrop'); 14 15 if (!menu) return; 16 17 const { default: Navi } = await import(`${modulePath}/Menu.js`) 18 19 Navi(); 20 21}; 22 23 24 25// ----- SCROLLLISTENER -------------------------------------------------------// 26 27const initScrolledListener = () => { 28 29 document.addEventListener('scroll', () => { 30 31 if (window.scrollY > 50) { 32 document.body.classList.add('js-scrolled'); 33 } else { 34 document.body.classList.remove('js-scrolled'); 35 } 36 }); 37}; 38 39 40// ----- SCROLLLISTENER -------------------------------------------------------// 41 42const initHeaderButton = () => { 43 44 const stage = document.querySelector('.stage--hideButton'); 45 46 if (!stage) return; 47 48 document.body.classList.add('js-hideButton'); 49 50 document.addEventListener('scroll', () => { 51 52 const { bottom } = stage.getBoundingClientRect(); 53 54 if (bottom < 0) { 55 document.body.classList.remove('js-hideButton'); 56 } else { 57 document.body.classList.add('js-hideButton'); 58 } 59 }); 60}; 61 62 63// ----- Accordion -------------------------------------------------------// 64 65const initAccordions = async () => { 66 67 const accordionSelector = '.js-accordion'; 68 69 if (!document.querySelector(accordionSelector)) return; 70 71 const { default: initAccordion } = await import(`${modulePath}/Accordion.js`); 72 73 initAccordion(accordionSelector); 74 75}; 76 77// ----- Countdown -------------------------------------------------------// 78 79const initCountdowns = async () => { 80 81 const countdownSelector = '.js-countdown'; 82 83 if (!document.querySelector(countdownSelector)) return; 84 85 const { default: initCountdown } = await import(`${modulePath}/Countdown.js`); 86 87 initCountdown(countdownSelector); 88 89}; 90 91const initTestimonials = async () => { 92 93 const swiperSelector = '.swiper.testimonials__container' 94 const swiperContainer = document.querySelector(swiperSelector); 95 96 if (!swiperContainer) return; 97 98 const { Swiper } = await import(`${vendorPath}/swiper-bundle.esm.browser.min.js`); 99 100 const swiper = new Swiper(swiperSelector, { 101 initialSlide: 0, 102 slidesPerView: 1, 103 loop: false, 104 autoplay: false, 105 pagination: { 106 el: '.swiper-pagination', 107 clickable: true 108 }, 109 navigation: { 110 nextEl: '.swiper-button--next', 111 prevEl: '.swiper-button--prev', 112 } 113 }); 114}; 115 116 117// ----- image Slider ---------------------------------------------------// 118 119const initImageSlider = async () => { 120 121 const swiperSelector = '.swiper.imageSlider' 122 const swiperContainers = document.querySelectorAll(swiperSelector); 123 124 if (!swiperContainers[0]) return; 125 126 const { Swiper } = await import(`${vendorPath}/swiper-bundle.esm.browser.min.js`); 127 128 129 swiperContainers.forEach((element, i) => { 130 131 const buttonPrev = document.querySelectorAll('.swiper ~ .swiper-button2--prev'); 132 const buttonNext = document.querySelectorAll('.swiper ~ .swiper-button2--next'); 133 const autoplay = (element.dataset.autoplay === '1'); 134 const loop = (element.dataset.loop === '1'); 135 136 const swiper = new Swiper(element, { 137 initialSlide: 0, 138 slidesPerView: 1, 139 loop: loop, 140 autoplay: autoplay, 141 pagination: { 142 el: '.swiper-pagination', 143 clickable: true 144 }, 145 navigation: { 146 prevEl: buttonPrev[i], 147 nextEl: buttonNext[i] 148 } 149 }); 150 }); 151}; 152 153 154// ----- teasers Slider ---------------------------------------------------// 155 156const initTeaserSlider = async () => { 157 158 const swiperSelector = '.swiper.teasers' 159 const swiperContainer = document.querySelector(swiperSelector); 160 161 if (!swiperContainer) return; 162 163 const { Swiper } = await import(`${vendorPath}/swiper-bundle.esm.browser.min.js`); 164 165 const swiper = new Swiper(swiperSelector, { 166 initialSlide: 0, 167 slidesPerView: 1.2, 168 loop: false, 169 autoplay: false, 170 spaceBetween: 20, 171 pagination: { 172 el: '.swiper-pagination', 173 clickable: true 174 }, 175 breakpoints: { 176 769: { 177 slidesPerView: 2.2 178 }, 179 1200: { 180 slidesPerView: 3 181 } 182 } 183 }); 184}; 185 186 187// ----- Form Input Label Animation ---------------------------------------------------// 188const initFormInputs = async () => { 189 190 const inputSelector = '.form-control'; 191 192 if (!document.querySelector(inputSelector)) return; 193 194 const { default: initInputs } = await import(`${modulePath}/FormInput.js`); 195 196 initInputs(inputSelector); 197}; 198 199 200// Custom Select -------------------------------------------------------// 201 202const initFormSelects = async () => { 203 204 const formSelects = document.querySelectorAll('.custom-select'); 205 206 if (!formSelects) return; 207 208 const { default: CSelect } = await import(`${vendorPath}/cselect.js`); 209 210 211 for (const select of formSelects) { 212 213 let search = false;
214 215 if (select.nextElementSibling?.classList.contains('cSelect')) return; 216 217 if (select.classList.contains('custom-select--search')) { 218 search = true; 219 } 220 221 new CSelect(select, { 222 search: search, 223 animated: true, 224 }); 225 } 226 227 initFormSelectsZIndex(); 228}; 229 230 231const initFormSelectsZIndex = () => { 232 233 // prevent custom selects overlapping each other 234 235 const formSelects = document.querySelectorAll('.cSelect__default'); 236 237 formSelects.forEach((element, i) => { 238 239 const selectDrop = element.nextElementSibling; 240 const zIndex = 1000 - i * 5; 241 242 element.style.zIndex = zIndex; 243 selectDrop.style.zIndex = zIndex - 1; 244 }) 245 246}; 247 248const initFormUploads = () => { 249 250 const formUploads = document.querySelectorAll('.file-input input'); 251 252 formUploads.forEach((element, i) => { 253 254 element.addEventListener('change', () => { 255 256 const fileName = element.value.replace(/\.[^/.]+$/, "").split('\\').pop(); 257 const uploadField = element.parentNode.parentNode; 258 259 if (fileName !== '') { 260 uploadField.querySelector('.file-name').innerHTML = fileName; 261 uploadField.querySelector('.placeholder').classList.add('hidden'); 262 uploadField.querySelector('label').classList.add('filled'); 263 } 264 }); 265 }) 266}; 267 268 269const initButtonChat = async () => { 270 271 const chatButtons = document.querySelectorAll('.button--chat'); 272 273 if (!chatButtons) return; 274 275 const { waitForElement: waitForElement } = await import(`${modulePath}/Utils.js`); 276 277 waitForElement('#launcher').then((elm) => { 278 279 const chatIFrame = document.querySelector('#launcher'); 280 281 chatButtons.forEach(element => { 282 element.addEventListener('click', (event) => { 283 const c = chatIFrame.contentDocument || chatIFrame.contentWindow.document; 284 c.querySelector('button[data-garden-id]').click(); 285 }); 286 }) 287 }); 288} 289 290 291const initCopyToClipboard = async () => { 292 293 const clipboardButtons = document.querySelectorAll('.copyToClipboard'); 294 295 if (!clipboardButtons[0]) return; 296 297 clipboardButtons.forEach(element => { 298 299 const clipboardContainer = element.querySelector('.copyToClipboardText') || element; 300 const clipboardText = clipboardContainer.innerHTML; 301 302 element.addEventListener('click', (event) => { 303 navigator.clipboard.writeText(clipboardText); 304 305 alert('"' + clipboardText + '" wurde in die Zwischenablage kopiert.'); 306 }); 307 }) 308} 309 310 311// fix for safari browser back issues 312const initBrowserBackFix = async () => { 313 window.onpageshow = function (event) { 314 if (event.persisted) { 315 window.location.reload(); 316 } 317 }; 318} 319const initLottie = async () => { 320 const lottieContainers = document.querySelectorAll('.lottie-animation'); 321 322 if (lottieContainers[0]) { 323 const { DotLottie } = await import(`${vendorPath}/dotlottie.js`); 324 const { default: InitLottie } = await import(`${modulePath}/Lottie.js`); 325 const lottieInstance = new InitLottie(lottieContainers); 326 327 lottieContainers.forEach(element => { 328 const filePath = element.dataset.path; 329 new DotLottie({ 330 autoplay: true, 331 loop: true, 332 canvas: element, 333 src: filePath, 334 }); 335 }); 336 337 lottieInstance.init(); 338 } 339}; 340const initFocus = () => { 341 342 document.addEventListener('keydown', function (e) { 343 if (e.key === 'Tab') { 344 document.body.classList.remove('js-hideOutline'); 345 } 346 }); 347 348 document.addEventListener('mousedown', function () { 349 document.body.classList.add('js-hideOutline'); 350 }); 351}; 352 353const initGlobalLottie = async () => { 354 // CustomEvent from register-new.js 355 document.addEventListener('FdLightboxContentLoaded', async (event) => { 356 // Container 357 const lottieContainers = document.querySelectorAll('.globalLottie'); 358 // Import necessary libraries 359 const { DotLottie } = await import(`${vendorPath}/dotlottie.js`); 360 const { default: InitGlobalLottie } = await import(`${modulePath}/GlobalLottie.js`); 361 362 // Add more animations 363 const lottieAnimation = new InitGlobalLottie([ 364 `${lottiePath}sad1.json`, 365 `${lottiePath}sad2.json`, 366 `${lottiePath}sad3.json`, 367 ]); 368
369 lottieContainers.forEach(element => { 370 setTimeout(() => { 371 const filePath = lottieAnimation.init(); 372 new DotLottie({ 373 autoplay: true, 374 loop: true, 375 canvas: element, 376 src: filePath, 377 }); 378 379 // Remove with transition 380 setTimeout(() => { 381 element.style.transition = "opacity 1s"; 382 element.style.opacity = 0; 383 setTimeout(() => { 384 element.style.display = "none"; 385 }, 1000); //transition time 386 }, 4000); 387 }, 500); // delay 0.5s 388 }); 389 }); 390}; 391 392 393 394// ----- ABKey DataLayer -------------------------------------------------------// 395const initAbKeyDataLayer = () => { 396 const links = document.querySelectorAll('a[data-abkey]'); 397 398 links.forEach(link => { 399 link.addEventListener('click', (e) => { 400 const abKey = link.dataset.abkey; 401 if (abKey) { 402 e.preventDefault(); 403 window.dataLayer = window.dataLayer || []; 404 window.dataLayer.push({ 405 'event': abKey, 406 'url': link.href 407 }); 408 409 setTimeout(() => { 410 window.location.href = link.href; 411 console.log(window.dataLayer); 412 }, 100); 413 } 414 }); 415 }); 416}; 417 418 419function initABTesting(options = {}) { 420 const { 421 selector = '[data-ab-variant]', 422 useSessionStorage = true, 423 sessionStorageKey = 'abUser' 424 } = options; 425 426 // Guard: nicht doppelt initialisieren 427 if (window.__abTestingInitialized) { 428 return window.__abUserVariant || null; 429 } 430 window.__abTestingInitialized = true; 431 432 // Weniger als 2 Varianten-Buttons: ersten sichtbar machen, kein A/B-Testing 433 const allVariantButtons = document.querySelectorAll(selector); 434 if (allVariantButtons.length < 2) { 435 if (allVariantButtons.length === 1) { 436 allVariantButtons[0].removeAttribute('hidden'); 437 } 438 return null; 439 } 440 441 const normalizeVariant = (value) => { 442 return (value === 'A' || value === 'B') ? value : null; 443 }; 444 445 const pickRandomVariant = () => { 446 // 50/50: B in 50% der Fälle 447 return (Math.random() < 0.5) ? 'B' : 'A'; 448 }; 449 450 const getVariantFromDataLayer = () => { 451 const dl = window.dataLayer; 452 if (!Array.isArray(dl)) return null; 453 454 // Von hinten suchen (letzter Stand ist meist maÃgeblich) 455 for (let i = dl.length - 1; i >= 0; i--) { 456 const item = dl[i]; 457 if (item && typeof item === 'object' && 'abUser' in item) { 458 return normalizeVariant(item.abUser); 459 } 460 } 461 return null; 462 }; 463 464 const hasAbUserInDataLayer = () => { 465 const dl = window.dataLayer; 466 if (!Array.isArray(dl)) return false; 467 468 for (let i = dl.length - 1; i >= 0; i--) { 469 const item = dl[i]; 470 if (item && typeof item === 'object' && 'abUser' in item) { 471 return true; 472 } 473 } 474 return false; 475 }; 476 477 // 1) Primär: Server-Session 478 let abUser = normalizeVariant(window?.fdsession?.abUser); 479 480 // 2) Falls schon im dataLayer vorhanden: übernehmen (und später NICHT nochmal pushen) 481 if (!abUser) { 482 abUser = getVariantFromDataLayer(); 483 } 484 485 // 3) Session-only Persistenz (kein Cookie) 486 if (!abUser && useSessionStorage) { 487 try { 488 abUser = normalizeVariant(window.sessionStorage.getItem(sessionStorageKey)); 489 } catch (e) {} 490 } 491 492 // 4) abUser bleibt null â Variante wird erst unten zufällig gewählt, 493 // aber erst bei Klick registriert 494 495 // Wurde der Nutzer bereits registriert (aus Session/DataLayer/SessionStorage)? 496 const alreadyRegistered = !!abUser; 497 498 // Falls noch nicht registriert: zufällige Variante nur für die Anzeige wählen 499 if (!abUser) { 500 abUser = pickRandomVariant(); 501 } 502 503 // Buttons je nach Variante ein-/ausblenden 504 document.querySelectorAll(selector).forEach((el) => { 505 const variant = el.getAttribute('data-ab-variant'); 506 if (variant === abUser) { 507 el.removeAttribute('hidden'); 508 509 // Erst bei Klick registrieren, wenn noch nicht registriert 510 if (!alreadyRegistered) { 511 el.addEventListener('click', () => { 512 if (useSessionStorage) { 513 try { 514 window.sessionStorage.setItem(sessionStorageKey, abUser); 515 } catch (e) {} 516 } 517 if (!hasAbUserInDataLayer()) { 518 window.dataLayer = window.dataLayer || []; 519 window.dataLayer.push({ 520 abUser: abUser 521 }); 522 } 523 }, { once: true }); 524 } 525 } else { 526 el.setAttribute('hidden', 'hidden'); 527 } 528 }); 529 530 // dataLayer nur setzen, wenn Nutzer bereits vorher registriert war 531 if (alreadyRegistered && !hasAbUserInDataLayer()) { 532 window.dataLayer = window.dataLayer || []; 533 window.dataLayer.push({ 534 abUser: abUser 535 }); 536 } 537 538 window.__abUserVariant = abUser; 539 return abUser; 540} 541 542 543 544// ----- INIT -------------------------------------------------------// 545const initMain = () => { 546 initMenu(); 547 initScrolledListener(); 548 initAccordions();
549 initCountdowns(); 550 initTestimonials(); 551 initImageSlider(); 552 initTeaserSlider(); 553 initFormInputs(); 554 initFormSelects(); 555 initFormUploads(); 556 initButtonChat(); 557 initHeaderButton(); 558 initCopyToClipboard(); 559 initBrowserBackFix(); 560 initLottie(); 561 initFocus(); 562 initGlobalLottie(); 563 initABTesting(); 564 initAbKeyDataLayer(); 565}; 566 567 568initMain();
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.