hd-commerce: neutrales SQLite-Commerce-Backend (Admin + API + Demo-Storefront)
This commit is contained in:
@@ -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">×</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);
|
||||
})();
|
||||
@@ -0,0 +1,62 @@
|
||||
/* hd-commerce — Storefront Cart (vanilla, localStorage) */
|
||||
(function () {
|
||||
var KEY = 'hdc_cart';
|
||||
function read() { try { return JSON.parse(localStorage.getItem(KEY) || '[]'); } catch (e) { return []; } }
|
||||
function write(c) { localStorage.setItem(KEY, JSON.stringify(c)); updateBadge(); }
|
||||
function count() { return read().reduce(function (s, i) { return s + (i.qty || 1); }, 0); }
|
||||
function updateBadge() {
|
||||
var b = document.getElementById('cartBadge');
|
||||
if (!b) return;
|
||||
var n = count();
|
||||
b.textContent = n;
|
||||
b.classList.toggle('show', n > 0);
|
||||
}
|
||||
function track(type, value, meta) {
|
||||
try {
|
||||
fetch('/api/track', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: type, path: location.pathname, value_cents: value || 0, meta: meta || {} }) });
|
||||
} catch (e) {}
|
||||
}
|
||||
function add(item) {
|
||||
var c = read();
|
||||
var ex = c.find(function (i) { return i.slug === item.slug && i.size === item.size; });
|
||||
if (ex) ex.qty += item.qty || 1; else c.push(item);
|
||||
write(c);
|
||||
track('add_to_cart', (item.priceCents || 0) * (item.qty || 1), { slug: item.slug });
|
||||
toast(item.name + ' wurde hinzugefügt');
|
||||
}
|
||||
function toast(msg) {
|
||||
var t = document.createElement('div');
|
||||
t.textContent = msg;
|
||||
t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--ink);color:#fff;padding:12px 22px;border-radius:999px;font-size:14px;font-weight:600;z-index:200;box-shadow:0 8px 30px rgba(0,0,0,.2);transition:.3s;opacity:0';
|
||||
document.body.appendChild(t);
|
||||
requestAnimationFrame(function () { t.style.opacity = '1'; });
|
||||
setTimeout(function () { t.style.opacity = '0'; setTimeout(function () { t.remove(); }, 320); }, 2000);
|
||||
}
|
||||
window.HDC = {
|
||||
read: read, write: write, count: count, add: add, track: track,
|
||||
remove: function (idx) { var c = read(); c.splice(idx, 1); write(c); },
|
||||
setQty: function (idx, q) { var c = read(); if (c[idx]) { c[idx].qty = Math.max(1, q); write(c); } },
|
||||
clear: function () { write([]); },
|
||||
subtotal: function () { return read().reduce(function (s, i) { return s + (i.priceCents || 0) * (i.qty || 1); }, 0); }
|
||||
};
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
updateBadge();
|
||||
document.querySelectorAll('[data-add-to-cart]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var p = JSON.parse(btn.getAttribute('data-product') || '{}');
|
||||
var sizeSel = document.querySelector('.size-chip.active');
|
||||
var size = sizeSel ? sizeSel.getAttribute('data-size') : (p.sizes && p.sizes[0]) || 'One Size';
|
||||
add({ slug: p.slug, name: p.name, size: size, priceCents: p.priceCents, image: p.image, qty: 1 });
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('.size-chip').forEach(function (chip) {
|
||||
chip.addEventListener('click', function () {
|
||||
document.querySelectorAll('.size-chip').forEach(function (c) { c.classList.remove('active'); });
|
||||
chip.classList.add('active');
|
||||
});
|
||||
});
|
||||
var pv = document.getElementById('pdpData');
|
||||
if (pv) track('product_view', 0, { slug: pv.getAttribute('data-slug') });
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,16 @@
|
||||
(function () {
|
||||
var track = document.getElementById('hdcSlides');
|
||||
if (!track) return;
|
||||
var slides = track.children, n = slides.length, i = 0;
|
||||
var dots = document.querySelectorAll('#hdcDots button');
|
||||
function go(x) {
|
||||
i = (x + n) % n;
|
||||
track.style.transform = 'translateX(-' + (i * 100) + '%)';
|
||||
dots.forEach(function (d, k) { d.classList.toggle('active', k === i); });
|
||||
}
|
||||
var prev = document.getElementById('hdcPrev'), next = document.getElementById('hdcNext');
|
||||
if (prev) prev.addEventListener('click', function () { go(i - 1); });
|
||||
if (next) next.addEventListener('click', function () { go(i + 1); });
|
||||
dots.forEach(function (d) { d.addEventListener('click', function () { go(+d.getAttribute('data-idx')); }); });
|
||||
if (n > 1) setInterval(function () { go(i + 1); }, 6000);
|
||||
})();
|
||||
Reference in New Issue
Block a user