Files
Claude 8da5102333 HSK-Vorlage aus der Olzinger-Website ableiten
Firmendaten durch Platzhalter ersetzt (Mustermann Haustechnik GmbH,
Musterstraße 1, 12345 Musterstadt, 01234 567890, example.com), Design
und Funktionen unverändert.

- Feiertage im Öffnungsstatus per BUNDESLAND (alle 16 Länder) statt fest Bayern
- Datenschutz: Aufsichtsbehörde als TODO je Bundesland
- Skript-Namensraum olz -> hsk, Logo-SVG und OG-Bild neu
- .webklar-preview.json von Olzinger entfernt (sonst Deploy auf olzinger)
- .gitea/template: neue Repos aus der Vorlage erben die Preview-Datei nicht
- README: Anleitung für neue HSK-Seiten, Platzhalter-Tabelle, Checkliste

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:10:17 +00:00

203 lines
8.4 KiB
JavaScript
Raw Permalink 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.
/* ==========================================================================
HSK-Vorlage – Hauptskript
Navigation, mobiles Menü, dezente Einblendungen, Leistungs-Karussell (mobil)
und Kartenstapel (Desktop). Bewusst ohne Bibliotheken.
========================================================================== */
(function (w, d) {
'use strict';
var html = d.documentElement;
var $ = function (s, c) { return (c || d).querySelector(s); };
var $$ = function (s, c) { return Array.prototype.slice.call((c || d).querySelectorAll(s)); };
var reduce = w.matchMedia('(prefers-reduced-motion: reduce)').matches;
var hasIO = 'IntersectionObserver' in w;
w.__hskReady = true;
/* ---------- Dynamische Jahreszahlen ---------- */
var nowYear = new Date().getFullYear();
$$('[data-current-year]').forEach(function (el) { el.textContent = nowYear; });
$$('[data-years]').forEach(function (el) { el.textContent = nowYear - 1990; });
/* ---------- Wärmebild (ruhiger Hintergrund im Hero) ---------- */
var canvas = $('[data-thermal]');
if (canvas && w.HskThermal) {
var thermal = w.HskThermal.init(canvas, {
hero: $('[data-hero]'),
calm: $('.hero__aside'),
reduceMotion: reduce
});
if (thermal) thermal.heatUp();
} else if (canvas) {
html.classList.add('no-webgl');
}
/* ---------- Mobiles Menü ---------- */
var menu = $('[data-menu]');
var toggle = $('[data-menu-toggle]');
var menuOpen = false;
if (menu) { menu.hidden = false; menu.setAttribute('inert', ''); menu.setAttribute('aria-hidden', 'true'); }
function setMenu(open) {
if (!menu || !toggle || open === menuOpen) return;
menuOpen = open;
menu.classList.toggle('is-open', open);
if (open) menu.removeAttribute('inert'); else menu.setAttribute('inert', '');
menu.setAttribute('aria-hidden', open ? 'false' : 'true');
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
toggle.querySelector('.sr-only').textContent = open ? 'Menü schließen' : 'Menü öffnen';
html.classList.toggle('menu-open', open);
// Inhalt hinter dem Menü für Tastatur und Screenreader sperren
[d.getElementById('main'), $('.footer')].forEach(function (el) { if (el) el.inert = open; });
}
if (toggle) toggle.addEventListener('click', function () { setMenu(!menuOpen); });
d.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && menuOpen) { setMenu(false); toggle.focus(); }
});
// Wird das Fenster breit genug für die Desktop-Navigation (z. B. Tablet drehen), Menü schließen
var deskMQ = w.matchMedia('(min-width: 1181px)');
var onDesk = function (e) { if (e.matches) setMenu(false); };
if (deskMQ.addEventListener) deskMQ.addEventListener('change', onDesk); else if (deskMQ.addListener) deskMQ.addListener(onDesk);
/* ---------- Anker-Links: Menü schließen, Thema ans Formular geben ---------- */
d.addEventListener('click', function (e) {
var a = e.target.closest('a[href*="#"]');
if (!a) return;
if (a.dataset.topic) w.dispatchEvent(new CustomEvent('hsk:topic', { detail: a.dataset.topic }));
if (menuOpen) setMenu(false);
});
/* ---------- Navigation: Hintergrund ab Scroll, beim Runterscrollen ausblenden ---------- */
var nav = $('[data-nav]');
if (nav) {
var lastY = w.scrollY, ticking = false;
var onScroll = function () {
ticking = false;
var y = w.scrollY;
nav.classList.toggle('is-scrolled', y > 40);
if (!menuOpen) {
if (y > 420 && y > lastY + 4) nav.classList.add('is-hidden');
else if (y < lastY - 4 || y <= 420) nav.classList.remove('is-hidden');
}
lastY = y;
};
w.addEventListener('scroll', function () {
if (!ticking) { ticking = true; w.requestAnimationFrame(onScroll); }
}, { passive: true });
nav.addEventListener('focusin', function () { nav.classList.remove('is-hidden'); });
onScroll();
}
// Aktiven Menüpunkt markieren
var navLinks = $$('[data-nav-link]');
if (hasIO && navLinks.length) {
var linkFor = {};
navLinks.forEach(function (l) { linkFor[l.getAttribute('href').slice(1)] = l; });
var activeIO = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
var link = linkFor[en.target.id];
if (link) link.classList.toggle('is-active', en.isIntersecting);
});
}, { rootMargin: '-45% 0px -54% 0px' });
Object.keys(linkFor).forEach(function (id) {
var sec = d.getElementById(id);
if (sec) activeIO.observe(sec);
});
}
/* ---------- Dezente Einblendungen ---------- */
var reveals = $$('[data-reveal]');
if (hasIO && !reduce) {
var revealIO = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (!en.isIntersecting) return;
en.target.classList.add('is-in');
revealIO.unobserve(en.target);
});
}, { rootMargin: '0px 0px -8% 0px' });
reveals.forEach(function (el) { revealIO.observe(el); });
} else {
reveals.forEach(function (el) { el.classList.add('is-in'); });
}
/* ---------- Leistungen: Karussell (mobil) bzw. Kartenstapel (Desktop) ---------- */
var stack = $('[data-stack]');
var cards = $$('.svc');
var stackNav = $('[data-stack-nav]');
var stackMQ = w.matchMedia('(min-width: 900px) and (min-height: 660px)');
if (stack && stackNav) {
var prevBtn = $('[data-stack-prev]', stackNav);
var nextBtn = $('[data-stack-next]', stackNav);
var currentEl = $('[data-stack-current]', stackNav);
var current = 0;
var isCarousel = function () { return stack.scrollWidth > stack.clientWidth + 2; };
var cardLeft = function (c) { return c.offsetLeft - cards[0].offsetLeft; };
var cardIndex = function () {
var x = stack.scrollLeft, best = 0, bestDist = Infinity;
if (x >= stack.scrollWidth - stack.clientWidth - 2) return cards.length - 1;
cards.forEach(function (c, i) {
var dist = Math.abs(cardLeft(c) - x);
if (dist < bestDist) { bestDist = dist; best = i; }
});
return best;
};
var updateNav = function () {
stackNav.hidden = !isCarousel();
current = cardIndex();
currentEl.textContent = current + 1;
prevBtn.disabled = current === 0;
nextBtn.disabled = current === cards.length - 1;
};
// Schritt relativ zur echten Scrollposition (bei zwei Karten nebeneinander
// liegen die letzten Karten jenseits der maximalen Scrollweite)
var step = function (dir) {
var x = stack.scrollLeft, i;
if (dir > 0) { for (i = 0; i < cards.length - 1; i++) if (cardLeft(cards[i]) > x + 2) break; }
else { for (i = cards.length - 1; i > 0; i--) if (cardLeft(cards[i]) < x - 2) break; }
stack.scrollTo({ left: Math.min(cardLeft(cards[i]), stack.scrollWidth - stack.clientWidth), behavior: reduce ? 'auto' : 'smooth' });
};
var navTick = false;
stack.addEventListener('scroll', function () {
if (!navTick) { navTick = true; w.requestAnimationFrame(function () { navTick = false; updateNav(); }); }
}, { passive: true });
prevBtn.addEventListener('click', function () { step(-1); });
nextBtn.addEventListener('click', function () { step(1); });
w.addEventListener('resize', updateNav);
updateNav();
}
// Desktop: hintere Karten treten beim Überdecken leicht zurück
if (stack && !reduce) {
var inners = cards.map(function (c) { return $('.svc__inner', c); });
var stackTick = false;
var paintStack = function () {
stackTick = false;
if (!stackMQ.matches) {
inners.forEach(function (el) { el.style.transform = ''; el.style.removeProperty('--shade'); });
return;
}
var vh = w.innerHeight;
cards.forEach(function (card, i) {
var next = cards[i + 1];
if (!next) return;
var stickyTop = parseFloat(getComputedStyle(next).top) || 100;
var start = vh * 0.85;
var p = (start - next.getBoundingClientRect().top) / (start - stickyTop);
p = Math.max(0, Math.min(1, p));
inners[i].style.transform = p ? 'scale(' + (1 - p * (0.1 - i * 0.012)) + ')' : '';
inners[i].style.setProperty('--shade', (p * 0.5).toFixed(3));
});
};
var requestPaint = function () {
if (!stackTick) { stackTick = true; w.requestAnimationFrame(paintStack); }
};
w.addEventListener('scroll', requestPaint, { passive: true });
w.addEventListener('resize', requestPaint);
paintStack();
}
})(window, document);