Files
Chickago-Boys/assets/js/main.js
T
rootandClaude Opus 5.5 e77f081b44 Fliegende Zutaten natürlich gestreut statt im Kreis
- Feste Positionen durch eine Streuung mit festem Seed ersetzt: dicht am Essen, nach außen immer weniger, unregelmäßige Winkel, Größe nimmt mit dem Abstand ab, keine Überlappung untereinander
- Gilt für alle Stellen (Hero, Champion-Karten, Wings, Tenders, Komm vorbei, Speisekarte); Explosion beim Scrollen unverändert
- Grenzen je Stelle, damit Zutaten nicht über Text liegen (Tenders links, Komm vorbei oben)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 03:44:08 +00:00

616 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Chickago Boys – Interaktion & Animation */
(function () {
'use strict';
var root = document.documentElement;
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var hasGsap = !!(window.gsap && window.ScrollTrigger);
var $ = function (s, r) { return (r || document).querySelector(s); };
var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
if (!hasGsap) root.classList.remove('anim');
/* ---------- Jahr ---------- */
$$('[data-year]').forEach(function (el) { el.textContent = new Date().getFullYear(); });
/* ---------- Öffnungszeiten (täglich 12–22 Uhr, Zeitzone Berlin) ---------- */
var OPEN = 12 * 60, CLOSE = 22 * 60;
function berlinNow() {
var parts = new Intl.DateTimeFormat('en-US', {
timeZone: 'Europe/Berlin', weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23'
}).formatToParts(new Date());
var map = {};
parts.forEach(function (p) { map[p.type] = p.value; });
var days = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
return { day: days[map.weekday], minutes: parseInt(map.hour, 10) * 60 + parseInt(map.minute, 10) };
}
function updateOpenState() {
var now = berlinNow();
var isOpen = now.minutes >= OPEN && now.minutes < CLOSE;
var text = isOpen ? 'Jetzt geöffnet · bis 22 Uhr'
: now.minutes < OPEN ? 'Geschlossen · öffnet heute um 12 Uhr'
: 'Geschlossen · öffnet morgen um 12 Uhr';
$$('[data-open-badge]').forEach(function (el) {
el.textContent = text;
el.classList.toggle('is-open', isOpen);
el.classList.toggle('is-closed', !isOpen);
});
$$('[data-hours] tr').forEach(function (tr) {
tr.classList.toggle('is-today', parseInt(tr.getAttribute('data-day'), 10) === now.day);
});
}
updateOpenState();
setInterval(updateOpenState, 60000);
/* ---------- Smooth Scroll (Lenis) ---------- */
var lenis = null;
if (!reduce && window.Lenis) {
lenis = new window.Lenis({ duration: 1.1, smoothWheel: true });
if (hasGsap) {
lenis.on('scroll', window.ScrollTrigger.update);
window.gsap.ticker.add(function (t) { lenis.raf(t * 1000); });
window.gsap.ticker.lagSmoothing(0);
} else {
var raf = function (t) { lenis.raf(t); requestAnimationFrame(raf); };
requestAnimationFrame(raf);
}
}
function navOffset() { return -(($('#nav') || {}).offsetHeight || 0) - 12; }
function scrollToTarget(target) {
if (lenis) lenis.scrollTo(target, { offset: navOffset(), duration: 1.1 });
else target.scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' });
}
document.addEventListener('click', function (e) {
var a = e.target.closest && e.target.closest('a[href*="#"]');
if (!a) return;
var url = new URL(a.href, location.href);
if (url.pathname !== location.pathname || !url.hash) return;
var target = url.hash === '#top' ? document.body : document.getElementById(decodeURIComponent(url.hash.slice(1)));
if (!target) return;
e.preventDefault();
closeMenu(false);
if (url.hash === '#top') { lenis ? lenis.scrollTo(0) : window.scrollTo({ top: 0, behavior: reduce ? 'auto' : 'smooth' }); }
else scrollToTarget(target);
history.replaceState(null, '', url.hash);
});
/* ---------- Navigation ein-/ausblenden ---------- */
var nav = $('#nav');
var lastY = window.scrollY;
function onScroll() {
var y = window.scrollY;
if (!nav) return;
if (document.body.classList.contains('menu-open')) { lastY = y; return; }
if (y > 140 && y - lastY > 6) nav.classList.add('is-hidden');
else if (lastY - y > 6 || y < 140) nav.classList.remove('is-hidden');
document.body.classList.toggle('nav-visible', !nav.classList.contains('is-hidden') && y > 10);
nav.classList.toggle('is-scrolled', y > 60);
lastY = y;
}
window.addEventListener('scroll', onScroll, { passive: true });
/* ---------- Menü ---------- */
var btn = $('#menu-btn'), panel = $('#menu-panel'), overlay = $('#menu-overlay');
function setLabel(txt) { $$('.menu-label', btn).forEach(function (s) { s.textContent = txt; }); }
function openMenu() {
document.body.classList.add('menu-open');
nav.classList.add('is-open');
btn.setAttribute('aria-expanded', 'true');
setLabel('Schließen');
if (lenis) lenis.stop();
if (hasGsap && !reduce) {
window.gsap.fromTo($$('li', panel), { y: 18, opacity: 0 }, { y: 0, opacity: 1, duration: .38, stagger: .06, ease: 'power3.out', delay: .14 });
}
var first = $('a', panel);
if (first) setTimeout(function () { first.focus({ preventScroll: true }); }, 60);
}
function closeMenu(returnFocus) {
if (!document.body.classList.contains('menu-open')) return;
document.body.classList.remove('menu-open');
nav.classList.remove('is-open');
btn.setAttribute('aria-expanded', 'false');
setLabel('Menü');
if (lenis) lenis.start();
if (returnFocus !== false) btn.focus({ preventScroll: true });
}
if (btn && panel) {
btn.addEventListener('click', function () {
document.body.classList.contains('menu-open') ? closeMenu() : openMenu();
});
overlay && overlay.addEventListener('click', function () { closeMenu(); });
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeMenu(); });
$$('a', panel).forEach(function (a) { a.addEventListener('click', function () { closeMenu(false); }); });
}
/* ---------- Navigation auf dunklen Flächen hell einfärben ---------- */
var darkEls = $$('[data-nav-dark]');
if (nav && darkEls.length) {
var checkDark = function () {
var probe = (nav.offsetHeight || 70) / 2;
nav.classList.toggle('is-dark', darkEls.some(function (el) {
var r = el.getBoundingClientRect(); return r.top <= probe && r.bottom >= probe;
}));
};
window.addEventListener('scroll', checkDark, { passive: true });
window.addEventListener('resize', checkDark);
checkDark();
}
/* ---------- Mobile Bestell-Leiste ---------- */
var orderbar = $('#orderbar');
var heroEl = $('.hero__cta .btn') || $('.page-hero');
if (orderbar && heroEl && 'IntersectionObserver' in window) {
new IntersectionObserver(function (entries) {
orderbar.classList.toggle('is-visible', !entries[0].isIntersecting);
}, { threshold: 0 }).observe(heroEl);
}
/* ---------- Speisekarte: Kategorien anheften ---------- */
var catNav = $('.cat-nav');
if (catNav) {
var stuck = false;
var checkStuck = function () {
var navH = document.body.classList.contains('nav-visible') ? ((nav && nav.offsetHeight) || 0) : 0;
var top = catNav.getBoundingClientRect().top;
var shouldStick = top < navH;
if (shouldStick === stuck) return;
stuck = shouldStick;
if (stuck) catNav.style.height = catNav.offsetHeight + 'px';
else catNav.style.height = '';
catNav.classList.toggle('is-stuck', stuck);
};
window.addEventListener('scroll', checkStuck, { passive: true });
window.addEventListener('resize', function () {
if (stuck) { stuck = false; catNav.classList.remove('is-stuck'); catNav.style.height = ''; }
checkStuck();
});
checkStuck();
}
/* ---------- Speisekarte: aktive Kategorie ---------- */
if (catNav && 'IntersectionObserver' in window) {
var links = $$('a', catNav);
var list = $('ul', catNav);
var setActive = function (id) {
links.forEach(function (l) {
var on = l.getAttribute('href') === '#' + id;
l.classList.toggle('is-active', on);
if (on) {
l.setAttribute('aria-current', 'true');
list.scrollTo({ left: l.offsetLeft - list.clientWidth / 2 + l.offsetWidth / 2, behavior: reduce ? 'auto' : 'smooth' });
} else l.removeAttribute('aria-current');
});
};
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (en) { if (en.isIntersecting) setActive(en.target.id); });
}, { rootMargin: '-35% 0px -60% 0px' });
var cats = $$('.menu-cat');
cats.forEach(function (s) { io.observe(s); });
window.addEventListener('scroll', function () {
if (cats[0] && cats[0].getBoundingClientRect().top > window.innerHeight * .4) {
links.forEach(function (l) { l.classList.remove('is-active'); l.removeAttribute('aria-current'); });
}
}, { passive: true });
}
/* ---------- Zutaten rund ums Essen (Explosion beim Scrollen) ----------
Jede Zutat: [Datei, x %, y % (relativ zur Essens-Box), Größe % der Box, Drehung, Ebene, auch mobil, gespiegelt]
Ebenen: b = hinter dem Essen (leicht unscharf), f = davor */
var BITS = 'assets/img/bits/';
/* Statt fester Positionen: natürliche Streuung. Nah am Essen dicht, nach außen immer weniger,
unregelmäßige Winkel, kein Kreis. Fester Seed, damit es bei jedem Laden gleich aussieht.
items: [Datei, Grundgröße in % der Essensbreite, auch mobil]
edge = Rand des Essens (0,5 = Bildrand), reach = weitester Abstand, top/bottom = erlaubter Bereich in % der Höhe,
behind = Anteil der nahen Teile hinter dem Essen, hw = Höhe/Breite der Essens-Box */
var SCATTER = {
burger: { seed: 11, edge: .4, reach: 1, top: -34, bottom: 112, behind: .5, hw: .72, items: [
['lettuce-2', 20, 1], ['tomato', 16, 1], ['bacon', 22, 1], ['onion-1', 15, 1], ['jalapeno-1', 11, 1], ['lettuce-1', 22, 1],
['pickle', 11, 1], ['tomato', 13, 0], ['onion-2', 12, 0], ['jalapeno-2', 10, 0], ['bacon', 17, 0], ['lettuce-3', 17, 0],
['sesame-1', 3.6, 1], ['sesame-2', 4, 0], ['sesame-1', 3, 0], ['sesame-1', 2.6, 0]
] },
'burger-visit': { seed: 11, edge: .4, reach: .95, top: -6, bottom: 112, left: -14, behind: .5, hw: .63, items: [
['lettuce-2', 20, 1], ['tomato', 16, 1], ['bacon', 22, 1], ['onion-1', 15, 1], ['jalapeno-1', 11, 1], ['lettuce-1', 22, 1],
['pickle', 11, 0], ['tomato', 13, 0], ['jalapeno-2', 10, 0], ['sesame-1', 3.6, 0], ['sesame-2', 4, 0]
] },
'burger-sm': { seed: 5, edge: .42, reach: .72, top: -14, bottom: 88, behind: .5, hw: .72, items: [
['lettuce-1', 18, 1], ['tomato', 14, 1], ['bacon', 18, 1], ['onion-2', 12, 1], ['pickle', 9, 0], ['sesame-1', 3, 0]
] },
'chicken-sm': { seed: 7, edge: .42, reach: .72, top: -14, bottom: 88, behind: .5, hw: .72, items: [
['bacon', 18, 1], ['lettuce-2', 18, 1], ['tomato', 13, 1], ['crumb-1', 9, 1], ['crumb-2', 7, 0], ['crumb-3', 6, 0]
] },
'wrap-sm': { seed: 3, edge: .42, reach: .72, top: -14, bottom: 88, behind: .5, hw: .67, items: [
['lettuce-3', 18, 1], ['tomato', 13, 1], ['jalapeno-2', 10, 1], ['onion-2', 12, 1], ['jalapeno-1', 8, 0]
] },
wings: { seed: 21, edge: .4, reach: .95, top: 4, bottom: 114, behind: .45, hw: .71, items: [
['crumb-1', 11, 1], ['chili', 18, 1], ['crumb-3', 10, 1], ['jalapeno-1', 11, 1], ['crumb-6', 10, 1], ['crumb-8', 10, 1],
['crumb-4', 9, 0], ['jalapeno-2', 10, 0], ['crumb-7', 8, 0], ['crumb-2', 7, 0], ['crumb-5', 7, 0], ['crumb-2', 5, 0]
] },
tenders: { seed: 9, edge: .4, reach: .92, top: -20, bottom: 116, left: 2, behind: .45, hw: .59, items: [
['crumb-6', 10, 1], ['chili', 17, 1], ['crumb-1', 10, 1], ['jalapeno-1', 10, 1], ['crumb-3', 9, 1],
['crumb-8', 9, 0], ['crumb-4', 8, 0], ['crumb-7', 7, 0], ['crumb-5', 6, 0]
] }
};
function seeded(a) {
return function () {
a = a + 0x6D2B79F5 | 0;
var t = Math.imul(a ^ a >>> 15, 1 | a);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
function scatter(spec) {
var r = seeded(spec.seed), placed = [], out = [];
spec.items.forEach(function (it) {
for (var k = 0; k < 80; k++) {
var a = r() * Math.PI * 2;
var d = spec.edge + Math.pow(r(), 2.4) * (spec.reach - spec.edge) + (r() - .5) * .06;
var x = 50 + Math.cos(a) * d * 100, y = 50 + Math.sin(a) * d * 100;
if (y < spec.top || y > spec.bottom || x < (spec.left != null ? spec.left : -60) || x > (spec.right != null ? spec.right : 160)) continue;
var size = it[1] * 1.35 * (.8 + r() * .4) * (1 - Math.max(0, d - spec.edge) * .45);
var free = placed.every(function (p) {
var dx = p.x - x, dy = (p.y - y) * spec.hw;
return Math.sqrt(dx * dx + dy * dy) > (p.s + size) * .42;
});
if (!free) continue;
var p = { x: x, y: y, s: size };
placed.push(p);
var near = d < spec.edge + .08;
out.push([it[0], +x.toFixed(1), +y.toFixed(1), +size.toFixed(1), Math.round((r() - .5) * 140), near && r() < spec.behind ? 'b' : 'f', it[2], r() < .5 ? 1 : 0]);
return;
}
});
return out;
}
var mobileBits = window.matchMedia('(max-width: 820px)').matches;
var bitScale = mobileBits ? 1.35 : 1;
var blasts = $$('[data-blast]').map(function (box) {
var spec = SCATTER[box.getAttribute('data-blast')];
if (!spec) return null;
var set = scatter(spec);
var lazy = box.getAttribute('data-blast-mode') !== 'hero';
var holder = document.createElement('span');
holder.className = 'blast__bits';
holder.setAttribute('aria-hidden', 'true');
var bits = [];
set.forEach(function (b) {
if (mobileBits && !b[6]) return;
// Verschachtelt, damit sich nichts in die Quere kommt:
// .bit (Scroll-Explosion) > .bit__c (CSS-Zentrierung) > .bit__in (Intro) > .bit__fl (Schweben) > img (feste Drehung)
var size = b[3] * bitScale;
var el = document.createElement('span');
el.className = 'bit bit--' + b[5];
el.style.left = b[1] + '%'; el.style.top = b[2] + '%'; el.style.width = size + '%';
var c = document.createElement('span'); c.className = 'bit__c';
var inner = document.createElement('span'); inner.className = 'bit__in';
var fl = document.createElement('span'); fl.className = 'bit__fl';
var img = document.createElement('img');
img.alt = ''; img.decoding = 'async'; img.draggable = false;
if (lazy) img.loading = 'lazy';
img.style.rotate = b[4] + 'deg';
if (b[7]) img.style.scale = '-1 1';
img.onerror = function () { el.remove(); };
// Größe als Fläche: lange, schmale Zutaten (Salatblatt, Bacon) wirken so gleich groß wie runde
img.onload = function () {
var ratio = img.naturalWidth / img.naturalHeight;
el.style.width = (size * Math.sqrt(ratio)).toFixed(2) + '%';
};
img.src = BITS + b[0] + '.webp';
fl.appendChild(img); inner.appendChild(fl); c.appendChild(inner); el.appendChild(c); holder.appendChild(el);
bits.push({ el: el, inner: inner, fl: fl, x: b[1], y: b[2], layer: b[5] });
});
box.appendChild(holder);
return { box: box, bits: bits, hero: !lazy };
}).filter(Boolean);
/* ---------- Flammen nur animieren, solange sie sichtbar sind ---------- */
if ('IntersectionObserver' in window) {
var flameIO = new IntersectionObserver(function (entries) {
entries.forEach(function (en) { en.target.classList.toggle('is-live', en.isIntersecting); });
});
$$('.flames, .cta__fire, .fx__sparks').forEach(function (el) { flameIO.observe(el); });
} else {
$$('.flames, .cta__fire, .fx__sparks').forEach(function (el) { el.classList.add('is-live'); });
}
/* ---------- Funken (Buckets) ---------- */
if (!reduce) {
$$('[data-sparks]').forEach(function (wrap) {
for (var i = 0; i < 22; i++) {
var sp = document.createElement('i');
sp.style.left = (Math.random() * 100).toFixed(1) + '%';
sp.style.setProperty('--sz', (2 + Math.random() * 3.5).toFixed(1) + 'px');
sp.style.setProperty('--d', (4 + Math.random() * 5).toFixed(2) + 's');
sp.style.setProperty('--dl', (-Math.random() * 9).toFixed(2) + 's');
sp.style.setProperty('--dx', ((Math.random() - .5) * 140).toFixed(0) + 'px');
sp.style.setProperty('--h', (300 + Math.random() * 600).toFixed(0) + 'px');
wrap.appendChild(sp);
}
});
}
/* ---------- 3D-Neigung der Karten (nur Maus) ---------- */
if (!reduce && window.matchMedia('(hover: hover) and (pointer: fine)').matches) {
$$('[data-tilt]').forEach(function (card) {
var r = null;
card.addEventListener('pointerenter', function () { r = card.getBoundingClientRect(); });
card.addEventListener('pointermove', function (e) {
if (!r) r = card.getBoundingClientRect();
var nx = (e.clientX - r.left) / r.width - .5, ny = (e.clientY - r.top) / r.height - .5;
card.classList.add('is-tilting');
card.style.setProperty('--ty', (nx * 14).toFixed(2) + 'deg');
card.style.setProperty('--tx', (-ny * 12).toFixed(2) + 'deg');
});
card.addEventListener('pointerleave', function () {
r = null;
card.classList.remove('is-tilting');
card.style.setProperty('--tx', '0deg'); card.style.setProperty('--ty', '0deg');
});
});
}
/* ---------- Route (Take away) ---------- */
var route = $('[data-route]');
var routeState = null;
function buildRoute() {
if (!route) return null;
var svg = $('.route__svg', route), path = $('path', svg), stops = $$('.stop', route);
if (!stops.length || window.getComputedStyle(svg).display === 'none') return null;
var box = route.getBoundingClientRect();
var pts = stops.map(function (s) {
var r = s.getBoundingClientRect();
return { x: r.left - box.left + r.width * .5, y: r.top - box.top - 34 };
});
var start = { x: Math.max(40, pts[0].x - box.width * .2), y: pts[0].y + 6 };
var d = 'M' + start.x + ' ' + start.y;
var prev = start;
pts.forEach(function (p, i) {
var dx = p.x - prev.x;
var lift = i % 2 ? 90 : -70;
d += ' C' + (prev.x + dx * .45) + ' ' + (prev.y + lift) + ' ' + (p.x - dx * .45) + ' ' + (p.y - lift) + ' ' + p.x + ' ' + p.y;
prev = p;
});
path.setAttribute('d', d);
return { path: path, len: path.getTotalLength(), rider: $('.route__rider', route) };
}
function placeRider(progress) {
if (!routeState) return;
var pt = routeState.path.getPointAtLength(routeState.len * progress);
var ahead = routeState.path.getPointAtLength(Math.min(routeState.len, routeState.len * progress + 4));
var angle = Math.atan2(ahead.y - pt.y, ahead.x - pt.x) * 180 / Math.PI;
var w = routeState.rider.offsetWidth, h = routeState.rider.offsetHeight;
routeState.rider.style.transform = 'translate(' + (pt.x - w / 2) + 'px,' + (pt.y - h / 2) + 'px) rotate(' + (angle * .25) + 'deg)';
}
/* ---------- Ohne GSAP: fertig ---------- */
if (!hasGsap) {
routeState = buildRoute();
placeRider(1);
return;
}
var gsap = window.gsap, ST = window.ScrollTrigger;
gsap.registerPlugin(ST);
var rand = gsap.utils.random;
function splitWords(el) {
(function walk(node) {
Array.prototype.slice.call(node.childNodes).forEach(function (ch) {
if (ch.nodeType === 3) {
var frag = document.createDocumentFragment();
ch.textContent.split(/(\s+)/).forEach(function (p) {
if (!p) return;
if (/^\s+$/.test(p)) frag.appendChild(document.createTextNode(p));
else { var s = document.createElement('span'); s.className = 'pw'; s.textContent = p; frag.appendChild(s); }
});
ch.parentNode.replaceChild(frag, ch);
} else if (ch.nodeType === 1 && ch.tagName !== 'BR') walk(ch);
});
})(el);
return $$('.pw', el);
}
function splitChars(el) {
var txt = el.textContent; el.textContent = '';
var sr = document.createElement('span'); sr.className = 'sr-only'; sr.textContent = txt; el.appendChild(sr);
return txt.split('').map(function (c) {
var s = document.createElement('span'); s.className = 'pl'; s.textContent = c === ' ' ? '\u00a0' : c;
s.setAttribute('aria-hidden', 'true'); s.style.display = 'inline-block';
el.appendChild(s); return s;
});
}
if (reduce) {
routeState = buildRoute();
placeRider(1);
return;
}
/* ---- Hero-Intro ---- */
var hero = $('.hero');
if (hero) {
var chars = [];
$$('[data-hero-big] .part', hero).forEach(function (p) { chars = chars.concat(splitChars(p)); });
var burgerImg = $('.hero__burger .blast__food', hero);
var tl = gsap.timeline({ delay: .05 });
tl.set($$('[data-intro]', hero), { opacity: 1 })
.fromTo('.hero__eyebrow', { y: 24, opacity: 0 }, { y: 0, opacity: 1, duration: .5, ease: 'power3.out' }, 0)
.fromTo(chars, { scale: 0, opacity: 0, y: function () { return rand(40, 100); }, rotation: function () { return rand(-18, 18); }, transformOrigin: '50% 90%' },
{ scale: 1, opacity: 1, y: 0, rotation: 0, duration: .75, stagger: .045, ease: 'back.out(2.2)' }, .05)
.fromTo(burgerImg, { scale: .45, y: 140, rotation: -12, opacity: 0 }, { scale: 1, y: 0, rotation: 0, opacity: 1, duration: 1.1, ease: 'back.out(1.5)' }, .3)
.fromTo($$('[data-sticker]', hero), { '--s': 0 }, { '--s': 1, duration: .6, stagger: .12, ease: 'back.out(3)' }, .9)
.fromTo(['.hero__bottom .lead', '.hero__cta'], { y: 30, opacity: 0 }, { y: 0, opacity: 1, duration: .7, stagger: .1, ease: 'power3.out' }, .8)
.add(function () {
gsap.to(burgerImg, { y: -14, duration: 2.4, yoyo: true, repeat: -1, ease: 'sine.inOut' });
});
if (window.matchMedia('(pointer: fine)').matches) {
var qx = gsap.quickTo(burgerImg, 'x', { duration: .8, ease: 'power3.out' });
var qr = gsap.quickTo(burgerImg, 'rotation', { duration: .8, ease: 'power3.out' });
hero.addEventListener('mousemove', function (e) {
var r = hero.getBoundingClientRect();
var nx = (e.clientX - r.left) / r.width - .5;
qx(nx * 40); qr(nx * 6);
});
}
var mm = gsap.matchMedia();
mm.add('(min-width: 821px)', function () {
var parts = $$('[data-hero-big] .part', hero);
gsap.to(parts[0], { xPercent: -10, ease: 'none', scrollTrigger: { trigger: hero, start: 'top top', end: 'bottom top', scrub: true } });
gsap.to(parts[1], { xPercent: 12, ease: 'none', scrollTrigger: { trigger: hero, start: 'top top', end: 'bottom top', scrub: true } });
gsap.to('.hero__burger', { yPercent: -12, ease: 'none', scrollTrigger: { trigger: hero, start: 'top top', end: 'bottom top', scrub: true } });
});
}
/* ---- Seiten-Hero (Unterseiten) ---- */
var pageHero = $('.page-hero');
if (pageHero) {
var ph = $('[data-hero-big]', pageHero);
if (ph) {
var pchars = splitChars(ph);
gsap.fromTo(pchars, { scale: 0, opacity: 0, y: function () { return rand(40, 90); }, rotation: function () { return rand(-16, 16); }, transformOrigin: '50% 90%' },
{ scale: 1, opacity: 1, y: 0, rotation: 0, duration: .75, stagger: .05, ease: 'back.out(2.2)', delay: .05 });
}
var phImg = $('.page-hero__img img', pageHero);
if (phImg) {
gsap.fromTo(phImg, { scale: .5, y: 120, rotation: 10, opacity: 0 }, { scale: 1, y: 0, rotation: 0, opacity: 1, duration: 1.1, ease: 'back.out(1.5)', delay: .25 });
gsap.to(phImg, { y: -12, duration: 2.4, yoyo: true, repeat: -1, ease: 'sine.inOut', delay: 1.4 });
}
}
/* ---- Wort-Pop für Headlines ---- */
$$('[data-pop]').forEach(function (el) {
var words = splitWords(el);
gsap.fromTo(words,
{ opacity: 0, scale: 0, y: function () { return rand(18, 40); }, rotation: function () { return rand(-14, 14); }, transformOrigin: '50% 90%' },
{ opacity: 1, scale: 1, y: 0, rotation: 0, duration: .72, stagger: .06, ease: 'back.out(2.35)',
scrollTrigger: { trigger: el, start: 'top 88%', once: true } });
});
/* ---- Reveal (nutzt translate/opacity, damit CSS-Rotationen bleiben) ---- */
var reveals = $$('[data-reveal]').filter(function (el) { return !el.closest('.hero'); });
ST.batch(reveals, {
start: 'top 90%', once: true,
onEnter: function (batch) {
gsap.fromTo(batch, { opacity: 0, '--ry': '40px' }, { opacity: 1, '--ry': '0px', duration: .9, stagger: .1, ease: 'power3.out' });
}
});
/* ---- Sticker "aufkleben" ---- */
$$('[data-sticker]').filter(function (el) { return !el.closest('.hero'); }).forEach(function (el) {
gsap.fromTo(el, { '--s': 0 }, { '--s': 1, duration: .7, ease: 'back.out(3)', scrollTrigger: { trigger: el, start: 'top 88%', once: true } });
});
/* ---- Zutaten: schweben, beim Laden aus dem Essen platzen, beim Scrollen wegfliegen ---- */
blasts.forEach(function (bl) {
var W = function () { return bl.box.offsetWidth; };
var vec = function (b) {
return { x: (b.x - 50) / 100 * W(), y: (b.y - 50) / 100 * bl.box.offsetHeight };
};
// Explosionsziel: kräftig nach außen, mit Drall zur Seite und nach oben
var away = function (b, k) {
var v = vec(b), len = Math.sqrt(v.x * v.x + v.y * v.y) || 1;
var side = b.side;
return {
x: v.x * .5 + (v.x / len) * W() * k + (-v.y / len) * W() * .18 * side,
y: v.y * .3 + (v.y / len) * W() * k * .55 - W() * .45 * k
};
};
var floats = [];
var tl = gsap.timeline({ paused: true });
bl.bits.forEach(function (b, i) {
b.side = i % 2 ? 1 : -1;
floats.push(gsap.to(b.fl, { y: '+=' + rand(-16, -8), rotation: '+=' + rand(-8, 8), duration: rand(1.8, 3.2), yoyo: true, repeat: -1, ease: 'sine.inOut', delay: rand(0, 1.5), paused: true }));
var spin = rand(160, 340) * b.side;
var grow = b.layer === 'b' ? 1.15 : 1.9;
if (bl.hero) {
gsap.from(b.inner, { x: function () { return -vec(b).x; }, y: function () { return -vec(b).y; }, scale: .2, opacity: 0, rotation: -spin / 3, duration: 1.2, ease: 'back.out(1.3)', delay: .55 + i * .035 });
tl.to(b.el, { x: function () { return away(b, 1).x; }, y: function () { return away(b, 1).y; }, rotation: spin, scale: grow, duration: 1, ease: 'power2.in' }, 0)
.to(b.el, { opacity: 0, duration: .25, ease: 'none' }, .75);
} else {
tl.fromTo(b.el, { x: function () { return -vec(b).x * .6; }, y: function () { return -vec(b).y * .6; }, scale: .4, opacity: 0, rotation: -spin / 3 },
{ x: 0, y: 0, scale: 1, opacity: 1, rotation: 0, duration: .4, ease: 'power2.out' }, 0)
.to(b.el, { x: function () { return away(b, .8).x; }, y: function () { return away(b, .8).y; }, scale: grow, rotation: spin, duration: .45, ease: 'power2.in' }, .55)
.to(b.el, { opacity: 0, duration: .12, ease: 'none' }, .88);
}
});
var sec = bl.box.closest('section') || bl.box;
ST.create({
animation: tl, invalidateOnRefresh: true, scrub: .6,
trigger: bl.hero ? sec : bl.box,
start: bl.hero ? 'top top' : 'top 92%',
end: bl.hero ? '65% top' : 'bottom 12%'
});
// Schweben nur, solange die Zutaten im Bild sind (spart Akku und Rechenzeit)
ST.create({
trigger: bl.box, start: 'top bottom', end: 'bottom top',
onToggle: function (self) { floats.forEach(function (t) { self.isActive ? t.play() : t.pause(); }); }
});
});
/* ---- Feuerwirbel dreht sich langsam mit ---- */
$$('[data-swirl]').forEach(function (sw) {
var sec = sw.closest('section') || sw.parentNode;
gsap.fromTo(sw, { rotation: -12 }, { rotation: 28, ease: 'none', scrollTrigger: { trigger: sec, start: 'top bottom', end: 'bottom top', scrub: true } });
});
/* ---- Parallax Fotoband ---- */
$$('[data-parallax]').forEach(function (img) {
gsap.fromTo(img, { yPercent: -7 }, { yPercent: 7, ease: 'none', scrollTrigger: { trigger: img.parentNode, start: 'top bottom', end: 'bottom top', scrub: true } });
});
/* ---- Route ---- */
if (route) {
routeState = buildRoute();
ST.create({
trigger: route, start: 'top 75%', end: 'bottom 55%', scrub: .6,
onUpdate: function (self) { placeRider(self.progress); },
onRefresh: function (self) { routeState = buildRoute(); placeRider(self.progress); }
});
placeRider(0);
}
/* ---- Footer: Zutaten fliegen von unten ins Bild und fallen wieder raus ---- */
var jug = $('[data-juggle]');
if (jug) {
var foot = jug.closest('footer') || jug;
var G = 1100; // Schwerkraft in px/s², hält Flugkurve und -zeit realistisch
var running = false;
// Versatz, ab dem eine Zutat (auch gedreht) komplett unter dem Footer-Rand steckt
var below = function (el) {
return foot.getBoundingClientRect().bottom - jug.getBoundingClientRect().top - el.offsetTop + el.offsetHeight * .3 + 2;
};
var toss = function (item, wait) {
var el = item.el;
var low = below(el);
var apex = -Math.min(window.innerHeight * .55, Math.max(jug.offsetHeight, 110) * rand(.8, 1.25) + 20);
var dir = Math.random() < .5 ? -1 : 1;
var drift = window.innerWidth * rand(.03, .08) * dir;
var rot = rand(-30, 30);
var up = Math.sqrt(2 * (low - apex) / G);
item.tl = gsap.timeline({ delay: wait, paused: !running, onComplete: function () { toss(item, rand(.15, .8)); } })
.fromTo(el, { y: low }, { y: apex, duration: up, ease: 'power2.out' })
.to(el, { y: low, duration: up, ease: 'power2.in' })
.fromTo(el, { x: -drift / 2, rotation: rot }, { x: drift / 2, rotation: rot + dir * rand(200, 480), duration: up * 2, ease: 'none' }, 0);
};
var items = $$('.juggle', jug).map(function (el, i) {
var item = { el: el, tl: null };
gsap.set(el, { y: below(el), visibility: 'visible' });
toss(item, .1 + i * .3);
return item;
});
var setRunning = function (on) {
running = on;
items.forEach(function (it) { on ? it.tl.play() : it.tl.pause(); });
};
ST.create({
trigger: jug, start: 'top bottom', end: 'bottom top',
onToggle: function (self) { setRunning(self.isActive); }
});
document.addEventListener('visibilitychange', function () {
setRunning(!document.hidden && ST.isInViewport(jug));
});
}
window.addEventListener('load', function () { ST.refresh(); });
})();