- Buchungslinks direkt auf Portal-IDs; Ernährungsberatung auf id=107, Kaufberatung entfernt (im Portal gibt es keine Seite dafür) - .htaccess: alte WordPress-Adressen per 301 auf die neuen Seiten - Tablet bis 980 px: Logo-Siegel über den Kacheln statt darauf - Handy: lange Wörter werden nicht mehr abgeschnitten, Pfeile überdecken keinen Text mehr, „Buchen“ neben dem Menü-Knopf - Dreier-Karten: Überschriften auf einer Linie (Subgrid) - Menü schließt mit Escape und Tipp daneben; sichtbarer Fokusrahmen - Startseite Handy/Tablet: Kopfleiste, sobald das Siegel weg ist - 404: Pfade ab der Wurzel statt <base>, Sprunglink bleibt auf der Seite Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
3.7 KiB
JavaScript
83 lines
3.7 KiB
JavaScript
(() => {
|
||
const body = document.body;
|
||
|
||
// Mobiles Menü – schließt auch mit Escape oder Tipp daneben
|
||
const burger = document.querySelector('.burger');
|
||
if (burger) {
|
||
const setMenu = open => {
|
||
body.classList.toggle('menu-open', open);
|
||
burger.setAttribute('aria-expanded', open);
|
||
burger.setAttribute('aria-label', open ? 'Menü schließen' : 'Menü öffnen');
|
||
};
|
||
burger.addEventListener('click', () => setMenu(!body.classList.contains('menu-open')));
|
||
document.querySelectorAll('.nav a').forEach(a => a.addEventListener('click', () => setMenu(false)));
|
||
document.addEventListener('keydown', e => {
|
||
if (e.key === 'Escape' && body.classList.contains('menu-open')) { setMenu(false); burger.focus(); }
|
||
});
|
||
document.addEventListener('click', e => {
|
||
if (body.classList.contains('menu-open') && !e.target.closest('.nav, .burger')) setMenu(false);
|
||
});
|
||
}
|
||
|
||
// Startseite: Header einblenden, sobald der Hero verlassen wird. Auf Handy/Tablet steht das
|
||
// Siegel über den Kacheln – dort kommt der Header schon, sobald das Siegel aus dem Bild ist.
|
||
const hero = document.querySelector('.hero');
|
||
const seal = document.querySelector('.seal');
|
||
if (body.classList.contains('is-home') && hero) {
|
||
const past = { hero: false, seal: false };
|
||
const update = () => body.classList.toggle('scrolled', past.hero || past.seal);
|
||
new IntersectionObserver(([e]) => { past.hero = !e.isIntersecting; update(); }, {
|
||
rootMargin: '-120px 0px 0px 0px'
|
||
}).observe(hero);
|
||
if (seal) new IntersectionObserver(([e]) => {
|
||
past.seal = !e.isIntersecting && e.boundingClientRect.top < 0 && getComputedStyle(seal).position === 'static';
|
||
update();
|
||
}).observe(seal);
|
||
}
|
||
|
||
// Ring um das Logo-Siegel: Kreis + die vier Kachel-Lücken, Übergänge mit Radius f abgerundet
|
||
const joint = document.querySelector('.seal-joint');
|
||
if (seal && joint) {
|
||
const drawJoint = () => {
|
||
const gap = parseFloat(getComputedStyle(seal.parentElement).columnGap) || 14;
|
||
const h = gap / 2; // halbe Lückenbreite
|
||
const R = seal.offsetWidth / 2 + gap; // Außenradius des Rings
|
||
const f = 18; // Radius der Rundung
|
||
const yd = Math.sqrt((R + f) ** 2 - (h + f) ** 2);
|
||
const k = R / (R + f);
|
||
const L = Math.ceil(R + f + 2); // Arme reichen bis zum Rand der Fläche
|
||
const rot = ([x, y], n) => { for (let i = 0; i < n; i++) [x, y] = [-y, x]; return [x + L, y + L]; };
|
||
const pt = (p, n) => rot(p, n).map(v => v.toFixed(2)).join(' ');
|
||
let d = `M${pt([-(h + f) * k, -yd * k], 0)}`;
|
||
for (let n = 0; n < 4; n++) {
|
||
d += ` A${f} ${f} 0 0 0 ${pt([-h, -yd], n)}`
|
||
+ ` L${pt([-h, -L], n)} L${pt([h, -L], n)} L${pt([h, -yd], n)}`
|
||
+ ` A${f} ${f} 0 0 0 ${pt([(h + f) * k, -yd * k], n)}`
|
||
+ ` A${R} ${R} 0 0 1 ${pt([-(h + f) * k, -yd * k], n + 1)}`;
|
||
}
|
||
joint.setAttribute('width', 2 * L);
|
||
joint.setAttribute('height', 2 * L);
|
||
joint.setAttribute('viewBox', `0 0 ${2 * L} ${2 * L}`);
|
||
joint.firstElementChild.setAttribute('d', d + ' Z');
|
||
};
|
||
drawJoint();
|
||
addEventListener('resize', drawJoint);
|
||
}
|
||
|
||
// Elemente beim Scrollen einblenden
|
||
const items = document.querySelectorAll('.reveal');
|
||
if ('IntersectionObserver' in window) {
|
||
const io = new IntersectionObserver(entries => {
|
||
entries.forEach(e => {
|
||
if (e.isIntersecting) {
|
||
e.target.classList.add('in');
|
||
io.unobserve(e.target);
|
||
}
|
||
});
|
||
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
|
||
items.forEach(el => io.observe(el));
|
||
} else {
|
||
items.forEach(el => el.classList.add('in'));
|
||
}
|
||
})();
|