197 lines
6 KiB
JavaScript
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();
|
|
}
|
|
})();
|