@@ -0,0 +1,497 @@
<!DOCTYPE html>
< html lang = "de" >
< head >
< meta charset = "UTF-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
< title > Bella Napoli — Pizzeria | Online bestellen< / title >
< meta name = "description" content = "Echte neapolitanische Pizza aus dem Holzofen. Online bestellen — Abholung oder Lieferung, ohne Telefonwarteschleife." / >
< link rel = "preconnect" href = "https://fonts.googleapis.com" / >
< link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin / >
< link href = "https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600;700&display=swap" rel = "stylesheet" / >
< style >
: root {
--bg : #fbf6f0 ;
--surface : #ffffff ;
--ink : #2a1812 ;
--muted : #8a6f63 ;
--red : #d6453a ;
--red-2 : #b5342b ;
--red-soft : #fdece9 ;
--green : #3f7d4f ;
--sand : #f5ece1 ;
--line : #efe1d6 ;
--radius : 16 px ;
--shadow : 0 16 px 44 px rgba ( 60 , 24 , 16 , .12 ) ;
}
* { box-sizing : border-box ; margin : 0 ; padding : 0 ; }
html { scroll-behavior : smooth ; }
body { font-family : 'Inter' , system-ui , - apple-system , Segoe UI , Roboto , sans-serif ; background : var ( - - bg ) ; color : var ( - - ink ) ; line-height : 1.65 ; -webkit- font-smoothing : antialiased ; }
h1 , h2 , h3 { font-family : 'Fraunces' , Georgia , serif ; line-height : 1.12 ; font-weight : 600 ; }
a { color : inherit ; text-decoration : none ; }
. wrap { width : min ( 1120 px , 92 vw ) ; margin-inline : auto ; }
section { padding : 84 px 0 ; }
. eyebrow { color : var ( - - red ) ; font-weight : 600 ; letter-spacing : 2 px ; text-transform : uppercase ; font-size : .72 rem ; }
/* demo ribbon */
. demo-bar { background : #3a140f ; color : #ffcfc6 ; font-size : .76 rem ; letter-spacing : 1 px ; text-align : center ; padding : 7 px 12 px ; }
/* nav */
header . nav { position : sticky ; top : 0 ; z-index : 50 ; background : rgba ( 251 , 246 , 240 , .88 ) ; backdrop-filter : blur ( 10 px ) ; border-bottom : 1 px solid var ( - - line ) ; }
. nav-inner { display : flex ; align-items : center ; justify-content : space-between ; height : 72 px ; gap : 16 px ; }
. brand { display : flex ; align-items : center ; gap : 9 px ; font-family : 'Fraunces' , serif ; font-size : 1.4 rem ; font-weight : 600 ; }
. brand . logo { width : 32 px ; height : 32 px ; border-radius : 9 px ; background : linear-gradient ( 135 deg , var ( - - red ) , var ( - - red -2 ) ) ; display : grid ; place-items : center ; color : #fff ; font-size : 1.05 rem ; }
nav ul { display : flex ; gap : 26 px ; list-style : none ; align-items : center ; }
nav a { color : var ( - - muted ) ; font-size : .92 rem ; font-weight : 500 ; transition : color .2 s ; }
nav a : hover { color : var ( - - ink ) ; }
. btn { display : inline-block ; background : var ( - - red ) ; color : #fff ; font-weight : 600 ; padding : 12 px 22 px ; border-radius : 999 px ; border : 0 ; cursor : pointer ; font-size : .95 rem ; transition : transform .15 s , background .2 s ; }
. btn : hover { transform : translateY ( -2 px ) ; background : var ( - - red -2 ) ; }
. btn . ghost { background : #fff ; color : var ( - - red -2 ) ; border : 1 px solid var ( - - line ) ; }
. cart-pill { display : inline-flex ; align-items : center ; gap : 7 px ; background : var ( - - red - soft ) ; color : var ( - - red -2 ) ; border : 1 px solid #f4cfc8 ; font-weight : 700 ; padding : 9 px 15 px ; border-radius : 999 px ; font-size : .9 rem ; cursor : pointer ; }
. cart-pill . badge { background : var ( - - red ) ; color : #fff ; border-radius : 999 px ; min-width : 20 px ; height : 20 px ; display : grid ; place-items : center ; font-size : .72 rem ; padding : 0 5 px ; }
. nav-toggle { display : none ; background : none ; border : 0 ; color : var ( - - ink ) ; font-size : 1.6 rem ; cursor : pointer ; }
/* hero */
. hero { background : linear-gradient ( 180 deg , #fff 0 % , var ( - - bg ) 100 % ) ; border-bottom : 1 px solid var ( - - line ) ; }
. hero-grid { display : grid ; grid-template-columns : 1.1 fr .9 fr ; gap : 50 px ; align-items : center ; padding : 74 px 0 82 px ; }
. hero h1 { font-size : clamp ( 2.5 rem , 5.4 vw , 3.9 rem ) ; margin : 16 px 0 0 ; }
. hero h1 em { font-style : italic ; color : var ( - - red -2 ) ; }
. hero p { color : var ( - - muted ) ; font-size : 1.12 rem ; margin : 18 px 0 28 px ; max-width : 480 px ; }
. hero-cta { display : flex ; gap : 14 px ; flex-wrap : wrap ; }
. trust-row { margin-top : 30 px ; display : flex ; gap : 22 px ; flex-wrap : wrap ; align-items : center ; color : var ( - - muted ) ; font-size : .88 rem ; }
. pill { background : var ( - - red - soft ) ; color : var ( - - red -2 ) ; padding : 6 px 13 px ; border-radius : 999 px ; font-weight : 600 ; font-size : .82 rem ; }
. hero-card { background : var ( - - surface ) ; border : 1 px solid var ( - - line ) ; border-radius : 22 px ; box-shadow : var ( - - shadow ) ; padding : 26 px ; }
. hero-card . pizza { font-size : 4.6 rem ; text-align : center ; line-height : 1 ; margin : 4 px 0 10 px ; }
. hero-card h3 { font-family : 'Fraunces' , serif ; font-size : 1.3 rem ; text-align : center ; }
. hero-card . row { display : flex ; justify-content : space-between ; align-items : center ; margin-top : 14 px ; font-size : .92 rem ; color : var ( - - muted ) ; border-top : 1 px solid var ( - - line ) ; padding-top : 12 px ; }
. hero-card . row b { color : var ( - - ink ) ; }
/* menu */
. sec-head { max-width : 640 px ; margin-bottom : 40 px ; }
. sec-head h2 { font-size : clamp ( 2 rem , 4.6 vw , 2.9 rem ) ; margin-top : 8 px ; }
. sec-head p { color : var ( - - muted ) ; margin-top : 10 px ; }
. menu-grid { display : grid ; grid-template-columns : repeat ( auto - fit , minmax ( 280 px , 1 fr ) ) ; gap : 16 px ; }
. item { background : var ( - - surface ) ; border : 1 px solid var ( - - line ) ; border-radius : var ( - - radius ) ; padding : 20 px 22 px ; display : flex ; flex-direction : column ; transition : transform .18 s , box-shadow .2 s ; }
. item : hover { transform : translateY ( -3 px ) ; box-shadow : var ( - - shadow ) ; }
. item . top { display : flex ; align-items : flex-start ; gap : 12 px ; }
. item . emoji { font-size : 1.8 rem ; line-height : 1 ; }
. item h3 { font-size : 1.18 rem ; margin-bottom : 2 px ; }
. item . desc { color : var ( - - muted ) ; font-size : .86 rem ; }
. item . foot { display : flex ; align-items : center ; justify-content : space-between ; margin-top : 16 px ; }
. item . price { font-weight : 700 ; font-size : 1.1 rem ; }
. add { background : var ( - - red - soft ) ; color : var ( - - red -2 ) ; border : 1 px solid #f4cfc8 ; font-weight : 700 ; padding : 8 px 15 px ; border-radius : 999 px ; cursor : pointer ; font-size : .88 rem ; transition : .15 s ; }
. add : hover { background : var ( - - red ) ; color : #fff ; border-color : var ( - - red ) ; }
. tagv { display : inline-block ; margin-top : 8 px ; font-size : .7 rem ; font-weight : 700 ; letter-spacing : 1 px ; text-transform : uppercase ; color : var ( - - green ) ; }
/* order */
. order { background : var ( - - sand ) ; border-top : 1 px solid var ( - - line ) ; border-bottom : 1 px solid var ( - - line ) ; }
. order-grid { display : grid ; grid-template-columns : 1 fr .95 fr ; gap : 34 px ; align-items : start ; }
. cart { background : var ( - - surface ) ; border : 1 px solid var ( - - line ) ; border-radius : var ( - - radius ) ; box-shadow : var ( - - shadow ) ; overflow : hidden ; position : sticky ; top : 90 px ; }
. cart-head { display : flex ; align-items : center ; justify-content : space-between ; padding : 16 px 20 px ; border-bottom : 1 px solid var ( - - line ) ; font-weight : 700 ; }
. cart-items { padding : 8 px 20 px ; max-height : 300 px ; overflow : auto ; }
. cart-empty { color : var ( - - muted ) ; font-size : .9 rem ; padding : 22 px 0 ; text-align : center ; }
. ci { display : flex ; align-items : center ; gap : 10 px ; padding : 12 px 0 ; border-bottom : 1 px solid var ( - - line ) ; }
. ci : last-child { border-bottom : 0 ; }
. ci . name { flex : 1 ; font-size : .92 rem ; }
. ci . name small { display : block ; color : var ( - - muted ) ; font-size : .78 rem ; }
. qty { display : flex ; align-items : center ; gap : 8 px ; }
. qty button { width : 26 px ; height : 26 px ; border-radius : 50 % ; border : 1 px solid var ( - - line ) ; background : #fff ; cursor : pointer ; font-size : 1 rem ; line-height : 1 ; color : var ( - - ink ) ; }
. qty button : hover { border-color : var ( - - red ) ; color : var ( - - red ) ; }
. qty span { min-width : 18 px ; text-align : center ; font-weight : 700 ; font-size : .9 rem ; }
. ci . lp { width : 64 px ; text-align : right ; font-weight : 600 ; font-size : .9 rem ; }
. cart-total { display : flex ; justify-content : space-between ; align-items : center ; padding : 16 px 20 px ; border-top : 1 px solid var ( - - line ) ; font-size : 1.15 rem ; font-weight : 700 ; }
. cart-foot { padding : 0 20 px 16 px ; color : var ( - - muted ) ; font-size : .74 rem ; }
form . order-form { background : var ( - - surface ) ; border : 1 px solid var ( - - line ) ; border-radius : var ( - - radius ) ; padding : 26 px ; box-shadow : var ( - - shadow ) ; }
. field { margin-bottom : 15 px ; }
. field label { display : block ; font-size : .85 rem ; color : var ( - - muted ) ; margin-bottom : 6 px ; }
. field input , . field textarea , . field select { width : 100 % ; background : #fdfaf7 ; border : 1 px solid var ( - - line ) ; border-radius : 10 px ; color : var ( - - ink ) ; padding : 12 px 14 px ; font : inherit ; font-size : .95 rem ; }
. field input : focus , . field textarea : focus , . field select : focus { outline : 2 px solid var ( - - red ) ; border-color : var ( - - red ) ; }
. seg { display : flex ; gap : 10 px ; }
. seg label { flex : 1 ; cursor : pointer ; border : 1 px solid var ( - - line ) ; border-radius : 10 px ; padding : 11 px ; text-align : center ; background : #fdfaf7 ; font-size : .9 rem ; font-weight : 600 ; color : var ( - - ink ) ; margin : 0 ; transition : .15 s ; }
. seg input { display : none ; }
. seg label : has ( input : checked ) { border-color : var ( - - red ) ; background : var ( - - red - soft ) ; color : var ( - - red -2 ) ; }
. two { display : grid ; grid-template-columns : 1 fr 1 fr ; gap : 12 px ; }
. form-note { font-size : .78 rem ; color : var ( - - muted ) ; margin-top : 10 px ; }
. toast { display : none ; margin-top : 14 px ; padding : 12 px 14 px ; border-radius : 10 px ; background : var ( - - red - soft ) ; border : 1 px solid var ( - - red ) ; color : var ( - - red -2 ) ; font-size : .9 rem ; }
. toast . err { background : #fdecec ; border-color : #e0533a ; color : #9b2226 ; }
/* footer */
footer { padding : 54 px 0 40 px ; border-top : 1 px solid var ( - - line ) ; color : var ( - - muted ) ; font-size : .9 rem ; background : var ( - - surface ) ; }
. foot-grid { display : flex ; justify-content : space-between ; flex-wrap : wrap ; gap : 24 px ; }
@ media ( max-width : 820px ) {
nav ul { display : none ; }
. nav-toggle { display : block ; }
nav . open ul { display : flex ; position : absolute ; inset : 72 px 0 auto 0 ; flex-direction : column ; background : var ( - - surface ) ; padding : 18 px 6 vw ; gap : 18 px ; border-bottom : 1 px solid var ( - - line ) ; }
. hero-grid , . order-grid { grid-template-columns : 1 fr ; }
. cart { position : static ; }
section { padding : 60 px 0 ; }
}
< / style >
< / head >
< body >
< div class = "demo-bar" > Demo · Pizzeria mit Online-Bestellung — Beispielseite für Gäste < button class = "cb-open" type = "button" onclick = "cbOpen()" > 📞 Rückruf anfordern< / button > < / div >
< header class = "nav" >
< div class = "wrap nav-inner" >
< a href = "#top" class = "brand" > < span class = "logo" > 🍕< / span > Bella Napoli< / a >
< nav id = "nav" >
< ul >
< li > < a href = "#speisekarte" > Speisekarte< / a > < / li >
< li > < a href = "#bestellen" > Bestellen< / a > < / li >
< li > < a href = "#kontakt" > Kontakt< / a > < / li >
< li > < a href = "#bestellen" class = "cart-pill" onclick = "scrollCart()" > 🛒 Warenkorb < span class = "badge" id = "navCount" > 0< / span > < / a > < / li >
< / ul >
< / nav >
< button class = "nav-toggle" aria-label = "Menü" onclick = "document.getElementById('nav').classList.toggle('open')" > ☰< / button >
< / div >
< / header >
< main id = "top" >
<!-- HERO -->
< section class = "hero" >
< div class = "wrap hero-grid" >
< div >
< span class = "eyebrow" > Pizzeria · Holzofen · Nürnberg< / span >
< h1 > Echte Pizza.< br > < em > Online bestellt.< / em > < / h1 >
< p > Neapolitanischer Teig, 48 Stunden gereift, aus dem Holzofen. Stell dir deine Bestellung zusammen — Abholung oder Lieferung, ganz ohne Telefon-Warteschleife.< / p >
< div class = "hero-cta" >
< a href = "#speisekarte" class = "btn" > Jetzt bestellen< / a >
< a href = "#kontakt" class = "btn ghost" > Öffnungszeiten< / a >
< / div >
< div class = "trust-row" >
< span class = "pill" > 🔥 Aus dem Holzofen< / span >
< span > 🚴 Lieferung in 30– 40 Min< / span >
< span > ⭐ 4,8 / 5< / span >
< / div >
< / div >
< aside class = "hero-card" >
< div class = "pizza" > 🍕< / div >
< h3 > Pizza der Woche< / h3 >
< p style = "text-align:center;color:var(--muted);font-size:.9rem;margin-top:6px;" > Diavola — scharfe Salami, Chili, frischer Basilikum< / p >
< div class = "row" > < span > Heute frisch gebacken< / span > < b > 10,90 €< / b > < / div >
< button class = "btn" style = "width:100%;margin-top:16px;" onclick = "addToCart('diavola'); scrollCart();" > + In den Warenkorb< / button >
< / aside >
< / div >
< / section >
<!-- MENU -->
< section id = "speisekarte" >
< div class = "wrap" >
< div class = "sec-head" >
< span class = "eyebrow" > Speisekarte< / span >
< h2 > Unsere Pizzen< / h2 >
< p > Alle Pizzen 30 cm, auf hausgemachtem Sauerteig. Tippe auf „Hinzufügen“ — dein Warenkorb füllt sich unten.< / p >
< / div >
< div class = "menu-grid" id = "menuGrid" > <!-- items injected by JS --> < / div >
< / div >
< / section >
<!-- ORDER -->
< section class = "order" id = "bestellen" >
< div class = "wrap" >
< div class = "sec-head" >
< span class = "eyebrow" > Bestellen< / span >
< h2 > Deine Bestellung< / h2 >
< p > Prüf deinen Warenkorb, wähl Abholung oder Lieferung — wir bestätigen direkt.< / p >
< / div >
< div class = "order-grid" >
<!-- Demo: dieses Formular postet die Bestellung an einen n8n - Webhook - > CRM (Leads/Bestellungen) + Telegram. -->
< form class = "order-form" onsubmit = "return submitOrder(event, this)" >
< div class = "field two" >
< div > < label for = "o-name" > Name< / label > < input id = "o-name" name = "name" type = "text" placeholder = "Luca Rossi" required / > < / div >
< div > < label for = "o-phone" > Telefon< / label > < input id = "o-phone" name = "phone" type = "tel" inputmode = "tel" placeholder = "+49 …" required / > < / div >
< / div >
< div class = "field" >
< label > Abholung oder Lieferung?< / label >
< div class = "seg" >
< label > < input type = "radio" name = "mode" value = "Abholung" checked onchange = "toggleAddr()" / > 🏠 Abholung< / label >
< label > < input type = "radio" name = "mode" value = "Lieferung" onchange = "toggleAddr()" / > 🚴 Lieferung< / label >
< / div >
< / div >
< div class = "field" id = "addrField" style = "display:none;" >
< label for = "o-addr" > Lieferadresse< / label >
< input id = "o-addr" name = "address" type = "text" placeholder = "Straße, Hausnr., PLZ Ort" / >
< / div >
< div class = "field" >
< label for = "o-time" > Wunschzeit < span style = "opacity:.6" > (optional)< / span > < / label >
< input id = "o-time" name = "time" type = "text" placeholder = "z. B. so schnell wie möglich / 19:30" / >
< / div >
< div class = "field" >
< label for = "o-note" > Anmerkung < span style = "opacity:.6" > (optional)< / span > < / label >
< textarea id = "o-note" name = "note" rows = "2" placeholder = "Klingel defekt, bitte anrufen · ohne Zwiebeln …" > < / textarea >
< / div >
< button class = "btn" type = "submit" style = "width:100%;" > Bestellung abschicken< / button >
< p class = "form-note" > Demo-Vorschau. In der Live-Version läuft die Bestellung direkt ins Bestell-Management der Pizzeria + sofortige Benachrichtigung.< / p >
< div class = "toast" id = "orderToast" > < / div >
< / form >
< aside class = "cart" id = "cartBox" >
< div class = "cart-head" > < span > 🛒 Warenkorb< / span > < span id = "cartCount" > 0 Artikel< / span > < / div >
< div class = "cart-items" id = "cartItems" >
< div class = "cart-empty" id = "cartEmpty" > Noch nichts ausgewählt — wähl oben deine Pizza.< / div >
< / div >
< div class = "cart-total" > < span > Summe< / span > < span id = "cartTotal" > 0,00 €< / span > < / div >
< div class = "cart-foot" > Lieferung ab 15 € · Mindestbestellwert nur bei Lieferung · Preise inkl. MwSt.< / div >
< / aside >
< / div >
< / div >
< / section >
<!-- CONTACT -->
< section id = "kontakt" >
< div class = "wrap" >
< div class = "sec-head" >
< span class = "eyebrow" > Kontakt & Öffnungszeiten< / span >
< h2 > Bella Napoli< / h2 >
< p > Frisch gebacken, Di– So. Montags Ruhetag.< / p >
< / div >
< div class = "foot-grid" style = "color:var(--muted)" >
< div > < p > < b style = "color:var(--ink)" > Adresse< / b > < / p > < p > Weinmarkt 7 · 90403 Nürnberg< / p > < / div >
< div > < p > < b style = "color:var(--ink)" > Öffnungszeiten< / b > < / p > < p > Di– So 11:30– 22:00 · Mo Ruhetag< / p > < / div >
< div > < p > < b style = "color:var(--ink)" > Telefon< / b > < / p > < p > 0911 000000 · ciao@bellanapoli-nbg.de< / p > < / div >
< / div >
< / div >
< / section >
< / main >
< footer >
< div class = "wrap foot-grid" >
< div >
< div class = "brand" style = "margin-bottom:8px;" > < span class = "logo" > 🍕< / span > Bella Napoli< / div >
< p > Weinmarkt 7 · 90403 Nürnberg< / p >
< / div >
< div > < p > < b style = "color:var(--ink)" > Bestellen< / b > < / p > < p > Online · Abholung & Lieferung< / p > < / div >
< div > < p > < b style = "color:var(--ink)" > Folge uns< / b > < / p > < p > Instagram · Facebook< / p > < / div >
< / div >
< div class = "wrap" style = "margin-top:30px;opacity:.7;font-size:.8rem;" > © 2026 Bella Napoli · Demo-Seite · Impressum · Datenschutz< / div >
< / footer >
< script >
// ===== Menü & Warenkorb =====
const MENU = [
{ id : 'margherita' , emoji : '🍕' , name : 'Margherita' , desc : 'Tomate, Mozzarella, Basilikum' , price : 8.50 , veg : true } ,
{ id : 'salami' , emoji : '🍕' , name : 'Salami' , desc : 'Tomate, Mozzarella, italienische Salami' , price : 9.50 } ,
{ id : 'funghi' , emoji : '🍄' , name : 'Prosciutto e Funghi' , desc : 'Schinken, frische Champignons, Mozzarella' , price : 10.50 } ,
{ id : 'diavola' , emoji : '🌶️' , name : 'Diavola' , desc : 'Scharfe Salami, Chili, Basilikum' , price : 10.90 } ,
{ id : 'formaggi' , emoji : '🧀' , name : 'Quattro Formaggi' , desc : 'Vier Käse: Mozzarella, Gorgonzola, Parmesan, Provolone' , price : 11.50 , veg : true } ,
{ id : 'vegetariale' , emoji : '🥦' , name : 'Vegetariale' , desc : 'Grillgemüse, Rucola, Cherrytomaten' , price : 10.50 , veg : true } ,
{ id : 'tonno' , emoji : '🐟' , name : 'Tonno e Cipolla' , desc : 'Thunfisch, rote Zwiebeln, Kapern' , price : 11.00 } ,
{ id : 'calzone' , emoji : '🥟' , name : 'Calzone' , desc : 'Gefüllte Teigtasche: Schinken, Pilze, Ricotta' , price : 11.50 } ,
] ;
const byId = Object . fromEntries ( MENU . map ( m => [ m . id , m ] ) ) ;
const cart = { } ; // id -> qty
function eur ( n ) { return n . toFixed ( 2 ) . replace ( '.' , ',' ) + ' €' ; }
function renderMenu ( ) {
document . getElementById ( 'menuGrid' ) . innerHTML = MENU . map ( m => `
<article class="item">
<div class="top">
<div class="emoji"> ${ m . emoji } </div>
<div>
<h3> ${ m . name } </h3>
<div class="desc"> ${ m . desc } </div>
${ m . veg ? '<span class="tagv">🌱 Vegetarisch</span>' : '' }
</div>
</div>
<div class="foot">
<span class="price"> ${ eur ( m . price ) } </span>
<button class="add" type="button" onclick="addToCart(' ${ m . id } ')">+ Hinzufügen</button>
</div>
</article> ` ) . join ( '' ) ;
}
function addToCart ( id ) { cart [ id ] = ( cart [ id ] || 0 ) + 1 ; renderCart ( ) ; }
function changeQty ( id , d ) {
cart [ id ] = ( cart [ id ] || 0 ) + d ;
if ( cart [ id ] <= 0 ) delete cart [ id ] ;
renderCart ( ) ;
}
function cartCount ( ) { return Object . values ( cart ) . reduce ( ( a , b ) => a + b , 0 ) ; }
function cartTotal ( ) { return Object . entries ( cart ) . reduce ( ( s , [ id , q ] ) => s + byId [ id ] . price * q , 0 ) ; }
function renderCart ( ) {
const n = cartCount ( ) ;
document . getElementById ( 'navCount' ) . textContent = n ;
document . getElementById ( 'cartCount' ) . textContent = n + ( n === 1 ? ' Artikel' : ' Artikel' ) ;
document . getElementById ( 'cartTotal' ) . textContent = eur ( cartTotal ( ) ) ;
const box = document . getElementById ( 'cartItems' ) ;
const ids = Object . keys ( cart ) ;
if ( ! ids . length ) { box . innerHTML = '<div class="cart-empty">Noch nichts ausgewählt — wähl oben deine Pizza.</div>' ; return ; }
box . innerHTML = ids . map ( id => {
const m = byId [ id ] , q = cart [ id ] ;
return ` <div class="ci">
<div class="name"> ${ m . emoji } ${ m . name } <small> ${ eur ( m . price ) } / Stück</small></div>
<div class="qty">
<button type="button" onclick="changeQty(' ${ id } ',-1)" aria-label="weniger">− </button>
<span> ${ q } </span>
<button type="button" onclick="changeQty(' ${ id } ',1)" aria-label="mehr">+</button>
</div>
<div class="lp"> ${ eur ( m . price * q ) } </div>
</div> ` ;
} ) . join ( '' ) ;
}
function scrollCart ( ) { document . getElementById ( 'bestellen' ) . scrollIntoView ( { behavior : 'smooth' } ) ; }
function toggleAddr ( ) {
const del = document . querySelector ( 'input[name=mode]:checked' ) . value === 'Lieferung' ;
const f = document . getElementById ( 'addrField' ) ;
f . style . display = del ? 'block' : 'none' ;
document . getElementById ( 'o-addr' ) . required = del ;
}
// ===== Bestellung absenden -> n8n Webhook (Leads/Bestellungen) =====
const ORDER _WEBHOOK = 'https://n8n.mivanchenko.de/webhook/lead-intake' ;
async function submitOrder ( e , form ) {
e . preventDefault ( ) ;
const toast = document . getElementById ( 'orderToast' ) ;
if ( ! cartCount ( ) ) {
toast . className = 'toast err' ; toast . textContent = '⚠ Dein Warenkorb ist noch leer.' ; toast . style . display = 'block' ;
return false ;
}
const d = Object . fromEntries ( new FormData ( form ) . entries ( ) ) ;
const lines = Object . entries ( cart ) . map ( ( [ id , q ] ) => ` ${ q } × ${ byId [ id ] . name } ( ${ eur ( byId [ id ] . price * q ) } ) ` ) . join ( ', ' ) ;
const summary = ` Bestellung ( ${ d . mode } ): ${ lines } . Summe: ${ eur ( cartTotal ( ) ) } . `
+ ( d . address ? ` Lieferadresse: ${ d . address } . ` : '' )
+ ( d . time ? ` Wunschzeit: ${ d . time } . ` : '' )
+ ( d . note ? ` Anmerkung: ${ d . note } . ` : '' ) ;
const payload = {
client _id : 'DEMO-PIZZA' , source : 'order-pizzeria' ,
name : d . name , phone : d . phone ,
service _interest : 'Online-Bestellung · ' + d . mode ,
message : summary
} ;
const btn = form . querySelector ( 'button[type=submit]' ) ;
const old = btn . textContent ; btn . disabled = true ; btn . textContent = 'Wird gesendet…' ;
toast . style . display = 'none' ;
try {
const r = await fetch ( ORDER _WEBHOOK , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( payload ) } ) ;
if ( ! r . ok ) throw new Error ( r . status ) ;
toast . className = 'toast' ; toast . textContent = '✓ Danke, ' + d . name + '! Deine Bestellung über ' + eur ( cartTotal ( ) ) + ' ist eingegangen — wir bestätigen gleich.' ;
toast . style . display = 'block' ;
for ( const k in cart ) delete cart [ k ] ; renderCart ( ) ; form . reset ( ) ; toggleAddr ( ) ;
} catch ( err ) {
toast . className = 'toast err' ; toast . textContent = '⚠ Senden fehlgeschlagen — bitte erneut versuchen oder anrufen.' ;
toast . style . display = 'block' ;
} finally { btn . disabled = false ; btn . textContent = old ; }
return false ;
}
renderMenu ( ) ;
renderCart ( ) ;
< / script >
< script > window . CB _CTX = { client _id : "DEMO-PIZZA" , source : "callback-pizzeria" } ; < / script >
<!-- ===== Rückruf - Widget (lead capture popup) ===== -->
< style >
. cb-fab { position : fixed ; right : 18 px ; bottom : 18 px ; z-index : 900 ; background : #d6453a ; color : #fff ; border : 0 ; border-radius : 999 px ; padding : 13 px 20 px ; font : 600 .95 rem system-ui , - apple-system , Segoe UI , Roboto , sans-serif ; box-shadow : 0 10 px 26 px rgba ( 0 , 0 , 0 , .28 ) ; cursor : pointer ; transition : transform .15 s , background .2 s ; }
. cb-fab : hover { transform : translateY ( -2 px ) ; background : #b5342b ; }
. cb-overlay { position : fixed ; inset : 0 ; z-index : 1000 ; background : rgba ( 30 , 12 , 8 , .55 ) ; backdrop-filter : blur ( 3 px ) ; display : none ; align-items : center ; justify-content : center ; padding : 18 px ; }
. cb-overlay . open { display : flex ; }
. cb-modal { position : relative ; width : min ( 440 px , 100 % ) ; background : #fff ; color : #2a1812 ; border-radius : 16 px ; box-shadow : 0 24 px 60 px rgba ( 0 , 0 , 0 , .35 ) ; padding : 28 px 26 px 22 px ; font-family : system-ui , - apple-system , Segoe UI , Roboto , sans-serif ; max-height : 92 vh ; overflow : auto ; animation : cbIn .18 s ease ; }
@ keyframes cbIn { from { opacity : 0 ; transform : translateY ( 14 px ) ; } to { opacity : 1 ; transform : none ; } }
. cb-modal h3 { margin : 0 0 4 px ; font-size : 1.35 rem ; }
. cb-sub { margin : 0 0 18 px ; color : #8a6f63 ; font-size : .9 rem ; }
. cb-x { position : absolute ; top : 10 px ; right : 13 px ; background : none ; border : 0 ; font-size : 1.7 rem ; line-height : 1 ; color : #b79f95 ; cursor : pointer ; }
. cb-x : hover { color : #2a1812 ; }
. cb-modal label { display : block ; font-size : .82 rem ; font-weight : 600 ; margin-bottom : 13 px ; }
. cb-modal label . o { color : #b79f95 ; font-weight : 400 ; }
. cb-modal input , . cb-modal textarea { width : 100 % ; margin-top : 5 px ; font : inherit ; font-size : .94 rem ; color : #2a1812 ; background : #fbf6f0 ; border : 1 px solid #efe1d6 ; border-radius : 9 px ; padding : 10 px 12 px ; resize : vertical ; }
. cb-modal input : focus , . cb-modal textarea : focus { outline : none ; border-color : #d6453a ; background : #fff ; }
. cb-submit { width : 100 % ; margin-top : 4 px ; background : #d6453a ; color : #fff ; border : 0 ; border-radius : 999 px ; padding : 13 px ; font : 600 1 rem system-ui ; cursor : pointer ; transition : background .2 s ; }
. cb-submit : hover { background : #b5342b ; }
. cb-submit : disabled { opacity : .6 ; cursor : default ; }
. cb-toast { display : none ; margin-top : 14 px ; padding : 11 px 13 px ; border-radius : 9 px ; font-size : .88 rem ; font-weight : 500 ; }
. cb-toast . ok { background : #e3f6ec ; color : #0c6b3c ; border : 1 px solid #b8e6cc ; }
. cb-toast . err { background : #fdecec ; color : #9b2226 ; border : 1 px solid #f3c7c7 ; }
. cb-open { margin-left : 10 px ; background : rgba ( 255 , 255 , 255 , .16 ) ; color : inherit ; border : 1 px solid rgba ( 255 , 255 , 255 , .34 ) ; border-radius : 999 px ; padding : 3 px 13 px ; font : 600 .74 rem system-ui ; cursor : pointer ; letter-spacing : .4 px ; vertical-align : middle ; }
. cb-open : hover { background : rgba ( 255 , 255 , 255 , .30 ) ; }
@ media ( max-width : 560px ) { . cb-fab { right : 12 px ; bottom : 12 px ; padding : 11 px 17 px ; } }
< / style >
< button class = "cb-fab" type = "button" onclick = "cbOpen()" aria-label = "Rückruf anfordern" > 📞 Rückruf< / button >
< div class = "cb-overlay" id = "cbOverlay" onclick = "cbBg(event)" >
< div class = "cb-modal" role = "dialog" aria-modal = "true" aria-labelledby = "cbTitle" >
< button class = "cb-x" type = "button" onclick = "cbClose()" aria-label = "Schließen" > × < / button >
< h3 id = "cbTitle" > Rückruf anfordern< / h3 >
< p class = "cb-sub" > Name & Nummer genügen — wir melden uns bei Ihnen. Alles andere ist optional.< / p >
< form onsubmit = "return cbSubmit(event, this)" >
< input type = "hidden" name = "client_id" / >
< input type = "hidden" name = "source" / >
< label > Name *
< input name = "name" required autocomplete = "name" placeholder = "Vor- und Nachname" / >
< / label >
< label > Telefon *
< input name = "phone" required autocomplete = "tel" inputmode = "tel" placeholder = "+49 …" / >
< / label >
< label > Ihr Betrieb < span class = "o" > (optional)< / span >
< input name = "business" placeholder = "z. B. Café Lichtblick" / >
< / label >
< label > Was sollen wir für Sie umsetzen? < span class = "o" > (optional)< / span >
< input name = "service" placeholder = "Website, Online-Bestellung, Rückruf-Service …" / >
< / label >
< label > Nachricht < span class = "o" > (optional)< / span >
< textarea name = "message" rows = "2" placeholder = "Worum geht es?" > < / textarea >
< / label >
< button class = "cb-submit" type = "submit" > Rückruf anfordern< / button >
< div class = "cb-toast" id = "cbToast" > < / div >
< / form >
< / div >
< / div >
< script >
const CB _WEBHOOK = 'https://n8n.mivanchenko.de/webhook/lead-intake' ;
const cbCtx = window . CB _CTX || { client _id : 'DEMO' , source : 'callback' } ;
function cbOpen ( ) {
const o = document . getElementById ( 'cbOverlay' ) ;
o . querySelector ( '[name=client_id]' ) . value = cbCtx . client _id ;
o . querySelector ( '[name=source]' ) . value = cbCtx . source ;
o . classList . add ( 'open' ) ;
document . body . style . overflow = 'hidden' ;
setTimeout ( ( ) => o . querySelector ( '[name=name]' ) . focus ( ) , 60 ) ;
}
function cbClose ( ) {
document . getElementById ( 'cbOverlay' ) . classList . remove ( 'open' ) ;
document . body . style . overflow = '' ;
}
function cbBg ( e ) { if ( e . target . id === 'cbOverlay' ) cbClose ( ) ; }
document . addEventListener ( 'keydown' , e => { if ( e . key === 'Escape' ) cbClose ( ) ; } ) ;
async function cbSubmit ( e , form ) {
e . preventDefault ( ) ;
const btn = form . querySelector ( '.cb-submit' ) ;
const toast = document . getElementById ( 'cbToast' ) ;
const d = Object . fromEntries ( new FormData ( form ) . entries ( ) ) ;
let msg = ( d . message || '' ) . trim ( ) ;
if ( d . business ) msg = ( msg ? msg + ' — ' : '' ) + 'Betrieb: ' + d . business ;
const payload = {
client _id : d . client _id , source : d . source ,
name : d . name , phone : d . phone ,
service _interest : d . service || '' , message : msg
} ;
const old = btn . textContent ;
btn . disabled = true ; btn . textContent = 'Senden…' ;
toast . style . display = 'none' ;
try {
const r = await fetch ( CB _WEBHOOK , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( payload ) } ) ;
if ( ! r . ok ) throw new Error ( r . status ) ;
toast . className = 'cb-toast ok' ;
toast . textContent = '✓ Danke! Wir rufen Sie zurück.' ;
toast . style . display = 'block' ;
form . reset ( ) ;
setTimeout ( cbClose , 2200 ) ;
} catch ( err ) {
toast . className = 'cb-toast err' ;
toast . textContent = '⚠ Senden fehlgeschlagen. Bitte erneut versuchen.' ;
toast . style . display = 'block' ;
} finally {
btn . disabled = false ; btn . textContent = old ;
}
return false ;
}
< / script >
<!-- ===== /Rückruf - Widget ===== -->
< / body >
< / html >