PageSourceSearch

https://hoellinger-juice.at/wp-content/themes/hoellinger/assets/js/main.js?ver=1787222946

js hoellinger-juice.at collected 2026-09-26 02:24:27 UTC 18,239 bytes, 510 lines download raw bytes

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.