Demoshop Directus+Astro — Hofladen Heidekorn (Katalog, Warenkorb, Magazin, Rechtstexte)

This commit is contained in:
2026-06-16 03:36:53 +00:00
commit 87fda0927e
21 changed files with 6072 additions and 0 deletions
+181
View File
@@ -0,0 +1,181 @@
---
import '@fontsource-variable/fraunces';
import '@fontsource-variable/public-sans';
const { title = 'Hofladen Heidekorn', desc = 'Regionale Spezialitäten aus der Lüneburger Heide — Demoshop.' } = Astro.props;
---
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title} · Hofladen Heidekorn</title>
<meta name="description" content={desc} />
<meta name="robots" content="noindex" />
<style is:global>
:root{
--petrol: oklch(0.50 0.075 205);
--petrol-deep: oklch(0.38 0.07 212);
--light: oklch(0.975 0.008 200);
--soft: oklch(0.93 0.018 195);
--slate: oklch(0.55 0.02 230);
--graphit: oklch(0.33 0.012 240);
--ink: oklch(0.21 0.012 250);
--weiss: oklch(0.995 0 0);
--line: oklch(0.88 0.012 215);
--radius: 14px;
--wrap: 1140px;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--light);color:var(--ink);
font-family:'Public Sans Variable',system-ui,sans-serif;line-height:1.6;-webkit-font-smoothing:antialiased}
h1,h2,h3{font-family:'Fraunces Variable',Georgia,serif;font-weight:560;line-height:1.12;letter-spacing:-0.01em;margin:0 0 .4em}
a{color:inherit;text-decoration:none}
img{display:block;max-width:100%}
.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:22px}
.u{position:relative;display:inline-block}
.u::after{content:"_";color:var(--petrol);font-weight:700}
.btn{display:inline-flex;align-items:center;gap:.5em;background:var(--petrol);color:var(--weiss);
border:none;border-radius:999px;padding:.7em 1.3em;font:inherit;font-weight:600;cursor:pointer;transition:background .18s,transform .18s}
.btn:hover{background:var(--petrol-deep);transform:translateY(-1px)}
.btn.ghost{background:transparent;color:var(--petrol);border:1.5px solid var(--line)}
.btn.ghost:hover{border-color:var(--petrol);background:transparent}
/* header */
header.site{position:sticky;top:0;z-index:40;background:color-mix(in oklch,var(--light) 86%, transparent);
backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
.nav{display:flex;align-items:center;justify-content:space-between;height:68px}
.brand{font-family:'Fraunces Variable',serif;font-size:1.3rem;font-weight:600}
.brand small{display:block;font-family:'Public Sans Variable',sans-serif;font-size:.62rem;letter-spacing:.18em;
text-transform:uppercase;color:var(--slate);font-weight:600}
.nav-links{display:flex;gap:1.6rem;align-items:center;font-weight:500}
.nav-links a:hover{color:var(--petrol)}
.cart-btn{position:relative;background:none;border:none;cursor:pointer;color:var(--ink);font:inherit;display:flex;align-items:center;gap:.4rem;font-weight:600}
.cart-badge{position:absolute;top:-8px;right:-10px;background:var(--petrol);color:#fff;border-radius:999px;
min-width:18px;height:18px;font-size:.7rem;display:grid;place-items:center;padding:0 4px}
/* drawer */
.drawer-bg{position:fixed;inset:0;background:oklch(0.2 0.02 250/.45);opacity:0;pointer-events:none;transition:opacity .25s;z-index:50}
.drawer-bg.open{opacity:1;pointer-events:auto}
.drawer{position:fixed;top:0;right:0;height:100%;width:min(420px,92vw);background:var(--weiss);z-index:60;
transform:translateX(100%);transition:transform .28s ease;display:flex;flex-direction:column;box-shadow:-12px 0 40px oklch(0.2 0.02 250/.18)}
.drawer.open{transform:none}
.drawer header{display:flex;justify-content:space-between;align-items:center;padding:20px;border-bottom:1px solid var(--line)}
.drawer h3{margin:0;font-size:1.15rem}
.drawer .items{flex:1;overflow:auto;padding:8px 20px}
.ci{display:flex;gap:12px;padding:14px 0;border-bottom:1px solid var(--line)}
.ci img{width:62px;height:62px;object-fit:cover;border-radius:10px;flex:none}
.ci .meta{flex:1;min-width:0}
.ci .nm{font-weight:600;font-size:.92rem}
.ci .qty{display:flex;align-items:center;gap:8px;margin-top:6px}
.ci .qty button{width:26px;height:26px;border:1px solid var(--line);background:var(--light);border-radius:7px;cursor:pointer;font-size:1rem;line-height:1}
.ci .rm{background:none;border:none;color:var(--slate);cursor:pointer;font-size:.78rem;text-decoration:underline}
.drawer footer{padding:18px 20px;border-top:1px solid var(--line)}
.sumline{display:flex;justify-content:space-between;margin-bottom:4px;color:var(--graphit);font-size:.9rem}
.sumline.total{color:var(--ink);font-weight:700;font-size:1.1rem;margin-top:8px}
.empty{color:var(--slate);text-align:center;padding:40px 0}
.close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:var(--slate);line-height:1}
/* footer */
footer.site{margin-top:80px;background:var(--graphit);color:oklch(0.9 0.01 220);padding:54px 0 30px}
footer.site a{color:oklch(0.9 0.01 220)}
footer.site a:hover{color:#fff}
.fgrid{display:grid;grid-template-columns:2fr 1fr 1fr;gap:32px}
footer h4{font-family:'Public Sans Variable',sans-serif;text-transform:uppercase;letter-spacing:.12em;font-size:.72rem;color:oklch(0.72 0.02 220);margin:0 0 12px}
.fcol a{display:block;padding:3px 0;font-size:.92rem}
.fnote{border-top:1px solid oklch(0.45 0.01 240);margin-top:34px;padding-top:18px;font-size:.8rem;color:oklch(0.72 0.02 220);display:flex;justify-content:space-between;flex-wrap:wrap;gap:10px}
.demo-pill{display:inline-block;background:var(--petrol);color:#fff;font-size:.7rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;padding:3px 10px;border-radius:999px}
@media(max-width:760px){.fgrid{grid-template-columns:1fr}.nav-links a:not(.cart-link){display:none}}
.legal{max-width:740px;padding-top:30px;padding-bottom:20px}
.legal h1{font-size:clamp(1.8rem,3.5vw,2.6rem);margin:.3em 0 .6em}
.legal h3{margin:1.4em 0 .3em;font-size:1.15rem}
.legal p{color:var(--graphit);margin:0 0 .9em}
.demo-note{display:inline-block;background:oklch(0.92 0.06 80);color:oklch(0.4 0.08 60);font-size:.8rem;font-weight:600;padding:6px 14px;border-radius:8px;margin-bottom:8px}
.legal .src{margin-top:24px;padding-top:16px;border-top:1px solid var(--line);font-size:.82rem;color:var(--slate)}
</style>
</head>
<body>
<header class="site"><div class="wrap nav">
<a href="/" class="brand">Hofladen Heidekorn<small>Lüneburger Heide</small></a>
<nav class="nav-links">
<a href="/#shop">Shop</a>
<a href="/#magazin">Magazin</a>
<button class="cart-btn" id="cartOpen" aria-label="Warenkorb öffnen">
Warenkorb <span class="cart-badge" id="cartBadge">0</span>
</button>
</nav>
</div></header>
<main><slot /></main>
<div class="drawer-bg" id="drawerBg"></div>
<aside class="drawer" id="drawer" aria-label="Warenkorb">
<header><h3>Dein Warenkorb</h3><button class="close" id="cartClose">×</button></header>
<div class="items" id="cartItems"></div>
<footer>
<div class="sumline"><span>Zwischensumme</span><span id="sumNet">0,00 €</span></div>
<div class="sumline"><span>inkl. MwSt.</span><span id="sumTax">0,00 €</span></div>
<div class="sumline total"><span>Gesamt</span><span id="sumTotal">0,00 €</span></div>
<button class="btn" style="width:100%;justify-content:center;margin-top:14px" id="checkoutBtn">Zur Kasse (Demo)</button>
<p style="font-size:.74rem;color:var(--slate);text-align:center;margin:.7em 0 0">Demoshop · keine echte Bestellung möglich</p>
</footer>
</aside>
<footer class="site"><div class="wrap">
<div class="fgrid">
<div>
<div class="brand" style="color:#fff">Hofladen Heidekorn<small>Lüneburger Heide</small></div>
<p style="max-width:34ch;color:oklch(0.78 0.02 220);font-size:.92rem;margin-top:14px">
Regionale Spezialitäten, direkt vom Hof. Dieser Shop ist ein technischer Demonstrator.</p>
<span class="demo-pill">Demo · Directus + Astro</span>
</div>
<div class="fcol"><h4>Shop</h4>
<a href="/#shop">Alle Produkte</a><a href="/#magazin">Magazin</a><a href="/warenkorb">Warenkorb</a>
</div>
<div class="fcol"><h4>Rechtliches</h4>
<a href="/impressum">Impressum</a><a href="/datenschutz">Datenschutz</a><a href="/agb">AGB</a><a href="/widerruf">Widerruf</a>
</div>
</div>
<div class="fnote"><span>© 2026 Hofladen Heidekorn (Demo) · Heidrich Digital</span><span>Preise inkl. MwSt., zzgl. Versand</span></div>
</div></footer>
<script is:inline>
const KEY='heidekorn_cart';
const load=()=>{try{return JSON.parse(localStorage.getItem(KEY))||[]}catch{return[]}};
const save=c=>localStorage.setItem(KEY,JSON.stringify(c));
const eur=n=>new Intl.NumberFormat('de-DE',{style:'currency',currency:'EUR'}).format(n);
let cart=load();
function add(p){const f=cart.find(i=>i.slug===p.slug);if(f)f.qty++;else cart.push({...p,qty:1});save(cart);render();open()}
function setQty(slug,d){const f=cart.find(i=>i.slug===slug);if(!f)return;f.qty+=d;if(f.qty<=0)cart=cart.filter(i=>i.slug!==slug);save(cart);render()}
function remove(slug){cart=cart.filter(i=>i.slug!==slug);save(cart);render()}
function totals(){let total=0,tax=0;cart.forEach(i=>{const line=i.preis*i.qty;total+=line;const r=(i.mwst||19)/100;tax+=line-(line/(1+r))});return{total,tax,net:total-tax}}
function render(){
const badge=document.getElementById('cartBadge');
const n=cart.reduce((s,i)=>s+i.qty,0);
if(badge)badge.textContent=n;
const wrap=document.getElementById('cartItems');
if(wrap){
if(!cart.length){wrap.innerHTML='<p class="empty">Dein Warenkorb ist leer.</p>';}
else{wrap.innerHTML=cart.map(i=>`<div class="ci"><img src="${i.bild_url}" alt=""><div class="meta"><div class="nm">${i.name}</div><div style="color:var(--slate);font-size:.8rem">${eur(i.preis)} · ${i.einheit||''}</div><div class="qty"><button data-dec="${i.slug}"></button><span>${i.qty}</span><button data-inc="${i.slug}">+</button><button class="rm" data-rm="${i.slug}">entfernen</button></div></div><div style="font-weight:600">${eur(i.preis*i.qty)}</div></div>`).join('');}
}
const t=totals();
const set=(id,v)=>{const e=document.getElementById(id);if(e)e.textContent=eur(v)};
set('sumNet',t.net);set('sumTax',t.tax);set('sumTotal',t.total);
document.dispatchEvent(new CustomEvent('cart:changed',{detail:{cart,totals:t}}));
}
const drawer=document.getElementById('drawer'),bg=document.getElementById('drawerBg');
function open(){drawer&&drawer.classList.add('open');bg&&bg.classList.add('open')}
function close(){drawer&&drawer.classList.remove('open');bg&&bg.classList.remove('open')}
document.getElementById('cartOpen')?.addEventListener('click',open);
document.getElementById('cartClose')?.addEventListener('click',close);
bg?.addEventListener('click',close);
document.getElementById('checkoutBtn')?.addEventListener('click',()=>{alert('Demoshop — hier würde der Checkout starten. Es findet keine echte Bestellung statt.')});
document.addEventListener('click',e=>{
const a=e.target.closest('[data-add]');
if(a){e.preventDefault();add(JSON.parse(a.getAttribute('data-add')));return}
const inc=e.target.closest('[data-inc]');if(inc){setQty(inc.getAttribute('data-inc'),1);return}
const dec=e.target.closest('[data-dec]');if(dec){setQty(dec.getAttribute('data-dec'),-1);return}
const rm=e.target.closest('[data-rm]');if(rm){remove(rm.getAttribute('data-rm'));return}
});
window.HK={add,setQty,remove,get:()=>cart,totals};
render();
</script>
</body>
</html>