Demoshop Directus+Astro — Hofladen Heidekorn (Katalog, Warenkorb, Magazin, Rechtstexte)
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user