PageSourceSearch

https://adriagel24.de/bundles/adriagelhomepage/js/adriagel-homepage-experience.js?1791345741

js adriagel24.de collected 2026-10-09 00:40:43 UTC 19,810 bytes, 437 lines download raw bytes

1/* ADRIAGEL Homepage Experience — Cinematic-Fassung (2026-07)
2   Ersetzt die Slider-Fassung: Hero-/Bundle-Kino-Rotation + Slider-Preloader sind
3   ausgebaut (Sektionen existieren nicht mehr). Neu: VideoDirector (max 2 parallel,
4   Play/Pause-Hysterese gegen Flackern), AV1-Formatwahl, Kachel-Hover-Warmup,
5   Farbwelt-Video-Buehne, Masken-Cleanup, Sichtbarkeits-Gating der Dauer-Animationen.
6   Erhalten aus Bestand: USP-Mobile-Autoslider, Farbwelt-Gruppen, Parallax, Count-up,
7   3D-Tilt, Sticky-Nav, Reveal-Observer, Adria-Licht-Layer-Loader.
8   Nur /* *\/-Kommentare (FroshHtmlMinify-Regel gilt zwar fuer Inline-JS, wir bleiben konsistent). */
9
10document.querySelector('.ag-homepage') && (function () {
11  'use strict';
12  var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
13  var canHover = window.matchMedia('(hover: hover)').matches;
14  var isMobile = window.matchMedia('(max-width: 768px)').matches;
15  /* Save-Data / 2G respektieren: dann KEIN Video-Autoplay, Poster bleiben stehen */
16  var conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
17  var dataSaver = !!(conn && (conn.saveData || /(^|-)2g$/.test(conn.effectiveType || '')));
18
19  /* ---------- AV1 bevorzugen (10-bit, bessere Qualitaet pro MB); H.264 bleibt Fallback ---------- */
20  var AV1_OK = document.createElement('video').canPlayType('video/mp4; codecs="av01.0.08M.10"') === 'probably';
21  /* Shopware asset() haengt einen Versions-Query an (…mp4?<ts>) — Regex muss das mitnehmen.
22     Mobil: 1280er-H.264-Rendition (<base>.m.mp4, universell dekodierbar, ~1/4 der Bytes) —
23     bei 390px-Viewport und 3x-DPR ist 1280 Quellbreite verlustfrei fuers Auge. */
24  function pickSrc(url) {
25    if (isMobile) { return url.replace(/\.mp4(\?[^#]*)?$/, '.m.mp4$1'); }
26    return AV1_OK ? url.replace(/\.mp4(\?[^#]*)?$/, '.av1.mp4$1') : url;
27  }
28
29  /* ---------- VideoDirector: hartes Budget von max 2 gleichzeitig spielenden Videos ---------- */
30  var Director = (function () {
31    var playing = new Set();
32    var CAP = 2;
33    function prioOf(v) { return parseInt(v.dataset.prio || '3', 10); }
34    function ensureSrc(v) { if (!v.src && v.dataset.src) { v.src = pickSrc(v.dataset.src); } }
35    function stop(v) { v.pause(); playing.delete(v); }
36    function request(v) {
37      if (reduced || dataSaver) { return false; }
38      ensureSrc(v);
39      if (playing.has(v)) { return true; }
40      if (playing.size >= CAP) {
41        var victim = Array.from(playing).sort(function (a, b) { return prioOf(b) - prioOf(a); })[0];
42        if (victim && prioOf(victim) >= prioOf(v)) { stop(victim); } else { return false; }
43      }
44      playing.add(v);
45      v.play().catch(function () { playing.delete(v); });
46      return true;
47    }
48    return { request: request, stop: stop, ensureSrc: ensureSrc };
49  })();
50
51  /* Hysterese gegen Play/Pause-Flackern: Start ab 35% Sichtbarkeit, Stop erst bei 0% */
52  var vio = new IntersectionObserver(function (entries) {
53    entries.forEach(function (e) {
54      var v = e.target;
55      if (e.intersectionRatio >= 0.35) {
56        if (v.dataset.prio !== '3' && !v.closest('.ag-colorworlds__stage')) { Director.request(v); }
57        if (v.closest('.ag-colorworlds__stage') && v.classList.contains('is-on')) { Director.request(v); }
58      } else if (!e.isIntersecting) {
59        Director.stop(v);
60      }
61    });
62  }, { threshold: [0, 0.35] });
63  document.querySelectorAll('video[data-director]').forEach(function (v) { vio.observe(v); });
64  document.querySelectorAll('video[data-eager]').forEach(function (v) { Director.request(v); });
65
66  /* ---------- Hero: Masken-Overlay nach dem Reveal aus dem DOM nehmen ---------- */
67  var heroMask = document.querySelector('.ag-hero__mask');
68  if (heroMask) {
69    if (reduced) { heroMask.remove(); }
70    else { heroMask.addEventListener('animationend', function () { heroMask.remove(); }, { once: true }); }
71  }
72
73  /* ---------- Sichtbarkeits-Gate fuer Dauer-Animationen (Gradient, Scroll-Cue) ---------- */
74  var aio = new IntersectionObserver(function (entries) {
75    entries.forEach(function (e) { e.target.classList.toggle('is-anim', e.isIntersecting); });
76  }, { threshold: 0.05 });
77  document.querySelectorAll('.ag-newsletter, .ag-hero').forEach(function (el) { aio.observe(el); });
78
79  /* ---------- Kategorien-Mosaik: Hover-Video mit Warmup (fluessig ab 1. Hover) ---------- */
80  (function () {
81    var cards = Array.from(document.querySelectorAll('.ag-category-card'));
82    if (!cards.length || reduced) { return; }
83
84    /* Touch-Geraete (kein Hover): die Kachel im mittleren Viewport-B
84and spielt
85       automatisch — das Kino kommt zum Daumen. Kein Warmup (Datenbudget: Videos
86       laden erst beim Eintritt ins Band, mobil als kleine .m.mp4-Rendition).
87       Director-Cap 2 + Hysterese des Bandes verhindern Flackern und Ueberlast. */
88    if (!canHover) {
89      if (dataSaver) { return; }
90      cards.forEach(function (card) {
91        var v = card.querySelector('video');
92        if (v) { v.addEventListener('playing', function () { v.classList.add('is-on'); }); }
93      });
94      var tio = new IntersectionObserver(function (entries) {
95        entries.forEach(function (e) {
96          var v = e.target.querySelector('video');
97          if (!v) { return; }
98          if (e.isIntersecting) {
99            Director.request(v);
100          } else {
101            Director.stop(v);
102            v.classList.remove('is-on');
103          }
104        });
105      }, { rootMargin: '-32% 0px -32% 0px', threshold: 0 });
106      cards.forEach(function (card) { if (card.querySelector('video')) { tio.observe(card); } });
107      return;
108    }
109    var activeCard = null;
110    var grid = document.querySelector('.ag-category-grid');
111    if (grid) {
112      var gio = new IntersectionObserver(function (entries) {
113        if (!entries.some(function (e) { return e.isIntersecting; })) { return; }
114        gio.disconnect();
115        var i = 0;
116        var warm = function () {
117          if (i >= cards.length) { return; }
118          var v = cards[i++].querySelector('video');
119          if (v && !v.src && v.dataset.src) { v.src = pickSrc(v.dataset.src); v.preload = 'auto'; }
120          if (v) { v.addEventListener('canplaythrough', warm, { once: true }); }
121          setTimeout(warm, 4000);
122        };
123        warm();
124      }, { threshold: 0.1 });
125      gio.observe(grid);
126    }
127    cards.forEach(function (card) {
128      var video = card.querySelector('video');
129      if (!video) { return; }
130      video.addEventListener('playing', function () {
131        if (activeCard === card) { video.classList.add('is-on'); }
132      });
133      function hideCard(c) {
134        var v = c.querySelector('video');
135        if (!v) { return; }
136        v.classList.remove('is-on');
137        Director.stop(v);
138      }
139      function show() {
140        if (activeCard && activeCard !== card) { hideCard(activeCard); }
141        activeCard = card;
142        Director.ensureSrc(video);
143        if (video.readyState >= 3) {
144          if (Director.request(video)) { video.classList.add('is-on'); }
145        } else {
146          Director.request(video);
147        }
148      }
149      card.addEventListener('mouseenter', show);
150      card.addEventListener('focusin', show);
151      card.addEventListener('mouseleave', function () {
152        hideCard(card);
153        if (activeCard === card) { activeCard = null; }
154      });
155    });
156  })();
157
158  /* ---------- Neuheiten: cinematischer Auto-Slider (Tolga 2026-07-13) ----------
159     Native scroll-snap-Rail (Compositor, kein Layout-Thrash) + sanftes Auto-Advance.
160     Pausiert bei Interaktion (9s), ausserhalb des Viewports, bei verstecktem Tab
161     und unter prefers-reduced-motion. Dots nur mobil (CSS), Tap = direkt hin. */
162  (function () {
163    var rail = document.querySelector('.ag-neuheiten__rail[data-ag-autoslider]');
164    if (!rail) { return; }
165    var cards = Array.from(rail.querySelectorAll('.ag-neuheiten__card'));
166    if (cards.length < 2) { return; }
167    var dotsWrap = document.querySelector('.ag-neuheiten__dots');
168    var inView = false;
169    var holdUntil = 0;
170
171    /* live abfragen statt Load-Snapshot: Tablet-Rotation um die 768px-Grenze */
172    function alignCenter() { return window.matchMedia('(max-width: 768px)').matches; }
173    function targetLeft(card) {
174      var left = alignCenter()
175        ? card.offsetLeft - (rail.clientWidth - card.offsetWidth) / 2
176        : card.offsetLeft - rail.offsetLeft;
177      return Math.max(0, Math.min(left, rail.scrollWidth - rail.clientWidth));
178    }
179    function nearestIndex() {
180      var best = 0;
181      var bestDist = Infinity;
182      cards.forEach(function (c, i) {
183        var d = Math.abs(targetLeft(c) - rail.scrollLeft);
184        if (d < bestDist) { bestDist = d; best = i; }
185      });
186      return best;
187    }
188    function goTo(i) {
189      rail.scrollTo({ left: targetLeft(cards[i]), behavior: 'smooth' });
190      markDot(i);
191    }
192
193    /* Dots (Anker kommt aus Twig, Buttons baut JS) */
194    var dots = [];
195    if (dotsWrap) {
196      cards.forEach(function (c, i) {
197        var b = document.createElement('button');
198        b.type = 'button';
199        b.setAttribute('aria-label', String(i + 1) + ' / ' + cards.length);
200        b.addEventListener('click', function () { holdUntil = Date.now() + 9000; goTo(i); });
201        dotsWrap.appendChild(b);
202        dots.push(b);
203      });
204    }
205    function markDot(i) {
206      dots.forEach(function (d, k) { d.classList.toggle('is-active', k === i); });
207    }
208    markDot(0);
209    var scrollSync = null;
210    rail.addEventListener('scroll', function () {
211      if (scrollSync) { return; }
212      scrollSync = requestAnimationFrame(function () { scrollSync = null; markDot(nearestIndex()); });
213    }, { passive: true });
214
215    /* Interaktion pausiert das Auto-Advance */
216    ['pointerdown', 'wheel', 'touchstart'].forEach(function (ev) {
217      rail.addEventListener(ev, function () { holdUntil = Date.now() + 9000; }, { passive: true });
218    });
219    rail.addEventListener('pointerenter', function () { holdUntil = Date.now() + 9000; });
220
221    var sio = new IntersectionObserver(function (entries) {
222      inView = entries.some(function (e) { return e.isIntersecting; });
223    }, { threshold: 0.25 });
224    sio.observe(rail);
225
226    if (!reduced) {
227      /* Tolga 2026-07-13: alle 3 Sekunden nach links weiter */
228      setInterval(function () {
229        if (!inView || document.hidden || Date.now() < holdUntil) { return; }
230        var atEnd = rail.scrollLeft + rail.clientWidth >= rail.scrollWidth - 8;
231        var i = nearestIndex();
232        goTo(atEnd || i >= cards.length - 1 ? 0 : i + 1);
233      }, 3000);
234    }
235  })();
236
237  /* ---------- USP-Bar: Mobile-Autoslider (Bestand) ---------- */
238  (function () {
239    var mq = window.matchMedia('(max-width: 768px)');
240    var timer = null;
241    function start() {
242      var items = document.querySelectorAll('.ag-usp__item');
243      if (!items.length) { return; }
244      if (!mq.matches) { return stopAll(items); }
245      items.forEach(function (el, i) { el.classList.toggle('is-active', i === 0); });
246      var idx = 0;
247      timer = setInterval(function () {
248        items[idx].classList.remove('is-active');
249        idx = (idx + 1) % items.length;
250        items[idx].classList.add('is-active');
251      }, 3000);
252    }
253    function stopAll(items) {
254      clearInterval(timer);
255      timer = null;
256      if (items) { items.forEach(function (el) { el.classList.remove('is-active'); }); }
257    }
258    mq.addEventListener('change', function () {
259      stopAll(document.querySelectorAll('.ag-usp__item'));
260      start();
261    });
262    start();
263  })();
264
265  /* ---------- Farbwelten: Buttons schalten Buehnen-Video + Caption + Produktgruppe ---------- */
266  (function () {
267    var section = document.querySelector('.ag-colorworlds');
268    if (!section) { return; }
269    var buttons = section.querySelectorAll('.ag-color-btn');
270    var groups = section.querySelectorAll('.ag-colorworlds__group');
271    var stage = section.querySelector('.ag-colorworlds__stage');
272    var caption = document.getElementById('cwCaption');
273    buttons.forEach(function (btn) {
274      btn.addEventListener('click', function () {
275        var color = btn.dataset.color;
276        buttons.forEach(function (b) { b.classList.remove('is-active'); });
277        btn.classList.add('is-active');
278        section.setAttribute('data-color', color);
279        groups.forEach(function (g) {
280          if (g.dataset.color === color) {
281            g.style.display = 'grid';
282            g.style.animation = 'ag-fade-in 0.5s ease forwards';
283          } else {
284            g.style.display = 'none';
285          }
286        });
287        if (stage) {
288          stage.querySelectorAll('video').forEach(function (v) {
289            var on = v.dataset.world === color;
290            v.classList.toggle('is-on', on);
291            if (on) { Director.request(v); } else { Director.stop(v); }
292          });
293        }
294        if (caption) { caption.textContent = btn.textContent.trim(); }
295      });
296    });
297    var style = document.createElement('style');
298    style.textContent = '@keyframes ag-fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }';
299    document.head.appendChild(style);
300  })();
301
302  /* ---------- Parallax (Bestand, .ag-parallax + data-speed) ---------- */
303  (function () {
304    var els = document.querySelectorAll('.ag-parallax');
305    if (!els.length || reduced) { return; }
306    var ticking = false;
307    function update() {
308      els.forEach(function (el) {
309        var speed = parseFloat(el.dataset.speed) || 0.3;
310        var r = el.getBoundingClientRect();
311        if (r.top < window.innerHeight && r.bottom > 0) {
312          el.style.transform = 'translateY(' + (r.top - window.innerHeight / 2) * speed + 'px)';
313        }
314      });
315      ticking = false;
316    }
317    window.addEventListener('scroll', function () {
318      if (!ticking) { requestAnimationFrame(update); ticking = true; }
319    }, { passive: true });
320  })();
321
322  /* ---------- Count-up (Bestand) ---------- */
323  (function () {
324    var els = document.querySelectorAll('[data-count]');
325    if (!els.length) { return; }
326    var cio = new IntersectionObserver(function (entries) {
327      entries.forEach(function (e) {
328        if (!e.isIntersecting) { return; }
329        var el = e.target;
330        cio.unobserve(el);
331        var target = parseInt(el.dataset.count, 10);
332        var suffix = el.dataset.suffix || '';
333        if (reduced) { el.textContent = target + suffix; return; }
334        var t0 = performance.now();
335        requestAnimationFrame(function tick(now) {
336          var p = Math.min((now - t0) / 2000, 1);
337          el.textContent = Math.round(target * (1 - Math.pow(1 - p, 4))) + suffix;
338          if (p < 1) { requestAnimationFrame(tick); }
339        });
340      });
341    }, { threshold: 0.5 });
342    els.forEach(function (el) { cio.observe(el); });
343  })();
344
345  /* ---------- 3D-Tilt auf Produktkarten (Bestand, nur pointer:fine) ---------- */
346  (function () {
347    var cards = document.querySelectorAll('.ag-neuheiten__card, .ag-product-card');
348    if (!cards.length || window.matchMedia('(pointer: coarse)').matches || reduced) { return; }
349    cards.forEach(function (card) {
350      card.addEventListener('mousemove', function (ev) {
351        var r = card.getBoundingClientRect();
352        var x = (ev.clientX - r.left) / r.width - 0.5;
353        var y = (ev.clientY - r.top) / r.height - 0.5;
354        card.style.transform = 'perspective(800px) rotateY(' + 8 * x + 'deg) rotateX(' + (-8 * y) + 'deg) translateY(-5px)';
355      });
356      card.addEventListener('mouseleave', function () {
357        card.style.transform = '';
358        card.style.transition = 'transform 0.5s cubic-bezier(0.4, 0, 0.2, 1)';
359        setTimeout(function () { card.style.transition = ''; }, 500);
360      });
361      card.addEventListener('mouseenter', function () { card.style.transition = 'none'; });
362    });
363  })();
364
365  /* ---------- Sticky-Nav ab 992px (Bestand) ---------- */
366  (function () {
367    if (window.innerWidth < 992) { return; }
368    var menu = document.querySelector('.main-navigation-menu-list');
369    if (!menu) { return; }
370    var links = Array.from(menu.querySelectorAll(':scope > .nav-item > .nav-link'));
371    if (!links.length) { return; }
372    var nav = document.createElement('div');
373    nav.className = 'ag-sticky-nav';
374    var html = '<div style="display:flex;justify-content:center;align-items:center;gap:clamp(0.8rem,2vw,2rem);padding:0.65rem 2rem;flex-wrap:nowrap;font-family:Jost,system-ui,sans-serif">';
375    links.forEach(function (link) {
376      var text = (link.textContent || '').trim();
377      if (!text) { return; }
378      var isAktion = text.toLowerCase().indexOf('finalaktion') !== -1 || text.toLowerCase().indexOf('aktionsbundle') !== -1;
379      var color = isAktion ? '#b5201e' : 'rgba(255,255,255,0.85)';
380      var hover = isAktion ? '#8e1518' : '#C9A26D';
381      html += '<a href="' + link.getAttribute('href') + '" style="color:' + color + ';text-decoration:none;font-size:1.18rem;font-weight:' + (isAktion ? '700' : '500') + ';letter-spacing:0.08em;padding:0.3rem 0;transition:all 0.25s ease;white-space:nowrap;border-bottom:1px solid transparent;text-transform:uppercase" onmouseenter="this.style.color=\'' + hover + '\';this.style.borderBottomColor=\'' + hover + '\'" onmouseleave="this.style.color=\'' + color + '\';this.style.borderBottomColor=\'transparent\'">
381' + text + '</a>';
382    });
383    html += '</div>';
384    nav.innerHTML = html;
385    document.body.appendChild(nav);
386    var ticking = false;
387    var visible = false;
388    window.addEventListener('scroll', function () {
389      if (ticking) { return; }
390      requestAnimationFrame(function () {
391        var show = window.scrollY > 300;
392        if (show !== visible) { nav.classList.toggle('is-visible', show); visible = show; }
393        ticking = false;
394      });
395      ticking = true;
396    }, { passive: true });
397  })();
398
399  /* ---------- Reveal-Observer (Bestand: ag-reveal -> ag-revealed) ---------- */
400  (function () {
401    var els = document.querySelectorAll('.ag-reveal, .ag-reveal--left, .ag-reveal--right, .ag-reveal--scale');
402    if (!els.length) { return; }
403    var rio = new IntersectionObserver(function (entries) {
404      entries.forEach(function (e) {
405        if (e.isIntersecting) {
406          e.target.classList.add('ag-revealed');
407          rio.unobserve(e.target);
408        }
409      });
410    }, { threshold: 0.12, rootMargin: '0px 0px 120px 0px' });
411    els.forEach(function (el) { rio.observe(el); });
412  })();
413})();
414
415/* Adria-Licht-Layer-Loader (Bestand, Phase 3): laedt den WebGL-Kaustik-Shader nur,
416   wenn ALLE Guards bestehen. Mobile, reduced-motion, Save-Data, schwache
417   Hardware und fehlendes WebGL2 bekommen exakt die heutige Seite. */
418document.querySelector('.ag-homepage') && (function () {
419  if (window.innerWidth < 992) { return; }
420  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { return; }
421  if (window.matchMedia('(pointer: coarse)').matches) { return; }
422  var c = navigator.connection;
423  if (c && (c.saveData || /2g/.test(c.effectiveType || ''))) { return; }
424  if (navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4) { return; }
425  if (navigator.deviceMemory && navigator.deviceMemory < 4) { return; }
426  try { var t = document.createElement('canvas'); if (!t.getContext('webgl2')) { return; } } catch (e) { return; }
427  function load() {
428    var s = document.createElement('script');
429    /* Cache-Buster: erbt den Versions-Stempel des Haupt-Scripts */
430    var me = document.querySelector('script[src*="adriagel-homepage-experience"]');
431    var v = me && me.src.indexOf('?') > -1 ? me.src.split('?')[1] : '1';
432    s.src = '/bundles/adriagelhomepage/js/adriagel-adria-layer.js?' + v;
433    s.defer = true;
434    document.head.appendChild(s);
435  }
436  'requestIdleCallback' in window ? requestIdleCallback(load, { timeout: 4000 }) : setTimeout(load, 3000);
437})();

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.