1jQuery(document).ready(function ($) { 2 3 /* 4 * Menütext anpassen, damit er in den Header passt 5 * Dynmisch gelöst mit eine beliebigen Anzahl an Menüeinträgen, die sich auch ändern kann. 6 */ 7 function initializeHeaderMenuFit() { 8 const menu = document.querySelector("#avia-menu"); 9 const container = document.querySelector("#header_main .container"); 10 const logo = document.querySelector("#header_main .logo"); 11 12 if (!menu || !container || !logo) { 13 return; 14 } 15 16 const minFontSize = 10.5; 17 const maxFontSize = 20; 18 const fontStep = 0.25; 19 const minPadX = 2; 20 const maxPadX = 21; 21 const minLetterSpacing = 0.2; 22 const maxLetterSpacing = 2.6; 23 24 const excludedTextItemSelector = ".menu-item-search-dropdown, .mein-konto, #menu-item-20243, .menu-item-gtranslate, .av-burger-menu-main, .menu-item-avia-special, .no-desktop"; 25 const buttonItemSelector = ".menu-item-search-dropdown, .mein-konto, #menu-item-20243, .menu-item-gtranslate, .av-burger-menu-main"; 26 27 function clamp(value, min, max) { 28 return Math.min(Math.max(value, min), max); 29 } 30 31 function lerp(min, max, ratio) { 32 return min + (max - min) * ratio; 33 } 34 35 function isVisible(element) { 36 const style = window.getComputedStyle(element); 37 return style.display !== "none" && style.visibility !== "hidden" && element.getClientRects().length > 0; 38 } 39 40 function sumWidths(elements) { 41 return elements.reduce(function (sum, element) { 42 const rect = element.getBoundingClientRect(); 43 const style = window.getComputedStyle(element); 44 const marginLeft = parseFloat(style.marginLeft) || 0; 45 const marginRight = parseFloat(style.marginRight) || 0; 46 47 return sum + rect.width + marginLeft + marginRight; 48 }, 0); 49 } 50 51 function getTextItems() { 52 return Array.prototype.slice.call(menu.children).filter(function (item) { 53 return item.matches(".menu-item-top-level") && 54 !item.matches(excludedTextItemSelector) && 55 item.querySelector(".avia-menu-text") && 56 isVisible(item); 57 }); 58 } 59 60 function getButtonItems() { 61 return Array.prototype.slice.call(menu.children).filter(function (item) { 62 return item.matches(buttonItemSelector) && isVisible(item); 63 }); 64 } 65 66 function setMenuTextVars(fontSize) { 67 const ratio = clamp((fontSize - minFontSize) / (maxFontSize - minFontSize), 0, 1); 68 const padX = lerp(minPadX, maxPadX, ratio); 69 const letterSpacing = lerp(minLetterSpacing, maxLetterSpacing, ratio); 70 71 menu.style.setProperty("--header-menu-font-size", fontSize.toFixed(2) + "px"); 72 menu.style.setProperty("--header-menu-pad-x", padX.toFixed(2) + "px"); 73 menu.style.setProperty("--header-menu-letter-spacing", letterSpacing.toFixed(2) + "px"); 74 } 75 76 function resetMenuVars() { 77 menu.style.removeProperty("--header-logo-clearance"); 78 menu.style.removeProperty("--header-menu-font-size"); 79 menu.style.removeProperty("--header-menu-pad-x"); 80 menu.style.removeProperty("--header-menu-letter-spacing"); 81 menu.style.removeProperty("--header-menu-offset-y"); 82 menu.style.removeProperty("--header-utility-offset-y"); 83 } 84 85 function fitHeaderMenu() { 86 if (window.innerWidth < 950) { 87 resetMenuVars(); 88 return; 89 } 90 91 const menuRect = menu.getBoundingClientRect(); 92 const logoRect = logo.getBoundingClientRect(); 93 94 const searchItem = Array.prototype.slice.call(menu.children).find(function (item) { 95 return item.matches(".menu-item-search-dropdown") && isVisible(item); 96 }); 97 const minLogoGap = searchItem 98 ? parseFloat(window.getComputedStyle(searchItem).marginLeft) || 12 99 : 12; 100 101 const logoClearance = Math.max(0, logoRect.right - menuRect.left + minLogoGap); 102 const availableWidth = Math.max(0, menuRect.width - logoClearance); 103 104 menu.style.setProperty("--header-logo-clearance", logoClearance.toFixed(2) + "px"); 105 106 let fittedFontSize = minFontSize; 107 108 for (let fontSize = maxFontSize; fontSize >= minFontSize; fontSize -= fontStep) { 109 setMenuTextVars(fontSize); 110 111 const textWidth = sumWidths(getTextItems()); 112 const buttonWidth = sumWidths(getButtonItems()); 113 114 if (textWidth + buttonWidth <= availableWidth) { 115 fittedFontSize = fontSize; 116 break; 117 } 118 } 119 120 setMenuTextVars(fittedFontSize); 121 menu.style.setProperty("--header-menu-offset-y", "0px"); 122 menu.style.setProperty("--header-utility-offset-y", "0px"); 123 124 const firstTextLink = getTextItems() 125 .map(function (item) { return item.querySelector(":scope > a"); }) 126 .find(Boolean); 127 128 if (firstTextLink) { 129 const logoRectAfterFit = logo.getBoundingClientRect(); 130 const linkRect = firstTextLink.getBoundingClientRect();
131 const logoCenterY = logoRectAfterFit.top + logoRectAfterFit.height / 2; 132 const linkCenterY = linkRect.top + linkRect.height / 2; 133 134 menu.style.setProperty( 135 "--header-menu-offset-y", 136 (logoCenterY - linkCenterY - 1).toFixed(2) + "px" 137 ); 138 } 139 140 const firstUtilityItem = getButtonItems()[0]; 141 142 if (firstUtilityItem) { 143 const logoRectAfterFit = logo.getBoundingClientRect(); 144 const utilityRect = firstUtilityItem.getBoundingClientRect(); 145 const logoCenterY = logoRectAfterFit.top + logoRectAfterFit.height / 2; 146 const utilityCenterY = utilityRect.top + utilityRect.height / 2; 147 148 menu.style.setProperty( 149 "--header-utility-offset-y", 150 (logoCenterY - utilityCenterY).toFixed(2) + "px" 151 ); 152 } 153 } 154 155 let queued = false; 156 157 function scheduleFit() { 158 if (queued) { 159 return; 160 } 161 162 queued = true; 163 164 window.requestAnimationFrame(function () { 165 queued = false; 166 fitHeaderMenu(); 167 }); 168 } 169 170 function fitAndRevealMenu() { 171 window.requestAnimationFrame(function () { 172 fitHeaderMenu(); 173 174 window.requestAnimationFrame(function () { 175 menu.classList.add("header-menu-fit-ready"); 176 }); 177 }); 178 } 179 window.addEventListener("resize", scheduleFit, { passive: true }); 180 181 const revealFallback = window.setTimeout(fitAndRevealMenu, 3000); 182 183 function revealAfterFonts() { 184 window.clearTimeout(revealFallback); 185 fitAndRevealMenu(); 186 } 187 188 if (document.fonts && document.fonts.ready) { 189 document.fonts.ready.then(revealAfterFonts, revealAfterFonts); 190 } else { 191 revealAfterFonts(); 192 } 193 194 if (window.ResizeObserver) { 195 const observer = new ResizeObserver(scheduleFit); 196 observer.observe(container); 197 observer.observe(menu); 198 observer.observe(logo); 199 } 200 } 201 initializeHeaderMenuFit(); 202 203 /* 204 * Produkt-Beschreibung: langen Text einklappen und per "Mehr lesen" aufklappen. 205 * Der Button erscheint nur, wenn der Text die eingeklappte Höhe (~10 Zeilen) überschreitet. 206 */ 207 function initializeBeschreibungToggle() { 208 const VISIBLE_LINES = 10; 209 // Der Button erscheint erst, wenn mind. so viele Zeilen zusätzlich verdeckt 210 // würden â kurze Ãberhänge werden komplett angezeigt statt eingeklappt. 211 const MIN_HIDDEN_LINES = 5; 212 const LABEL_MORE = "Mehr lesen"; 213 const LABEL_LESS = "Weniger anzeigen"; 214 215 const blocks = document.querySelectorAll(".beschreibung"); 216 217 // "rgb(a)(...)" der tatsächlichen Hintergrundfarbe -> deckende + transparente Variante. 218 function fadeColors(el) { 219 let node = el; 220 let color = ""; 221 222 while (node && node !== document.documentElement) { 223 const bg = window.getComputedStyle(node).backgroundColor; 224 if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") { 225 color = bg; 226 break; 227 } 228 node = node.parentElement; 229 } 230 231 if (!color) { 232 return null; 233 } 234 235 const nums = color.match(/[\d.]+/g); 236 if (!nums || nums.length < 3) { 237 return null; 238 } 239 240 const rgb = nums.slice(0, 3).join(", "); 241 return { 242 to: "rgb(" + rgb + ")", 243 from: "rgba(" + rgb + ", 0)" 244 }; 245 } 246 247 blocks.forEach(function (block) { 248 const content = block.querySelector(".beschreibung__content"); 249 const toggle = block.querySelector(".beschreibung__toggle"); 250 const label = block.querySelector(".beschreibung__toggle-label"); 251 252 if (!content || !toggle || !label) { 253 return; 254 } 255 256 // Fade an die tatsächliche (pro Produkt unterschiedliche) Hintergrundfarbe anpassen. 257 const colors = fadeColors(block); 258 if (colors) { 259 content.style.setProperty("--beschreibung-fade-from", colors.from); 260 content.style.setProperty("--beschreibung-fade-to", colors.to); 261 } 262 263 // Höhe von 10 Zeilen anhand der tatsächlichen line-height berechnen. 264 function lineHeightPx() { 265 const reference = content.querySelector("p, li, h2") || content; 266 let lineHeight = parseFloat(window.getComputedStyle(reference).lineHeight); 267 268 if (!lineHeight || isNaN(lineHeight)) { 269 const fontSize = parseFloat(window.getComputedStyle(reference).fontSize) || 20; 270 lineHeight = fontSize * 1.5; 271 } 272 273 return lineHeight; 274 } 275 276 function collapsedHeight() { 277 return Math.round(lineHeightPx() * VISIBLE_LINES); 278 } 279 280 function collapse() { 281 const maxHeight = collapsedHeight(); 282 // Erst ab dieser Höhe einklappen (verdeckt mind. MIN_HIDDEN_LINES Zeilen). 283 const triggerHeight = maxHeight + Math.round(lineHeightPx() * MIN_HIDDEN_LINES); 284 285 // Nur einklappen, wenn der Inhalt deutlich länger als die sichtbare Höhe ist. 286 if (content.scrollHeight <= triggerHeight) { 287 content.classList.remove("is-clamped"); 288 content.style.maxHeight = ""; 289 toggle.setAttribute("hidden", ""); 290 toggle.setAttribute("aria-expanded", "false"); 291 return; 292 } 293 294 content.classList.add("is-clamped"); 295 content.style.maxHeight = maxHeight + "px"; 296 toggle.removeAttribute("hidden"); 297 toggle.setAttribute("aria-expanded", "false"); 298 label.textContent = LABEL_MORE; 299 } 300 301 function expand() { 302 content.style.maxHeight = content.scrollHeight + "px"; 303 content.classList.remove("is-clamped"); 304 toggle.setAttribute("aria-expanded", "true"); 305 label.textContent = LABEL_LESS; 306 307 // Nach dem Ãbergang die feste Höhe entfernen, damit spätere Reflows passen. 308 window.setTimeout(function () { 309 if (toggle.getAttribute("aria-expanded") === "true") { 310 content.style.maxHeight = ""; 311 } 312 }, 450); 313 } 314 315 toggle.addEventListener("click", function () { 316 if (toggle.getAttribute("aria-expanded") === "true") { 317 collapse(); 318 } else { 319 expand(); 320 } 321 }); 322
323 // Beim Laden und bei GröÃenänderung neu vermessen (aber nicht, solange aufgeklappt). 324 collapse(); 325 326 let resizeTimer; 327 window.addEventListener("resize", function () { 328 window.clearTimeout(resizeTimer); 329 resizeTimer = window.setTimeout(function () { 330 if (toggle.getAttribute("aria-expanded") !== "true") { 331 collapse(); 332 } 333 }, 200); 334 }); 335 }); 336 } 337 338 initializeBeschreibungToggle(); 339 340 341 /* 342 * GSAP Animationen 343 */ 344 const hasGsapAnimations = 345 $(".gsap-animations").length > 0 && 346 typeof window.gsap !== "undefined" && 347 typeof window.ScrollTrigger !== "undefined"; 348 349 if (hasGsapAnimations) { 350 gsap.registerPlugin(ScrollTrigger); 351 } 352 353 function initializeAnimation() { 354 const pfirsich = document.querySelector(".pfirsich"); 355 const erdbeerspalte = document.querySelector(".erdbeerspalte"); 356 const johannisbeere = document.querySelector(".johannisbeere"); 357 const heidelbeere = document.querySelector(".heidelbeere"); 358 const passionsfrucht = document.querySelector(".passionsfrucht"); 359 const pfirsichspalte = document.querySelector(".pfirsichspalte"); 360 const holunder = document.querySelector(".holunder"); 361 const erdbeere = document.querySelector(".erdbeere"); 362 363 // pfirsich 364 gsap.timeline({ 365 scrollTrigger: { 366 trigger: pfirsich, 367 start: "top 80%", 368 end: "+=50%", 369 scrub: 1.5, 370 // markers: true, 371 }, 372 }) 373 .fromTo( 374 pfirsich, 375 { x: -100, y: -50, rotate: -20, transformOrigin: "center" }, 376 { x: 0, y: 0, rotate: 0, duration: 1 } 377 ); 378 379 // erdbeerspalte 380 gsap.timeline({ 381 scrollTrigger: { 382 trigger: erdbeerspalte, 383 start: "top 80%", 384 end: "+=60%", 385 scrub: 1.5, 386 // markers: true, 387 }, 388 }) 389 .fromTo( 390 erdbeerspalte, 391 { x: 300, y: -50, rotate: 180, transformOrigin: "center" }, 392 { x: 0, y: 0, rotate: 0, duration: 1 } 393 ); 394 395 // johannisbeere 396 gsap.timeline({ 397 scrollTrigger: { 398 trigger: johannisbeere, 399 start: "top 90%", 400 end: "+=50%", 401 scrub: 1.5, 402 // markers: true, 403 }, 404 }) 405 .fromTo( 406 johannisbeere, 407 { x: 50, y: 50, rotate: 20, transformOrigin: "center" }, 408 { x: 0, y: 0, rotate: 0, duration: 1 } 409 ); 410 411 // heidelbeere 412 gsap.timeline({ 413 scrollTrigger: { 414 trigger: heidelbeere, 415 start: "top 80%", 416 end: "+=50%", 417 scrub: 1.5, 418 // markers: true, 419 }, 420 }) 421 .fromTo( 422 heidelbeere, 423 { x: -100, y: -50, rotate: -20, transformOrigin: "center" }, 424 { x: 0, y: 0, rotate: 0, duration: 1 } 425 ); 426 427 // passionsfrucht 428 gsap.timeline({ 429 scrollTrigger: { 430 trigger: passionsfrucht, 431 start: "top 80%", 432 end: "+=60%", 433 scrub: 1.5, 434 // markers: true, 435 }, 436 }) 437 .fromTo( 438 passionsfrucht, 439 { x: 200, y: -100, rotate: -30, transformOrigin: "center" }, 440 { x: 0, y: 0, rotate: 0, duration: 1 } 441 ); 442 443 // pfirsichspalte 444 gsap.timeline({ 445 scrollTrigger: { 446 trigger: pfirsichspalte, 447 start: "top 80%", 448 end: "+=60%", 449 scrub: 1.5, 450 // markers: true, 451 }, 452 }) 453 .fromTo( 454 pfirsichspalte, 455 { x: -30, y: -100, rotate: -10, transformOrigin: "center" }, 456 { x: 0, y: 0, rotate: 0, duration: 1 } 457 ); 458 459 // holunder 460 gsap.timeline({ 461 scrollTrigger: { 462 trigger: holunder, 463 start: "top 90%", 464 end: "+=40%", 465 scrub: 1.5, 466 // markers: true, 467 }, 468 }) 469 .fromTo( 470 holunder, 471 { x: -100, y: 50, rotate: -10, transformOrigin: "center" }, 472 { x: 0, y: 0, rotate: 0, duration: 1 } 473 ); 474 475 // erdbeere 476 gsap.timeline({ 477 scrollTrigger: { 478 trigger: erdbeere, 479 start: "top 80%", 480 end: "+=50%", 481 scrub: 1.5, 482 // markers: true, 483 }, 484 }) 485 .fromTo( 486 erdbeere, 487 { x: 200, y: -70, rotate: -20, transformOrigin: "center" }, 488 { x: 0, y: 0, rotate: 0, duration: 1 } 489 ); 490 } 491 492 if (hasGsapAnimations) { 493 setTimeout(function () { 494 initializeAnimation(); 495 }, 1000); 496 497 setTimeout(function () {
498 ScrollTrigger.update(); 499 }, 2000); 500 501 $(window).on("resize.amhaGsapAnimations", function () { 502 ScrollTrigger.update(); 503 ScrollTrigger.refresh(); 504 }); 505 506 $(window).on("scroll.amhaGsapAnimations", function () { 507 ScrollTrigger.update(); 508 }); 509 } 510});
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.