hd-commerce: neutrales SQLite-Commerce-Backend (Admin + API + Demo-Storefront)

This commit is contained in:
2026-06-17 12:05:29 +00:00
commit 4e8a3ab105
43 changed files with 2689 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
/* hd-commerce — Popup-Engine (vanilla). Frequenz-Cap via localStorage. */
(function () {
var root = document.getElementById('popupRoot');
if (!root) return;
var popups;
try { popups = JSON.parse(root.getAttribute('data-popups') || '[]'); } catch (e) { return; }
if (!popups || !popups.length) return;
function seen(p) {
var k = 'hdc_popup_' + p.id;
var v = localStorage.getItem(k);
if (!v) return false;
if (p.freq === 'always') return false;
if (p.freq === 'session') return sessionStorage.getItem('hdc_ps_' + p.id) === '1';
if (p.freq === 'days7') { return (Date.now() - parseInt(v, 10)) < 7 * 864e5; }
return true;
}
function mark(p) {
localStorage.setItem('hdc_popup_' + p.id, String(Date.now()));
if (p.freq === 'session') sessionStorage.setItem('hdc_ps_' + p.id, '1');
}
function build(p) {
var ov = document.createElement('div');
ov.className = 'hdc-popup-overlay';
var isNl = p.type === 'newsletter';
ov.innerHTML =
'<div class="hdc-popup">' +
'<button class="px" aria-label="Schließen">&times;</button>' +
(p.image ? '<img src="' + p.image + '" alt="" style="border-radius:10px;margin-bottom:16px;max-height:160px;width:100%;object-fit:cover">' : '') +
'<h3>' + (p.headline || '') + '</h3>' +
'<p>' + (p.body || '') + '</p>' +
(isNl
? '<form class="nl-form"><input type="email" required placeholder="deine@email.de"><button class="btn btn-primary btn-block" type="submit">' + (p.cta_text || 'Anmelden') + '</button><div class="nl-msg"></div></form>'
: (p.cta_url ? '<a class="btn btn-primary btn-lg" href="' + p.cta_url + '">' + (p.cta_text || 'Mehr') + '</a>' : '')) +
'</div>';
document.body.appendChild(ov);
requestAnimationFrame(function () { ov.classList.add('show'); });
function close() { ov.classList.remove('show'); mark(p); setTimeout(function () { ov.remove(); }, 320); }
ov.querySelector('.px').addEventListener('click', close);
ov.addEventListener('click', function (e) { if (e.target === ov) close(); });
var form = ov.querySelector('form');
if (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
var email = form.querySelector('input').value;
var msg = form.querySelector('.nl-msg');
fetch('/api/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email, source: 'popup' }) })
.then(function (r) { return r.json(); })
.then(function () { msg.textContent = 'Danke! Schau in dein Postfach.'; setTimeout(close, 1600); })
.catch(function () { msg.textContent = 'Bitte später erneut versuchen.'; });
});
}
}
function arm(p) {
if (seen(p)) return;
var trig = p.trigger || 'delay';
var val = parseInt(p.trigger_value, 10) || 0;
if (trig === 'delay') { setTimeout(function () { build(p); }, Math.max(0, val) * 1000); }
else if (trig === 'scroll') {
var fn = function () {
var sc = (window.scrollY + window.innerHeight) / document.body.scrollHeight * 100;
if (sc >= (val || 50)) { window.removeEventListener('scroll', fn); build(p); }
};
window.addEventListener('scroll', fn, { passive: true });
} else if (trig === 'exit') {
var fired = false;
document.addEventListener('mouseout', function (e) {
if (!fired && e.clientY <= 0 && !e.relatedTarget) { fired = true; build(p); }
});
setTimeout(function () { if (!fired) { fired = true; build(p); } }, 25000); // mobile-fallback
}
}
popups.forEach(arm);
})();