markemacht/_markemacht.de/assets/js/app.js
Kevin Adametz 18216e301c
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
05-06-2026 marke macht Webseite Design Inhalte
2026-06-05 17:43:31 +02:00

197 lines
6 KiB
JavaScript

/* markemacht.de — Interaktionen
* - Komponenten: Header/Footer
* - Wirkung-Switch (monolithisch | editorial) + Persistenz
* - Mobile Drawer
* - Scroll-Reveal (IntersectionObserver, max 400ms, ease-out)
* - Keyboard: ESC schließt Drawer
*/
(function () {
'use strict';
/* ---------- Komponenten ------------------------------------------------ */
const COMPONENTS = [
['[data-site-header]', './assets/components/header.html'],
['[data-site-footer]', './assets/components/footer.html'],
];
async function loadComponent(selector, url) {
const mount = document.querySelector(selector);
if (!mount) return;
try {
const res = await fetch(url, { cache: 'no-cache' });
if (!res.ok) throw new Error(`Component not found: ${url}`);
mount.innerHTML = await res.text();
} catch (err) {
console.error(err);
mount.setAttribute('data-component-error', 'true');
}
}
function initComponents() {
return Promise.all(COMPONENTS.map(([selector, url]) => loadComponent(selector, url)));
}
function getCurrentPage() {
const file = window.location.pathname.split('/').pop() || 'index.html';
if (file === 'index.html') return 'start';
if (file === 'manifest.html') return 'manifest';
if (file === 'methode.html') return 'methode';
if (file === 'denken.html' || file.startsWith('denken-')) return 'denken';
return '';
}
function initActiveNavigation() {
const current = getCurrentPage();
document.querySelectorAll('[data-nav-page]').forEach((link) => {
const isActive = link.getAttribute('data-nav-page') === current;
link.classList.toggle('is-active', isActive);
if (isActive) {
link.setAttribute('aria-current', 'page');
} else {
link.removeAttribute('aria-current');
}
});
}
/* ---------- Wirkung ---------------------------------------------------- */
const STORAGE_KEY = 'mm-wirkung';
const VALID = ['monolith', 'editorial'];
const DEFAULT = 'monolith';
function getCookie() {
const entry = document.cookie
.split('; ')
.find((row) => row.startsWith(`${STORAGE_KEY}=`));
if (!entry) return null;
const v = decodeURIComponent(entry.split('=').slice(1).join('='));
return VALID.includes(v) ? v : null;
}
function setCookie(mode) {
document.cookie = `${STORAGE_KEY}=${encodeURIComponent(mode)}; path=/; max-age=31536000; SameSite=Lax`;
}
function getStored() {
try {
const v = localStorage.getItem(STORAGE_KEY);
return VALID.includes(v) ? v : getCookie();
} catch (_) { return getCookie(); }
}
function applyMode(mode) {
if (!VALID.includes(mode)) mode = DEFAULT;
document.documentElement.setAttribute('data-theme', mode);
try { localStorage.setItem(STORAGE_KEY, mode); } catch (_) {}
setCookie(mode);
syncButtons(mode);
}
function syncButtons(mode) {
document.querySelectorAll('[data-wirkung]').forEach((btn) => {
const pressed = btn.getAttribute('data-wirkung') === mode;
btn.setAttribute('aria-pressed', pressed ? 'true' : 'false');
});
}
function initWirkung() {
const initial = getStored() || DEFAULT;
applyMode(initial);
document.querySelectorAll('[data-wirkung]').forEach((btn) => {
btn.addEventListener('click', () => {
const mode = btn.getAttribute('data-wirkung');
applyMode(mode);
});
});
}
/* ---------- Mobile Drawer --------------------------------------------- */
function initDrawer() {
const drawer = document.querySelector('[data-mobile-drawer]');
if (!drawer) return;
const openBtns = document.querySelectorAll('[data-drawer-open]');
const closeBtns = document.querySelectorAll('[data-drawer-close]');
function open() {
drawer.classList.add('is-open');
document.body.classList.add('drawer-open');
drawer.setAttribute('aria-hidden', 'false');
openBtns.forEach((btn) => btn.setAttribute('aria-expanded', 'true'));
const firstLink = drawer.querySelector('a, button');
if (firstLink) firstLink.focus();
}
function close() {
drawer.classList.remove('is-open');
document.body.classList.remove('drawer-open');
drawer.setAttribute('aria-hidden', 'true');
openBtns.forEach((btn) => btn.setAttribute('aria-expanded', 'false'));
}
openBtns.forEach((b) => b.addEventListener('click', open));
closeBtns.forEach((b) => b.addEventListener('click', close));
drawer.querySelectorAll('a[href]').forEach((a) => {
a.addEventListener('click', close);
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && drawer.classList.contains('is-open')) {
close();
}
});
}
/* ---------- Scroll Reveal --------------------------------------------- */
function initReveal() {
const items = document.querySelectorAll('[data-reveal]');
if (!items.length) return;
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReduced || !('IntersectionObserver' in window)) {
items.forEach((el) => el.classList.add('is-revealed'));
return;
}
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const el = entry.target;
const delay = parseInt(el.getAttribute('data-reveal-delay') || '0', 10);
if (delay > 0) {
setTimeout(() => el.classList.add('is-revealed'), delay);
} else {
el.classList.add('is-revealed');
}
io.unobserve(el);
}
});
}, {
threshold: 0.08,
rootMargin: '0px 0px -5% 0px'
});
items.forEach((el) => io.observe(el));
}
/* ---------- Boot ------------------------------------------------------- */
async function boot() {
await initComponents();
initActiveNavigation();
initWirkung();
initDrawer();
initReveal();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();