(function() { function init() { // ---- Konfiguration (URLs ggf. anpassen) ---- // ---- Datei-URLs (Monatsordner pro Datei anpassen, je nachdem wo sie liegt) ---- var DATA_URL = "https://weco-naturstein.de/wp-content/uploads/2026/07/preisliste_export.txt"; var PLZ_URL = "https://weco-naturstein.de/wp-content/uploads/2026/06/plz_geocoord.csv"; var BILD_URL = "https://weco-naturstein.de/wp-content/uploads/2026/06/preisliste_bilder.txt"; var GESTEIN_URL = "https://weco-naturstein.de/wp-content/uploads/2026/07/sortiment_gesteinsart.csv"; // Modus: "preisliste" (Standard) oder "anfrage" (ohne Preise, ohne PLZ-Gate) var appEl = document.getElementById('preisliste-app'); var IS_ANFRAGE = !!(appEl && (appEl.getAttribute('data-modus') || '').toLowerCase() === 'anfrage'); var LABEL = IS_ANFRAGE ? 'Anfrage' : 'Bestellanfrage'; var ANFRAGE_LISTE = 'N-Z1'; // im Anfrage-Modus verwendete Datenliste (nur fuer Artikelstruktur, Preise werden ausgeblendet) // Bilddarstellung: "gross" = grosses Bild in der Karte | "klein_link" = kleines verlinktes Bild | "aus" = keine Bilder var BILD_MODUS = "gross"; var VALID_KEYS = ["N-Z1", "N-Z2", "O-Z1", "O-Z2"]; // Werks-PLZ (Koordinaten werden aus der PLZ-Tabelle gelesen; Fallback unten) var WERK_LEER = { plz: "26789", lat: 53.2316, lng: 7.4528 }; var WERK_OST = { plz: "39240", lat: 51.9226, lng: 11.8665 }; var ZONE_GRENZE_KM = 150; // Farbzuordnung der Kategorien var KATEGORIE_FARBEN = [ { farbe: '#ABB5D8', kats: ['GARTENMINERALIEN', 'LAVAMULCH', 'BIENENSAND'] }, { farbe: '#A2C62F', kats: ['ZIERKIES', 'ZIERSPLITT', 'DEKOSTEINE', 'EASIHOLD', 'POLYGONALPLATTEN', 'KERAMISCHE TRITTPLATTEN', 'NATURSTEINPFLASTER', 'GRANIT', 'BETON', 'KR\u00c4UTERSPIRALEN', 'CO2 NEUTRALER BETONSTEIN', 'CO\u2082 NEUTRALER BETONSTEIN', 'BRUCHSTEINE', 'TROCKENMAUERWERK'] }, { farbe: '#DB4E2A', kats: ['BAUSAND', 'STREU- & PFLASTERSPLITT', 'STREU- UND PFLASTERSPLITT', 'FUGEN-QUARZSAND', 'FUGENSPLITT', 'WASSERGEB. WEGEDECKE', 'WASSERGEBUNDENE WEGEDECKE'] }, { farbe: '#F08013', kats: ['SPIELEN MIT SAND'] }, { farbe: '#1EB5D2', kats: ['WASSERAUFBEREITUNG'] } ]; // ---- Elemente ---- var headline = document.getElementById('pl-headline'); var gate = document.getElementById('pl-gate'); var plzInput = document.getElementById('pl-plz'); var plzBtn = document.getElementById('pl-plz-btn'); var gateMsg = document.getElementById('pl-gate-msg'); var mainArea = document.getElementById('pl-main'); var listeInfo = document.getElementById('pl-liste-info'); var searchInput = document.getElementById('pl-search'); var kategorieSelect= document.getElementById('pl-kategorie'); var farbeSelect = document.getElementById('pl-farbe'); var koernungSelect = document.getElementById('pl-koernung'); var clearKategorie = document.getElementById('pl-clear-kategorie'); var clearFarbe = document.getElementById('pl-clear-farbe'); var clearKoernung = document.getElementById('pl-clear-koernung'); var excelBtn = document.getElementById('pl-excel'); var plzChangeBtn = document.getElementById('pl-plz-change'); var resultsDiv = document.getElementById('pl-results'); var loadingDiv = document.getElementById('pl-loading'); var emptyDiv = document.getElementById('pl-empty'); if (!mainArea || !searchInput || !kategorieSelect || !farbeSelect || !koernungSelect || !resultsDiv) { console.warn('Preisliste: ben\u00f6tigte Elemente nicht gefunden, breche ab.'); return; } var KAT_DEFAULT = 'Alle Kategorien'; var FARBE_DEFAULT = 'Alle Farben'; var KOERN_DEFAULT = 'Alle K\u00f6rnungen / Ma\u1e9ee / Formate'; var fullData = null; // gesamtes JSON-Objekt mit allen Listen var allData = []; // aktuell gewaehlte Liste var plzData = null; // { plz: [lat, lng] } var bildData = {}; // { "BEZEICHNUNG|FARBE": { bild, link } } var gesteinData = {}; // { MITTELTEIL: "Gesteinsart" } var listeKey = null; // Bilddaten laden (optional; fehlt eine Zuordnung, bleibt der Artikel ohne Bild) fetch(BILD_URL) .then(function(r) { return r.text(); }) .then(function(text) { var start = text.indexOf('{'); bildData = start !== -1 ? JSON.parse(text.slice(start)) : {}; if (mainArea.style.display !== 'none') render(); }) .catch(function() { /* keine Bilddatei vorhanden - ignorieren */ }); // Gesteinsart-Zuordnung laden (optional) fetch(GESTEIN_URL) .then(function(r) { return r.text(); }) .then(function(text) { var start = text.indexOf('{'); gesteinData = start !== -1 ? JSON.parse(text.slice(start)) : {}; if (mainArea.style.display !== 'none') render(); }) .catch(function() { /* keine Gesteinsart-Datei vorhanden - ignorieren */ }); // ---- Daten laden ---- fetch(DATA_URL) .then(function(r) { return r.json(); }) .then(function(json) { fullData = json; if (IS_ANFRAGE) { // Anfrage-Modus: kein PLZ-Gate, direkt Artikelliste (Preise werden ausgeblendet) aktiviereListe(ANFRAGE_LISTE, ''); } else { // Optionaler Direktaufruf fuer interne Zwecke: ?liste=N-Z1 var params = new URLSearchParams(window.location.search); var direct = (params.get('liste') || '').toUpperCase(); if (VALID_KEYS.indexOf(direct) !== -1) { aktiviereListe(direct, direct); } } }) .catch(function(err) { if (gateMsg) gateMsg.textContent = 'Preisliste konnte nicht geladen werden.'; console.error('Preisliste Fehler:', err); }); // PLZ-Tabelle lazy laden function ladePlzDaten() { if (plzData) return Promise.resolve(plzData); return fetch(PLZ_URL) .then(function(r) { return r.text(); }) .then(function(text) { plzData = parsePlzCsv(text); // Werkskoordinaten aus Tabelle uebernehmen, falls vorhanden if (plzData[WERK_LEER.plz]) { WERK_LEER.lat = plzData[WERK_LEER.plz][0]; WERK_LEER.lng = plzData[WERK_LEER.plz][1]; } if (plzData[WERK_OST.plz]) { WERK_OST.lat = plzData[WERK_OST.plz][0]; WERK_OST.lng = plzData[WERK_OST.plz][1]; } return plzData; }); } function parsePlzCsv(text) { var map = {}; var lines = text.split(/\r?\n/); for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (!line) continue; var parts = line.split(','); if (parts.length < 3) continue; var plz = parts[0].trim(); if (!/^\d{5}$/.test(plz)) continue; // Kopfzeile / ungueltige ueberspringen var lat = parseFloat(parts[1]); var lng = parseFloat(parts[2]); if (!isNaN(lat) && !isNaN(lng)) map[plz] = [lat, lng]; } return map; } // ---- Geo ---- function haversine(lat1, lng1, lat2, lng2) { var R = 6371; // km var toRad = Math.PI / 180; var dLat = (lat2 - lat1) * toRad; var dLng = (lng2 - lng1) * toRad; var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * toRad) * Math.cos(lat2 * toRad) * Math.sin(dLng / 2) * Math.sin(dLng / 2); return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } function ermittleListe(plz) { var c = plzData[plz]; if (!c) return null; var dLeer = haversine(c[0], c[1], WERK_LEER.lat, WERK_LEER.lng); var dOst = haversine(c[0], c[1], WERK_OST.lat, WERK_OST.lng); var nord = dLeer <= dOst; var dWerk = nord ? dLeer : dOst; var zone = dWerk <= ZONE_GRENZE_KM ? 1 : 2; var key = (nord ? 'N' : 'O') + '-Z' + zone; var label = (nord ? 'Nord' : 'Ost') + ', Zone ' + zone; return { key: key, label: label, dist: Math.round(dWerk) }; } // ---- PLZ-Gate ---- function pruefePlz() { var raw = (plzInput.value || '').trim(); if (/^\d{1,5}$/.test(raw)) { while (raw.length < 5) raw = '0' + raw; // fuehrende Nullen } if (!/^\d{5}$/.test(raw)) { gateMsg.textContent = 'Bitte eine g\u00fcltige 5-stellige Postleitzahl eingeben.'; return; } gateMsg.textContent = 'Ermittle passende Preisliste\u2026'; ladePlzDaten().then(function() { var res = ermittleListe(raw); if (!res) { gateMsg.textContent = 'Zu dieser Postleitzahl wurde kein Standort gefunden. Bitte pr\u00fcfen Sie die Eingabe oder kontaktieren Sie uns.'; return; } if (!fullData || !fullData[res.key]) { gateMsg.textContent = 'F\u00fcr Ihre Region (' + res.label + ') liegt aktuell keine Preisliste vor. Bitte kontaktieren Sie uns.'; return; } gateMsg.textContent = ''; aktiviereListe(res.key, res.label + ' (ca. ' + res.dist + ' km)'); }).catch(function(err) { gateMsg.textContent = 'Die Standortdaten konnten nicht geladen werden.'; console.error('PLZ-Daten Fehler:', err); }); } if (plzBtn) plzBtn.addEventListener('click', pruefePlz); if (plzInput) plzInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); pruefePlz(); } }); if (plzChangeBtn) { plzChangeBtn.addEventListener('click', function() { mainArea.style.display = 'none'; gate.style.display = 'block'; plzInput.value = ''; plzInput.focus(); }); } // ---- Liste aktivieren ---- // Gebinde-Fortsetzungszeilen zusammenfuehren. // Fall: In der Excel steht ein Gebinde ueber zwei Zeilen (z. B. "1 Palette" / "ca. 1500 KG"). // Die zweite Zeile hat KEINE Artikelnummer und KEINE Preise -> sie ist keine eigene Variante, // sondern gehoert zum Gebinde der vorherigen Variante. // Abgrenzung: Varianten ohne Artnr, aber MIT Preis (z. B. "lose" bei Wegedecken) bleiben eigenstaendig. function normalisiereVarianten(liste) { if (!liste || !liste.length) return liste; function leer(v) { return v === null || v === undefined || String(v).trim() === ''; } liste.forEach(function(item) { if (!item.varianten || item.varianten.length < 2) return; var neu = []; item.varianten.forEach(function(v) { var ohneArtnr = leer(v.artikelnummer); var ohnePreis = leer(v.preis_ab_werk) && leer(v.preis_ab5t) && leer(v.preis_ab12t) && leer(v.preis_ab24t); if (ohneArtnr && ohnePreis && !leer(v.gebinde) && neu.length > 0) { var prev = neu[neu.length - 1]; prev.gebinde = (leer(prev.gebinde) ? '' : prev.gebinde + ', ') + String(v.gebinde).trim(); } else { neu.push(v); } }); item.varianten = neu; }); return liste; } function aktiviereListe(key, label) { listeKey = key; allData = normalisiereVarianten((fullData && fullData[key]) ? fullData[key] : []); if (listeInfo && !IS_ANFRAGE) { listeInfo.textContent = key; } if (gate) gate.style.display = 'none'; mainArea.style.display = 'block'; // Filter zuruecksetzen searchInput.value = ''; kategorieSelect.value = ''; farbeSelect.value = ''; koernungSelect.value = ''; render(); } // ---- Filter-Logik ---- function passes(item, f) { if (f.kategorie && item.kategorie !== f.kategorie) return false; if (f.farbe && item.farbe !== f.farbe) return false; if (f.koernung) { var ok = false; for (var vi = 0; vi < item.varianten.length; vi++) { if (item.varianten[vi].koernung === f.koernung) { ok = true; break; } } if (!ok) return false; } if (f.search) { var parts = [item.bezeichnung, item.farbe, item.kategorie]; for (var vj = 0; vj < item.varianten.length; vj++) { parts.push(item.varianten[vj].koernung + ' ' + item.varianten[vj].artikelnummer); } if (parts.join(' ').toLowerCase().indexOf(f.search) === -1) return false; } return true; } function aktuelleFilter() { return { search: searchInput.value.trim().toLowerCase(), kategorie: kategorieSelect.value, farbe: farbeSelect.value, koernung: koernungSelect.value }; } function clone(f, ohne) { var c = { search: f.search, kategorie: f.kategorie, farbe: f.farbe, koernung: f.koernung }; c[ohne] = ''; return c; } function distinct(items, getter) { var seen = []; items.forEach(function(item) { var vals = getter(item); vals.forEach(function(v) { if (v && seen.indexOf(v) === -1) seen.push(v); }); }); return seen.sort(); } function rebuildSelect(select, defaultLabel, values, currentVal) { select.innerHTML = ''; var def = document.createElement('option'); def.value = ''; def.textContent = defaultLabel; select.appendChild(def); values.forEach(function(val) { var opt = document.createElement('option'); opt.value = val; opt.textContent = val; select.appendChild(opt); }); select.value = (values.indexOf(currentVal) !== -1) ? currentVal : ''; } function updateFacets() { var f = aktuelleFilter(); var katItems = allData.filter(function(it) { return passes(it, clone(f, 'kategorie')); }); rebuildSelect(kategorieSelect, KAT_DEFAULT, distinct(katItems, function(it) { return [it.kategorie]; }), f.kategorie); var farbeItems = allData.filter(function(it) { return passes(it, clone(f, 'farbe')); }); rebuildSelect(farbeSelect, FARBE_DEFAULT, distinct(farbeItems, function(it) { return [it.farbe]; }), f.farbe); var koernItems = allData.filter(function(it) { return passes(it, clone(f, 'koernung')); }); rebuildSelect(koernungSelect, KOERN_DEFAULT, distinct(koernItems, function(it) { return it.varianten.map(function(v) { return v.koernung; }); }), f.koernung); } function updateClearButtons() { if (clearKategorie) clearKategorie.style.display = kategorieSelect.value !== '' ? 'inline-block' : 'none'; if (clearFarbe) clearFarbe.style.display = farbeSelect.value !== '' ? 'inline-block' : 'none'; if (clearKoernung) clearKoernung.style.display = koernungSelect.value !== '' ? 'inline-block' : 'none'; } // ---- Darstellung ---- function kategorieFarbe(kat) { var k = (kat || '').toUpperCase(); for (var gi = 0; gi < KATEGORIE_FARBEN.length; gi++) { var grp = KATEGORIE_FARBEN[gi]; for (var ki = 0; ki < grp.kats.length; ki++) { if (k.indexOf(grp.kats[ki]) === 0) return grp.farbe; } } return '#888888'; } function formatPrice(p) { if (p === null || p === undefined) return '\u2013'; if (typeof p === 'number') return p.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' \u20ac'; return p; } function escapeHtml(str) { var div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function escapeAttr(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function normBild(v) { if (!v) return null; if (typeof v === 'string') return { bild: v, link: null }; return v; } // WordPress-Vorschaubild aus der Original-URL ableiten (Format siehe THUMB_SUFFIX) var THUMB_SUFFIX = '-300x400'; // muss einer in WordPress erzeugten Bildgroesse entsprechen function thumbUrl(bild) { if (!bild) return bild; return bild.replace(/\.(jpe?g|png|gif|webp)$/i, THUMB_SUFFIX + '.$1'); } function mittelteil(an) { var core = (an || '').toUpperCase().trim().split('-')[0]; return core.length > 2 ? core.substring(2) : ''; } function normTxt(s) { return (s || '').toUpperCase().replace(/\s+/g, ' ').trim(); } function bildInfo(item) { var i, mt; // 1. exakte Artikelnummer einer Variante if (item.varianten) { for (i = 0; i < item.varianten.length; i++) { var an = (item.varianten[i].artikelnummer || '').toUpperCase().trim(); if (an && bildData[an]) return normBild(bildData[an]); } // 2. Mittelteil (Material+Koernung+Farbe) for (i = 0; i < item.varianten.length; i++) { mt = mittelteil(item.varianten[i].artikelnummer); if (mt && bildData[mt]) return normBild(bildData[mt]); } // 3. Material+Farbe (Koernung ignoriert) for (i = 0; i < item.varianten.length; i++) { mt = mittelteil(item.varianten[i].artikelnummer); if (mt && mt.length === 9) { var mf = mt.substring(0, 2) + mt.substring(6, 9); if (bildData[mf]) return normBild(bildData[mf]); } } } var bez = normTxt(item.bezeichnung); var farbe = normTxt(item.farbe); var koern = normTxt(item.varianten && item.varianten[0] ? item.varianten[0].koernung : ''); // 4. BEZEICHNUNG|FARBE|KOERNUNG if (koern && bildData[bez + '|' + farbe + '|' + koern]) return normBild(bildData[bez + '|' + farbe + '|' + koern]); // 5. BEZEICHNUNG|FARBE return normBild(bildData[bez + '|' + farbe]); } // Gesteinsart eines Artikels ueber den Mittelteil einer Variante function gesteinVon(item) { if (!item.varianten) return ''; for (var i = 0; i < item.varianten.length; i++) { var an = (item.varianten[i].artikelnummer || '').toUpperCase().trim(); if (an && gesteinData[an]) return gesteinData[an]; // exakte Artnr var mt = mittelteil(item.varianten[i].artikelnummer); if (mt && gesteinData[mt]) return gesteinData[mt]; // Mittelteil } return ''; } function render() { updateFacets(); updateClearButtons(); var f = aktuelleFilter(); var filtered = allData.filter(function(it) { return passes(it, f); }); resultsDiv.innerHTML = ''; if (filtered.length === 0) { if (emptyDiv) emptyDiv.style.display = 'block'; return; } if (emptyDiv) emptyDiv.style.display = 'none'; filtered.forEach(function(item) { var katFarbe = kategorieFarbe(item.kategorie); var farbeInline = item.farbe ? ' ' + escapeHtml(item.farbe) + '' : ''; var gestein = gesteinVon(item); var gesteinInline = gestein ? ' ' + escapeHtml(gestein) + '' : ''; var info = bildInfo(item); var rows = item.varianten.map(function(v) { var d = 'data-kat="' + escapeAttr(item.kategorie || '') + '" data-bez="' + escapeAttr(item.bezeichnung || '') + '" data-farbe="' + escapeAttr(item.farbe || '') + '" data-koern="' + escapeAttr(v.koernung || '') + '" data-art="' + escapeAttr(v.artikelnummer || '') + '"'; var preisTds = IS_ANFRAGE ? '' : '' + escapeHtml(String(formatPrice(v.preis_ab_werk))) + '' + '' + escapeHtml(String(formatPrice(v.preis_ab5t))) + '' + '' + escapeHtml(String(formatPrice(v.preis_ab12t))) + '' + '' + escapeHtml(String(formatPrice(v.preis_ab24t))) + ''; return '' + '' + escapeHtml(v.koernung || '\u2013') + '' + '' + escapeHtml(v.gebinde || '\u2013') + '' + '' + escapeHtml(v.artikelnummer || '\u2013') + '' + preisTds + '' + ''; }).join(''); var preisThs = IS_ANFRAGE ? '' : 'Ab Werk' + 'Frei Lager
ab 5 t' + 'Frei Lager
ab 12 t' + 'Frei Lager
ab 24 t'; var tableHtml = '' + '' + '' + '' + preisThs + '' + '' + rows + '
K\u00f6rnung / Ma\u1e9ee / FormatGebindeArtikel-Nr.' + LABEL + '
'; var categoryHtml = '
' + escapeHtml(item.kategorie || '') + '
'; var titleHtml = '
' + escapeHtml(item.bezeichnung) + farbeInline + gesteinInline + '
'; var bodyHtml; if (BILD_MODUS === 'gross' && info && info.bild) { var img = '' + escapeAttr(item.bezeichnung) + ''; if (info.link) img = '' + img + ''; bodyHtml = '
' + '
' + img + '
' + '
' + '
' + titleHtml + '
' + tableHtml + '
' + '
'; } else if (BILD_MODUS === 'klein_link' && info && info.bild) { var thumb = '' + escapeAttr(item.bezeichnung) + ''; if (info.link) thumb = '' + thumb + ''; bodyHtml = '
' + thumb + titleHtml + '
' + tableHtml; } else { bodyHtml = '
' + titleHtml + '
' + tableHtml; } var card = document.createElement('div'); card.className = 'pl-card'; card.innerHTML = categoryHtml + bodyHtml; resultsDiv.appendChild(card); }); } // ---- CSV / Excel-Export ---- function csvCell(val) { var s = (val === null || val === undefined) ? '' : String(val); if (s.indexOf(';') !== -1 || s.indexOf('"') !== -1 || s.indexOf('\n') !== -1) { s = '"' + s.replace(/"/g, '""') + '"'; } return s; } function preisCsv(p) { if (p === null || p === undefined) return ''; if (typeof p === 'number') return String(p).replace('.', ','); return p; } function exportExcel() { var f = aktuelleFilter(); var filtered = allData.filter(function(it) { return passes(it, f); }); var header = IS_ANFRAGE ? ['Kategorie', 'Bezeichnung', 'Farbe', 'Gesteinsart', 'K\u00f6rnung / Ma\u00dfe / Format', 'Gebinde', 'Artikel-Nr.'] : ['Kategorie', 'Bezeichnung', 'Farbe', 'K\u00f6rnung / Ma\u00dfe / Format', 'Gebinde', 'Artikel-Nr.', 'Ab Werk', 'Frei Lager ab 5 t', 'Frei Lager ab 12 t', 'Frei Lager ab 24 t']; var lines = [header.map(csvCell).join(';')]; filtered.forEach(function(item) { var gestein = IS_ANFRAGE ? gesteinVon(item) : ''; item.varianten.forEach(function(v) { var row = IS_ANFRAGE ? [ item.kategorie || '', item.bezeichnung || '', item.farbe || '', gestein, v.koernung || '', v.gebinde || '', v.artikelnummer || '' ] : [ item.kategorie || '', item.bezeichnung || '', item.farbe || '', v.koernung || '', v.gebinde || '', v.artikelnummer || '', preisCsv(v.preis_ab_werk), preisCsv(v.preis_ab5t), preisCsv(v.preis_ab12t), preisCsv(v.preis_ab24t) ]; lines.push(row.map(csvCell).join(';')); }); }); var csv = '\ufeff' + lines.join('\r\n'); // BOM fuer Excel/Umlaute var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); var datum = new Date().toISOString().slice(0, 10); a.href = url; a.download = (IS_ANFRAGE ? 'Sortiment_' : 'Preisliste_') + datum + '.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } if (excelBtn) excelBtn.addEventListener('click', exportExcel); // ---- Filter-Events ---- searchInput.addEventListener('input', render); kategorieSelect.addEventListener('change', render); farbeSelect.addEventListener('change', render); koernungSelect.addEventListener('change', render); if (clearKategorie) clearKategorie.addEventListener('click', function() { kategorieSelect.value = ''; render(); }); if (clearFarbe) clearFarbe.addEventListener('click', function() { farbeSelect.value = ''; render(); }); if (clearKoernung) clearKoernung.addEventListener('click', function() { koernungSelect.value = ''; render(); }); setupLightbox(); setupBestellung(); } // ========================================================================== // BESTELLANFRAGE / WARENKORB // ========================================================================== // NACH dem Gravity-Forms-Import hier die Formular-ID eintragen (Zahl): var BESTELL_FORM_ID = null; // z. B. 9 -> aus der GF-Formularliste (Spalte ID) var VORLAUF_WERKTAGE = 10; // frühestes Wunschlieferdatum = heute + X Werktage var CART_KEY = 'weco_pl_cart_v1'; function setupBestellung() { if (window.__plCart) return; window.__plCart = true; var resultsDiv = document.getElementById('pl-results'); var appEl2 = document.getElementById('preisliste-app'); var IS_ANFRAGE = !!(appEl2 && (appEl2.getAttribute('data-modus') || '').toLowerCase() === 'anfrage'); var LABEL = IS_ANFRAGE ? 'Anfrage' : 'Bestellanfrage'; function escapeHtml(s) { var d = document.createElement('div'); d.textContent = (s == null ? '' : String(s)); return d.innerHTML; } function escapeAttr(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } var KAT_FARBEN = [ { farbe: '#ABB5D8', kats: ['GARTENMINERALIEN', 'LAVAMULCH', 'BIENENSAND'] }, { farbe: '#A2C62F', kats: ['ZIERKIES', 'ZIERSPLITT', 'DEKOSTEINE', 'EASIHOLD', 'POLYGONALPLATTEN', 'KERAMISCHE TRITTPLATTEN', 'NATURSTEINPFLASTER', 'GRANIT', 'BETON', 'KR\u00c4UTERSPIRALEN', 'CO2 NEUTRALER BETONSTEIN', 'CO\u2082 NEUTRALER BETONSTEIN', 'BRUCHSTEINE', 'TROCKENMAUERWERK'] }, { farbe: '#DB4E2A', kats: ['BAUSAND', 'STREU- & PFLASTERSPLITT', 'STREU- UND PFLASTERSPLITT', 'FUGEN-QUARZSAND', 'FUGENSPLITT', 'WASSERGEB. WEGEDECKE', 'WASSERGEBUNDENE WEGEDECKE'] }, { farbe: '#F08013', kats: ['SPIELEN MIT SAND'] }, { farbe: '#1EB5D2', kats: ['WASSERAUFBEREITUNG'] } ]; function katFarbeVon(kat) { var k = (kat || '').toUpperCase(); for (var gi = 0; gi < KAT_FARBEN.length; gi++) { for (var ki = 0; ki < KAT_FARBEN[gi].kats.length; ki++) { if (k.indexOf(KAT_FARBEN[gi].kats[ki]) === 0) return KAT_FARBEN[gi].farbe; } } return '#888888'; } var cart = ladeCart(); // ---------- Styles (injiziert, damit der HTML-Block unberührt bleibt) ---------- var st = document.createElement('style'); st.textContent = '#preisliste-app .pl-filters { position: static !important; }' + '.pl-gestein-inline { font-size: 12px; font-weight: 600; color: #3a5a40; background: #eef3ec; padding: 2px 8px; border-radius: 5px; margin-left: 6px; vertical-align: middle; }' + '.pl-order { text-align: center; vertical-align: middle; }' + '.pl-order-head { text-align: center; }' + '.pl-order .pl-add { padding: 7px 14px; border: none; border-radius: 5px; background: #1d6f42; color: #fff; font-size: 13px; font-weight: 600; cursor: pointer; }' + '.pl-order .pl-add:hover { background: #155232; }' + '.pl-add-full { display: none; }' + '.pl-add-short { display: inline; }' + '.pl-cart-fab { position: fixed; right: 20px; bottom: 20px; z-index: 100001; padding: 10px 16px; border: none; border-radius: 26px; background: #1d6f42; color: #fff; font-size: 14px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 14px rgba(0,0,0,0.25); display: none; }' + '.pl-cart-fab.show { display: inline-block; }' + '.pl-cart-panel { position: fixed; right: 16px; bottom: 76px; top: auto; height: min(80vh, 820px); width: 380px; max-width: 90vw; background: #fff; z-index: 100002; border: 1px solid #ddd; border-radius: 10px; box-shadow: 0 8px 30px rgba(0,0,0,0.25); transform: translateX(calc(100% + 24px)); transition: transform 0.25s ease; display: flex; flex-direction: column; overflow: hidden; }' + '.pl-cart-panel.open { transform: translateX(0); }' + '.pl-cart-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px; border-bottom: 1px solid #e5e5e5; background: #1d6f42; }' + '.pl-cart-head h3 { margin: 0; font-size: 15px; color: #fff; }' + '.pl-cart-close { background: none; border: none; font-size: 24px; line-height: 1; cursor: pointer; color: #fff; }' + '.pl-cart-list { flex: 1 1 auto; overflow-y: auto; padding: 10px 14px; }' + '.pl-cart-empty { color: #888; text-align: center; padding: 30px 0; font-size: 13px; }' + '.pl-cart-item { border: 1px solid #e8e8e8; border-radius: 7px; overflow: hidden; margin-bottom: 10px; }' + '.pl-cart-item .pl-ci-head { padding: 6px 10px; color: #fff; }' + '.pl-cart-item .pl-ci-name { font-weight: 700; font-size: 13px; }' + '.pl-cart-item .pl-ci-farbe { font-weight: 400; font-size: 12px; opacity: 0.9; margin-left: 4px; }' + '.pl-cart-item .pl-ci-body { padding: 8px 10px; }' + '.pl-cart-item .pl-ci-sub { font-size: 11px; color: #666; margin: 0 0 6px; }' + '.pl-cart-item .pl-ci-row { display: flex; align-items: center; gap: 6px; }' + '.pl-cart-item .pl-ci-menge, .pl-cart-item .pl-ci-einheit { flex: 1 1 0 !important; width: auto !important; min-width: 0 !important; height: 32px !important; box-sizing: border-box !important; padding: 4px 8px !important; border: 1px solid #ccc !important; border-radius: 5px !important; font-size: 13px !important; margin: 0 !important; }' + '.pl-cart-item .pl-ci-del { flex: 0 0 auto; background: none; border: none; color: #c0392b; cursor: pointer; font-size: 12px; text-decoration: underline; padding: 0; }' + '.pl-cart-foot { border-top: 1px solid #e5e5e5; padding: 10px 14px; }' + '.pl-cart-hint { font-size: 11px; color: #777; margin: 0 0 8px; }' + '.pl-cart-checkout { width: 100%; padding: 10px; border: none; border-radius: 6px; background: #A2C62F; color: #fff; font-size: 14px; font-weight: 600; cursor: pointer; }' + '.pl-cart-checkout:hover { background: #82a024; }' + '.pl-cart-checkout:disabled { background: #ccc; cursor: not-allowed; }' + '.pl-cart-clear { width: 100%; margin-top: 6px; padding: 6px; border: 1px solid #ccc; border-radius: 6px; background: #f5f5f5; font-size: 12px; cursor: pointer; }' + '.pl-cart-data { display: none !important; }' + // ===================== RESPONSIVE ===================== // Tablets / kleinere Desktops: Karte kompakter, Bild kleiner '@media (max-width: 1024px) {' + '#preisliste-app { max-width: 100% !important; }' + '#preisliste-app .pl-card-img { flex: 0 0 130px !important; }' + '#preisliste-app table.pl-variants { font-size: 13px; }' + '#preisliste-app table.pl-variants th, #preisliste-app table.pl-variants td { padding: 6px 8px; }' + '}' + // Handys / schmale Tablets: Tabelle -> gestapelte Zeilen-Karten '@media (max-width: 820px) {' + '#preisliste-app .pl-card { padding: 12px 14px; }' + '#preisliste-app .pl-category { margin: -12px -14px 10px -14px; padding: 8px 14px; font-size: 12px; }' + '#preisliste-app .pl-card-body { flex-direction: column !important; gap: 12px !important; }' + '#preisliste-app .pl-card-img { flex: 0 0 auto !important; width: 100% !important; max-width: 200px !important; margin: 0 auto !important; }' + '#preisliste-app .pl-card-img img { aspect-ratio: 4 / 3 !important; }' + '#preisliste-app .pl-card-content { overflow-x: visible !important; }' + '#preisliste-app .pl-card-title { font-size: 16px; }' + // Tabelle aufbrechen '#preisliste-app table.pl-variants { display: block; width: 100%; border-collapse: collapse; }' + '#preisliste-app table.pl-variants thead { display: none; }' + '#preisliste-app table.pl-variants tbody, #preisliste-app table.pl-variants tr, #preisliste-app table.pl-variants td { display: block; width: 100%; }' + '#preisliste-app table.pl-variants tr { border: 1px solid #e5e5e5; border-radius: 8px; padding: 8px 10px; margin-bottom: 10px; background: #fff; }' + '#preisliste-app table.pl-variants td { border: none !important; padding: 3px 0 !important; white-space: normal !important; text-align: left !important; display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 13px; }' + '#preisliste-app table.pl-variants td[data-label]::before { content: attr(data-label); font-size: 11px; font-weight: 700; color: #777; text-transform: uppercase; flex: 0 0 auto; }' + '#preisliste-app table.pl-variants td.pl-price { text-align: right !important; }' + '#preisliste-app table.pl-variants td.pl-order { display: block; margin-top: 8px; padding-top: 8px !important; border-top: 1px solid #eee !important; }' + '#preisliste-app table.pl-variants td.pl-order .pl-add { width: 100%; padding: 10px; font-size: 14px; }' + '#preisliste-app table.pl-variants td.pl-order .pl-add-short { display: none; }' + '#preisliste-app table.pl-variants td.pl-order .pl-add-full { display: inline; }' + '}' + // Warenkorb-Panel + Formular auf Handys '@media (max-width: 600px) {' + '.pl-cart-panel { right: 8px !important; left: 8px !important; bottom: 70px !important; width: auto !important; max-width: none !important; height: min(70vh, 520px) !important; }' + '.pl-cart-fab { right: 12px !important; bottom: 12px !important; font-size: 13px !important; padding: 9px 14px !important; }' + '.gform_wrapper { padding: 14px !important; }' + '#pl-confirm-popup .pl-cp-box { padding: 22px 18px; }' + '#preisliste-app .pl-headline { font-size: 28px !important; }' + '#preisliste-app .pl-filters { gap: 8px !important; padding: 8px 0 !important; }' + '#preisliste-app .pl-filters input, #preisliste-app .pl-filters select { padding: 9px 10px !important; font-size: 14px !important; }' + '#preisliste-app .pl-filter-row { flex-direction: column !important; gap: 8px !important; }' + '#preisliste-app .pl-toolbar { flex-direction: column; align-items: stretch; }' + '#preisliste-app .pl-toolbar button { width: 100%; }' + '#preisliste-app #pl-gate { padding: 14px !important; }' + '#preisliste-app #pl-plz, #preisliste-app #pl-plz-btn { flex: 1 1 100% !important; }' + '.pl-cart-item .pl-ci-row { flex-wrap: wrap; }' + '}' + '.pl-order-summary { border: 1px solid #e0e0e0; border-radius: 8px; padding: 14px 18px; margin: 0 0 20px; background: #fafafa; scroll-margin-top: 150px; }' + '.pl-order-summary h4 { margin: 0 0 10px; font-size: 16px; }' + '.pl-order-summary ul { list-style: none; margin: 0; padding: 0; }' + '.pl-order-summary li { padding: 8px 12px; margin: 0 0 6px; background: #fff; border: 1px solid #e5e5e5; border-radius: 6px; font-size: 14px; line-height: 1.5; }' + '.pl-order-summary li:last-child { margin-bottom: 0; }' + '.pl-order-note { margin: 10px 0 0 !important; font-size: 12px; color: #555; line-height: 1.5; }' + '#pl-confirm-popup { position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 100003; display: flex; align-items: center; justify-content: center; padding: 20px; }' + '#pl-confirm-popup .pl-cp-box { background: #fff; border-radius: 10px; max-width: 460px; width: 100%; padding: 28px 26px; text-align: center; box-shadow: 0 10px 40px rgba(0,0,0,0.3); }' + '#pl-confirm-popup .pl-cp-icon { width: 52px; height: 52px; margin: 0 auto 14px; border-radius: 50%; background: #A2C62F; color: #fff; font-size: 30px; line-height: 52px; }' + '#pl-confirm-popup .pl-cp-msg { font-size: 14px; line-height: 1.6; color: #333; margin-bottom: 18px; }' + '#pl-confirm-popup .pl-cp-close { padding: 10px 26px; border: none; border-radius: 6px; background: #1d6f42; color: #fff; font-size: 14px; font-weight: 600; cursor: pointer; }' + '#pl-confirm-popup .pl-cp-close:hover { background: #155232; }' + '.pl-add-flash { outline: 2px solid #A2C62F; }' + // Bestellformular bis zum Checkout ausblenden (per CSS, timing-unabhaengig) '.gform_wrapper:not(.pl-form-visible), .gform-wrapper:not(.pl-form-visible) { display: none !important; }' + // Gravity-Forms-Formular: kompakt, 2-spaltig, Weco-Look '.gform_wrapper { max-width: 860px !important; background: #fafafa !important; border: 1px solid #e0e0e0 !important; border-radius: 8px !important; padding: 22px 26px !important; }' + '.gform_wrapper .gform_fields { display: grid !important; grid-template-columns: repeat(12, 1fr) !important; gap: 12px 22px !important; }' + '.gform_wrapper .gfield { grid-column: span 6 !important; }' + '.gform_wrapper .gfield--type-textarea, .gform_wrapper .gfield--type-checkbox, .gform_wrapper .gfield--type-section, .gform_wrapper .pl-cart-data { grid-column: span 12 !important; }' + '@media (max-width: 700px) { .gform_wrapper .gfield { grid-column: span 12 !important; } }' + 'html body .gform_wrapper .gform_fields .gfield .gfield_label, html body .gform_wrapper label.gfield_label, html body .gform_wrapper legend.gfield_label { font-size: 12px !important; font-weight: 600 !important; margin-bottom: 3px !important; line-height: 1.3 !important; letter-spacing: 0 !important; }' + '.gform_wrapper .gsection, .gform_wrapper .gfield--type-section { border-bottom: 1px solid #ddd !important; padding-bottom: 4px !important; margin-top: 4px !important; }' + '.gform_wrapper .gsection_title, .gform_wrapper .gfield--type-section h2, .gform_wrapper .gfield--type-section h3 { font-size: 14px !important; font-weight: 700 !important; margin: 0 !important; }' + '.gform_wrapper .gfield_required { color: #DB4E2A !important; }' + '.gform_wrapper .gfield_description { font-size: 11px !important; color: #777 !important; }' + '.gform_wrapper input[type="text"], .gform_wrapper input[type="email"], .gform_wrapper input[type="tel"], .gform_wrapper input[type="number"], .gform_wrapper select, .gform_wrapper textarea { width: 100% !important; padding: 7px 10px !important; font-size: 13px !important; border: 1px solid #ccc !important; border-radius: 6px !important; background: #fff !important; box-sizing: border-box !important; }' + '.gform_wrapper textarea { min-height: 70px !important; }' + '.gform_wrapper .gchoice { display: flex !important; align-items: flex-start !important; gap: 8px !important; }' + '.gform_wrapper .gchoice input[type="checkbox"] { margin-top: 3px !important; flex: 0 0 auto !important; }' + '.gform_wrapper .ginput_container_checkbox label, .gform_wrapper .gfield_checkbox label { font-size: 13px !important; font-weight: 400 !important; line-height: 1.5 !important; flex: 1 1 auto !important; }' + // Absende-Button: sichtbar erzwingen (Divi blendet Formular-Buttons gern aus) + Weco-Look '.gform_wrapper .gform_footer, .gform_wrapper .gform-footer { display: block !important; margin-top: 18px !important; }' + '.gform_wrapper input[type="submit"], .gform_wrapper button[type="submit"], .gform_wrapper .gform_button { display: inline-block !important; visibility: visible !important; opacity: 1 !important; padding: 12px 28px !important; border: none !important; border-radius: 6px !important; background: #A2C62F !important; color: #fff !important; font-size: 15px !important; font-weight: 600 !important; cursor: pointer !important; line-height: 1.3 !important; }' + '.gform_wrapper input[type="submit"]:hover, .gform_wrapper .gform_button:hover { background: #82a024 !important; }'; document.head.appendChild(st); // ---------- UI-Elemente (Panel + Overlay + FAB) ---------- var fab = document.createElement('button'); fab.type = 'button'; fab.className = 'pl-cart-fab'; document.body.appendChild(fab); var panel = document.createElement('div'); panel.className = 'pl-cart-panel'; panel.innerHTML = '

Ihre ' + LABEL + '

' + '
' + '
' + '

Mindestbestellmenge gesamt: 5 t (\u00fcber alle Positionen).' + (IS_ANFRAGE ? '' : ' Diese ' + LABEL + ' ist unverbindlich.') + '
Wichtiger Hinweis: Wir versenden nur in vollen Verpackungseinheiten. Sollte Ihre angegebene Menge von einer vollen VE abweichen, wird diese von uns erh\u00f6ht. Bei Fragen dazu melden Sie sich gerne!

' + '' + '' + '
'; document.body.appendChild(panel); var listEl = panel.querySelector('.pl-cart-list'); var checkoutBtn = panel.querySelector('.pl-cart-checkout'); // ---------- Speicherung ---------- function ladeCart() { try { var r = window.localStorage.getItem(CART_KEY); return r ? JSON.parse(r) : []; } catch (e) { return []; } } function speichere() { try { window.localStorage.setItem(CART_KEY, JSON.stringify(cart)); } catch (e) {} } // ---------- Warenkorb-Operationen ---------- function keyOf(it) { return (it.art || '') + '|' + it.einheit; } function addItem(it) { for (var i = 0; i < cart.length; i++) { if (keyOf(cart[i]) === keyOf(it)) { cart[i].menge += it.menge; speichere(); renderCart(); return; } } cart.push(it); speichere(); renderCart(); } function leeren() { cart = []; speichere(); renderCart(); } function renderCart() { // FAB var n = cart.length; fab.textContent = '\uD83D\uDCCB ' + LABEL + ' (' + n + ')'; fab.className = 'pl-cart-fab' + (n > 0 ? ' show' : ''); checkoutBtn.disabled = (n === 0); // Liste if (n === 0) { listEl.innerHTML = '
Noch keine Artikel ausgew\u00e4hlt.
'; closePanel(); return; } listEl.innerHTML = cart.map(function(it, idx) { var sub = [it.koern, it.art].filter(Boolean).join(' \u00b7 '); var farbe = it.farbe ? '' + escapeHtml(it.farbe) + '' : ''; return '
' + '
' + '' + escapeHtml(it.bez) + '' + farbe + '
' + '
' + '
' + escapeHtml(sub) + '
' + '
' + '' + '' + '' + '
' + '
' + '
'; }).join(''); } function openPanel() { panel.classList.add('open'); } function closePanel() { panel.classList.remove('open'); } // ---------- Events ---------- fab.addEventListener('click', openPanel); panel.querySelector('.pl-cart-close').addEventListener('click', closePanel); panel.querySelector('.pl-cart-clear').addEventListener('click', function() { if (cart.length && window.confirm('Auswahl wirklich leeren?')) leeren(); }); // "Hinzufügen" pro Zeile (Delegation auf Ergebnisliste) if (resultsDiv) { resultsDiv.addEventListener('click', function(e) { var btn = e.target.closest ? e.target.closest('.pl-add') : null; if (!btn) return; addItem({ kat: btn.getAttribute('data-kat'), bez: btn.getAttribute('data-bez'), farbe: btn.getAttribute('data-farbe'), koern: btn.getAttribute('data-koern'), art: btn.getAttribute('data-art'), katfarbe: btn.getAttribute('data-katfarbe'), menge: 1, einheit: 'Tonnen' }); openPanel(); btn.classList.add('pl-add-flash'); setTimeout(function(){ btn.classList.remove('pl-add-flash'); }, 400); }); } // Menge/Einheit im Panel ändern + entfernen listEl.addEventListener('input', function(e) { var wrap = e.target.closest('.pl-cart-item'); if (!wrap) return; var idx = parseInt(wrap.getAttribute('data-idx'), 10); if (e.target.classList.contains('pl-ci-menge')) { var m = parseFloat((e.target.value || '').replace(',', '.')); if (m > 0) { cart[idx].menge = m; speichere(); } } }); listEl.addEventListener('change', function(e) { var wrap = e.target.closest('.pl-cart-item'); if (!wrap) return; var idx = parseInt(wrap.getAttribute('data-idx'), 10); if (e.target.classList.contains('pl-ci-einheit')) { cart[idx].einheit = e.target.value; speichere(); } }); listEl.addEventListener('click', function(e) { if (!e.target.classList.contains('pl-ci-del')) return; var wrap = e.target.closest('.pl-cart-item'); var idx = parseInt(wrap.getAttribute('data-idx'), 10); cart.splice(idx, 1); speichere(); renderCart(); }); // ---------- Checkout: Warenkorb -> Gravity-Forms-Feld + Formular anzeigen ---------- // Formular ist per CSS ausgeblendet und wird hier per Inline-Style eingeblendet checkoutBtn.addEventListener('click', function() { if (!cart.length) return; var form = findForm(); if (!form) { alert('Das Bestellformular wurde auf dieser Seite nicht gefunden. Bitte stellen Sie sicher, dass das Gravity-Forms-Formular unter der Preisliste eingebunden ist (Shortcode in einem Text-Modul, korrekte Formular-ID).'); return; } // verstecktes Feld füllen var dataField = form.querySelector('.pl-cart-data textarea, .pl-cart-data input'); if (dataField) { dataField.value = cartText(); } // Formular sichtbar machen + Zusammenfassung einfügen form.classList.add('pl-form-visible'); var summaryBox = zeigeSummary(form); closePanel(); (summaryBox || form).scrollIntoView({ block: 'start' }); }); function findForm() { if (BESTELL_FORM_ID) { var byId = document.querySelector('#gform_wrapper_' + BESTELL_FORM_ID); if (byId) return byId; } return document.querySelector('.gform_wrapper, .gform-wrapper, form[id^="gform_"]'); } function cartText() { return cart.map(function(it, i) { return (i + 1) + ') ' + it.bez + (it.farbe ? ' (' + it.farbe + ')' : '') + ' | Körnung/Maß: ' + (it.koern || '-') + ' | Art.-Nr.: ' + (it.art || '-') + ' | Menge: ' + it.menge + ' ' + it.einheit + ' | Kategorie: ' + (it.kat || '-'); }).join('\n'); } function zeigeSummary(form) { var old = document.getElementById('pl-order-summary'); if (old) old.parentNode.removeChild(old); var box = document.createElement('div'); box.id = 'pl-order-summary'; box.className = 'pl-order-summary'; box.innerHTML = '

Ihre ' + cart.length + ' Position(en)

' + '

Wichtiger Hinweis: Wir versenden nur in vollen Verpackungseinheiten. Sollte Ihre angegebene Menge von einer vollen VE abweichen, wird diese von uns erh\u00f6ht. Bei Fragen dazu melden Sie sich gerne!

'; form.parentNode.insertBefore(box, form); return box; } // ---------- Gravity Forms: Datumsgrenze + Warenkorb nach Absenden leeren ---------- var jq = window.jQuery; if (window.gform && gform.addFilter) { gform.addFilter('gform_datepicker_options_pre_init', function(optionsObj, formId) { if (BESTELL_FORM_ID && formId != BESTELL_FORM_ID) return optionsObj; var earliest = werktagePlus(new Date(), VORLAUF_WERKTAGE); optionsObj.minDate = earliest; optionsObj.beforeShowDay = function(d) { return [istWerktag(d), '']; }; return optionsObj; }); } if (jq) { jq(document).on('gform_confirmation_loaded', function(e, formId) { if (BESTELL_FORM_ID && formId != BESTELL_FORM_ID) return; leeren(); var s = document.getElementById('pl-order-summary'); if (s) s.parentNode.removeChild(s); // Bestätigung als Pop-up statt inline var conf = document.querySelector('#gform_confirmation_wrapper_' + formId + ', .gform_confirmation_message'); var msg = conf ? conf.innerHTML : '

Vielen Dank f\u00fcr Ihre ' + LABEL + '. Wir haben Ihre Anfrage erhalten und melden uns in K\u00fcrze bei Ihnen.

'; if (conf) conf.style.display = 'none'; zeigeConfirmPopup(msg); }); } function zeigeConfirmPopup(html) { var old = document.getElementById('pl-confirm-popup'); if (old) old.parentNode.removeChild(old); var ov = document.createElement('div'); ov.id = 'pl-confirm-popup'; ov.innerHTML = '
\u2713
' + html + '
'; document.body.appendChild(ov); function close() { if (ov.parentNode) ov.parentNode.removeChild(ov); } ov.addEventListener('click', function(e) { if (e.target === ov) close(); }); ov.querySelector('.pl-cp-close').addEventListener('click', close); } // ---------- Werktags-/Feiertagslogik (Niedersachsen) ---------- function ostern(year) { var a = year % 19, b = Math.floor(year / 100), c = year % 100; var d = Math.floor(b / 4), e = b % 4, f = Math.floor((b + 8) / 25); var g = Math.floor((b - f + 1) / 3), h = (19 * a + b - d - g + 15) % 30; var i = Math.floor(c / 4), k = c % 4, l = (32 + 2 * e + 2 * i - h - k) % 7; var m = Math.floor((a + 11 * h + 22 * l) / 451); var month = Math.floor((h + l - 7 * m + 114) / 31); var day = ((h + l - 7 * m + 114) % 31) + 1; return new Date(year, month - 1, day); } function feiertage(year) { var o = ostern(year); var set = {}; function add(dt) { set[dt.getFullYear() + '-' + dt.getMonth() + '-' + dt.getDate()] = true; } add(new Date(year, 0, 1)); // Neujahr add(new Date(o.getTime() - 2 * 86400000)); // Karfreitag add(new Date(o.getTime() + 1 * 86400000)); // Ostermontag add(new Date(year, 4, 1)); // Tag der Arbeit add(new Date(o.getTime() + 39 * 86400000)); // Christi Himmelfahrt add(new Date(o.getTime() + 50 * 86400000)); // Pfingstmontag add(new Date(year, 9, 3)); // Tag der Deutschen Einheit add(new Date(year, 9, 31)); // Reformationstag (Niedersachsen) add(new Date(year, 11, 25)); // 1. Weihnachtstag add(new Date(year, 11, 26)); // 2. Weihnachtstag return set; } var ftCache = {}; function istFeiertag(d) { var y = d.getFullYear(); if (!ftCache[y]) ftCache[y] = feiertage(y); return !!ftCache[y][y + '-' + d.getMonth() + '-' + d.getDate()]; } function istWerktag(d) { var wd = d.getDay(); return wd !== 0 && wd !== 6 && !istFeiertag(d); } function werktagePlus(start, n) { var d = new Date(start.getFullYear(), start.getMonth(), start.getDate()); var count = 0; while (count < n) { d.setDate(d.getDate() + 1); if (istWerktag(d)) count++; } return d; } renderCart(); } function setupLightbox() { if (window.__plLightbox) return; window.__plLightbox = true; var style = document.createElement('style'); style.textContent = '.pl-card-img img, .pl-thumb { cursor: zoom-in; }' + '#pl-lightbox { position: fixed; inset: 0; background: rgba(0,0,0,0.85); display: none; align-items: center; justify-content: center; z-index: 99999; padding: 24px; box-sizing: border-box; cursor: zoom-out; }' + '#pl-lightbox.open { display: flex; }' + '#pl-lightbox img { max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 4px; box-shadow: 0 4px 30px rgba(0,0,0,0.5); }' + '#pl-lightbox-close { position: absolute; top: 16px; right: 24px; color: #fff; font-size: 40px; line-height: 1; font-weight: 300; cursor: pointer; background: none; border: none; }'; document.head.appendChild(style); var box = document.createElement('div'); box.id = 'pl-lightbox'; box.innerHTML = ''; document.body.appendChild(box); var boxImg = box.querySelector('img'); function open(src, alt) { boxImg.src = src; boxImg.alt = alt || ''; box.classList.add('open'); } function close() { box.classList.remove('open'); boxImg.src = ''; } var results = document.getElementById('pl-results'); if (results) { results.addEventListener('click', function(e) { var img = e.target; if (img && img.tagName === 'IMG' && img.closest('.pl-card-img, .pl-card-header--thumb')) { e.preventDefault(); open(img.getAttribute('data-full') || img.getAttribute('src'), img.getAttribute('alt')); } }); } box.addEventListener('click', close); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') close(); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();