tischlerei-staffenberger/js/script.js
mrwhiski f2507033c3 Real contact/Impressum data, SEO tags, responsive gallery images, a11y and templating fixes
- Impressum rewritten with real Austrian business data (ECG instead of
  German TMG references); UID, Gewerbeberechtigung and Firmenbuchnummer
  marked as pending until available
- Real contact email/phone/address across footer and Impressum
- Meta description, Open Graph tags and favicon on all pages
- Gallery images now offer both 800w and 1920w srcset candidates
- Lightbox traps focus while open and restores it on close
- Header/footer extracted into partials/, pages now built from
  templates/ via `npm run build` (scripts/build.js) to remove
  duplication across the 4 pages
2026-07-12 18:11:11 +02:00

214 lines
7 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.

/**
* Tischlerei Staffenberger - Main JavaScript
* Handles navigation active state, gallery filtering, and lightbox on Bilder page.
*/
(function () {
'use strict';
/**
* Set active state on the current page's nav link based on URL pathname.
*/
function setActiveNav() {
var navLinks = document.querySelectorAll('.main-nav a');
var path = window.location.pathname;
var page = path.split('/').pop() || 'index.html';
navLinks.forEach(function (link) {
var href = link.getAttribute('href');
if (!href) return;
var linkPage = href.split('/').pop() || 'index.html';
if (linkPage === page || (page === '' && linkPage === 'index.html')) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
/**
* Gallery filter: show/hide items by data-category.
*/
function initGalleryFilter() {
var filters = document.querySelectorAll('.gallery-filters button');
var items = document.querySelectorAll('.gallery-item');
if (!filters.length || !items.length) return;
filters.forEach(function (btn) {
btn.addEventListener('click', function () {
var category = this.getAttribute('data-filter');
// Update active button
filters.forEach(function (b) {
b.classList.toggle('active', b === btn);
});
// Show/hide items
items.forEach(function (item) {
var itemCategory = item.getAttribute('data-category');
var show = category === 'all' || itemCategory === category;
item.style.display = show ? '' : 'none';
});
});
});
}
/**
* Lightbox on Bilder page: open on gallery image click, navigate with swipe / arrow
* keys / prev+next buttons, close via X, backdrop or ESC.
* Respects the active gallery filter only visible items are part of the sequence.
*/
function initLightbox() {
var lightbox = document.getElementById('lightbox');
var lightboxImg = document.getElementById('lightbox-img');
if (!lightbox || !lightboxImg) return;
var closeBtn = lightbox.querySelector('.lightbox-close');
var backdrop = lightbox.querySelector('.lightbox-backdrop');
var prevBtn = lightbox.querySelector('.lightbox-prev');
var nextBtn = lightbox.querySelector('.lightbox-next');
var currentIndex = 0;
var sequence = []; // visible gallery links at the time the lightbox was opened
var lastFocused = null; // element to restore focus to on close
function getFocusableEls() {
return [prevBtn, nextBtn, closeBtn].filter(function (el) {
return el && !el.hidden;
});
}
function onFocusTrap(e) {
if (e.key !== 'Tab') return;
var focusable = getFocusableEls();
if (!focusable.length) return;
var first = focusable[0];
var last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
function getVisibleLinks() {
return Array.from(document.querySelectorAll('.gallery-item'))
.filter(function (item) { return item.style.display !== 'none'; })
.map(function (item) { return item.querySelector('.gallery-image-link'); })
.filter(Boolean);
}
function showImage(index) {
var link = sequence[index];
if (!link) return;
currentIndex = index;
lightboxImg.src = link.getAttribute('data-lightbox-src') || link.getAttribute('href');
lightboxImg.alt = link.getAttribute('data-lightbox-alt') || '';
var multiple = sequence.length > 1;
if (prevBtn) prevBtn.hidden = !multiple;
if (nextBtn) nextBtn.hidden = !multiple;
}
function navigate(dir) {
showImage((currentIndex + dir + sequence.length) % sequence.length);
}
function openLightbox(index, trigger) {
sequence = getVisibleLinks();
lastFocused = trigger || document.activeElement;
showImage(index);
lightbox.classList.add('is-open');
document.addEventListener('keydown', onKeydown);
var focusable = getFocusableEls();
if (focusable.length) focusable[0].focus();
}
function closeLightbox() {
lightbox.classList.remove('is-open');
document.removeEventListener('keydown', onKeydown);
if (lastFocused && typeof lastFocused.focus === 'function') lastFocused.focus();
lastFocused = null;
}
function onKeydown(e) {
if (e.key === 'Escape') closeLightbox();
if (e.key === 'ArrowRight') navigate(1);
if (e.key === 'ArrowLeft') navigate(-1);
onFocusTrap(e);
}
// Touch swipe
var touchStartX = 0;
lightbox.addEventListener('touchstart', function (e) {
touchStartX = e.changedTouches[0].clientX;
}, { passive: true });
lightbox.addEventListener('touchend', function (e) {
var dx = e.changedTouches[0].clientX - touchStartX;
if (Math.abs(dx) > 50) navigate(dx < 0 ? 1 : -1);
}, { passive: true });
// Open on gallery click
document.querySelectorAll('.gallery-image-link').forEach(function (link) {
link.addEventListener('click', function (e) {
e.preventDefault();
var visibles = getVisibleLinks();
var idx = visibles.indexOf(link);
openLightbox(idx >= 0 ? idx : 0, link);
});
});
if (prevBtn) prevBtn.addEventListener('click', function () { navigate(-1); });
if (nextBtn) nextBtn.addEventListener('click', function () { navigate(1); });
if (closeBtn) closeBtn.addEventListener('click', closeLightbox);
if (backdrop) backdrop.addEventListener('click', closeLightbox);
}
/**
* "Request" buttons on shop page open mailto with product name in subject.
*/
function initShopRequests() {
var requestBtns = document.querySelectorAll('.product-card .btn[data-product]');
requestBtns.forEach(function (btn) {
btn.addEventListener('click', function (e) {
var product = this.getAttribute('data-product');
if (product) {
// Open mail with subject line (no real checkout) replace email with your address
var mailto = 'mailto:info@tischlerei-staffenberger.at?subject=Anfrage%20Produkt%3A%20' + encodeURIComponent(product);
window.location.href = mailto;
}
});
});
}
/**
* Shop empty state: show message when no products are present.
*/
function initShopEmptyState() {
var emptyMsg = document.getElementById('shop-empty-message');
var grid = document.getElementById('shop-grid');
if (!emptyMsg || !grid) return;
var hasCards = grid.querySelectorAll('.product-card').length > 0;
emptyMsg.hidden = hasCards;
}
/**
* Run on DOM ready.
*/
function init() {
setActiveNav();
initGalleryFilter();
initLightbox();
initShopRequests();
initShopEmptyState();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();