Files
mivanchenko 156166b4e5 Add booking stack, client deploys, and back-office updates
- deploy/booking: shared Easy!Appointments stack with brand-matched
  wizard (flatpickr recolor, single-tenant provider hide, iframe
  auto-fit height reporter)
- deploy/clients: per-client isolated nginx compose stacks with
  _template scaffold, new-client.sh, and happynails live site
- deploy/backup: smb-db backup script
- n8n: booking-sync workflow; onboarding tweaks
- playbooks: lead-to-customer lifecycle + outreach
- templates: nail-studio landing previews
- backoffice: app/db/init/compose updates

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 16:10:15 +02:00

434 lines
24 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Best Nails Nürnberg — Nagelstudio | Termin online buchen</title>
<meta name="description" content="Maniküre, Gel- & Shellac-Nägel und individuelles Nageldesign in Nürnberg. Jetzt bequem online einen Termin buchen." />
<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=Inter:wght@400;500;600;700&family=Playfair+Display:wght@500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #fdf6f4;
--bg-2: #f8e9e5;
--card: #ffffff;
--ink: #3b2a2e;
--muted: #9a8589;
--gold: #d96e7d;
--gold-2: #ec9aa6;
--line: #f1e1de;
--radius: 16px;
--shadow: 0 18px 50px rgba(180,120,130,.18);
}
* { 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, .display { font-family: 'Playfair Display', Georgia, serif; letter-spacing: .2px; line-height: 1.1; }
a { color: inherit; text-decoration: none; }
.wrap { width: min(1120px, 92vw); margin-inline: auto; }
section { padding: 92px 0; }
.eyebrow { color: var(--gold); font-weight: 600; letter-spacing: 3px; text-transform: uppercase; font-size: .72rem; }
/* demo ribbon */
.demo-bar {
background: repeating-linear-gradient(45deg, #f6ddd6, #f6ddd6 12px, #f9e7e2 12px, #f9e7e2 24px);
color: #a05468; font-size: .76rem; letter-spacing: 1.5px; text-transform: uppercase;
text-align: center; padding: 7px 12px; border-bottom: 1px solid var(--line);
}
/* nav */
header.nav { position: sticky; top: 0; z-index: 50; background: rgba(253,246,244,.85); backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); }
.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 68px; }
.brand { font-family: 'Playfair Display', serif; font-size: 1.55rem; font-weight: 700; letter-spacing: .5px; }
.brand span { color: var(--gold); }
nav ul { display: flex; gap: 30px; list-style: none; align-items: center; }
nav a { color: var(--muted); font-size: .92rem; font-weight: 500; transition: color .2s; }
nav a:hover { color: var(--ink); }
.btn {
display: inline-block; background: linear-gradient(180deg, var(--gold-2), var(--gold));
color: #fff; font-weight: 700; padding: 12px 22px; border-radius: 999px;
border: 0; cursor: pointer; font-size: .95rem; transition: transform .15s, box-shadow .2s;
box-shadow: 0 8px 22px rgba(199,127,147,.32);
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(199,127,147,.45); }
.btn.ghost { background: transparent; color: var(--ink); border: 1px solid var(--line); box-shadow: none; }
.nav-toggle { display: none; background: none; border: 0; color: var(--ink); font-size: 1.6rem; cursor: pointer; }
/* hero */
.hero { position: relative; background:
linear-gradient(rgba(253,246,244,.40), rgba(253,246,244,.78)),
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40'%3E%3Crect width='40' height='40' fill='%23fbeae5'/%3E%3Ccircle cx='20' cy='20' r='1.4' fill='%23f0cdc5'/%3E%3C/svg%3E");
border-bottom: 1px solid var(--line); }
.hero-inner { padding: 96px 0 104px; max-width: 720px; }
.hero h1 { font-size: clamp(2.6rem, 7vw, 4.6rem); margin: 14px 0 6px; }
.hero h1 em { color: var(--gold); font-style: italic; }
.hero p { color: var(--muted); font-size: 1.18rem; max-width: 540px; margin: 18px 0 30px; }
.hero-cta { display: flex; gap: 14px; flex-wrap: wrap; }
.hero-meta { margin-top: 34px; display: flex; gap: 28px; flex-wrap: wrap; color: var(--muted); font-size: .9rem; }
.hero-meta b { color: var(--ink); }
/* services */
.sec-head { max-width: 620px; margin-bottom: 46px; }
.sec-head h2 { font-size: clamp(2.2rem, 5vw, 3.2rem); margin-top: 8px; }
.sec-head p { color: var(--muted); margin-top: 10px; }
.grid { display: grid; gap: 18px; }
.services { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.svc { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 26px; transition: transform .18s, border-color .2s; box-shadow: 0 6px 18px rgba(180,120,130,.06); }
.svc:hover { transform: translateY(-4px); border-color: var(--gold); }
.svc .ic { font-size: 1.8rem; }
.svc h3 { font-size: 1.45rem; margin: 14px 0 6px; }
.svc p { color: var(--muted); font-size: .92rem; min-height: 42px; }
.svc .price { color: var(--gold); font-weight: 700; font-size: 1.05rem; margin-top: 12px; font-family: 'Inter'; }
/* booking */
.booking { background: var(--bg-2); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.booking-grid { display: grid; grid-template-columns: 1.05fr 1fr; gap: 40px; align-items: center; }
.booking-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 26px; box-shadow: var(--shadow); }
.gcal-head { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: .82rem; border-bottom: 1px solid var(--line); padding-bottom: 14px; margin-bottom: 18px; }
.gcal-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: #4caf7d; box-shadow: 0 0 0 3px rgba(76,175,125,.18); margin-right: 12px; }
.slots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.slot { text-align: center; padding: 11px 6px; border: 1px solid var(--line); border-radius: 9px; color: var(--ink); font-size: .9rem; cursor: pointer; transition: .15s; background: #fdf3f1; }
.slot:hover { border-color: var(--gold); color: var(--gold); }
.slot.off { opacity: .4; text-decoration: line-through; cursor: not-allowed; }
.booking ul.points { list-style: none; display: grid; gap: 14px; margin: 22px 0 28px; }
.booking ul.points li { padding-left: 30px; position: relative; color: var(--muted); }
.booking ul.points li::before { content: "✓"; position: absolute; left: 0; color: var(--gold); font-weight: 700; }
/* hours / about */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: start; }
.hours { width: 100%; border-collapse: collapse; }
.hours td { padding: 12px 0; border-bottom: 1px solid var(--line); color: var(--muted); }
.hours td:last-child { text-align: right; color: var(--ink); font-weight: 500; }
/* contact */
.contact { background: var(--bg-2); border-top: 1px solid var(--line); }
form.lead { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 28px; max-width: 560px; box-shadow: var(--shadow); }
.field { margin-bottom: 16px; }
.field label { display: block; font-size: .85rem; color: var(--muted); margin-bottom: 6px; }
.field input, .field textarea, .field select {
width: 100%; background: #fdf3f1; border: 1px solid var(--line); border-radius: 9px;
color: var(--ink); padding: 12px 14px; font: inherit; font-size: .95rem;
}
.field input:focus, .field textarea:focus, .field select:focus { outline: 2px solid var(--gold); border-color: var(--gold); }
.form-note { font-size: .78rem; color: var(--muted); margin-top: 10px; }
.toast { display: none; margin-top: 14px; padding: 12px 14px; border-radius: 9px; background: #eef9ef; border: 1px solid #cdeccf; color: #3b7a44; font-size: .9rem; }
/* footer */
footer { padding: 54px 0 40px; border-top: 1px solid var(--line); color: var(--muted); font-size: .9rem; }
.foot-grid { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 24px; }
footer .brand { color: var(--ink); }
@media (max-width: 820px) {
nav ul { display: none; }
.nav-toggle { display: block; }
nav.open ul { display: flex; position: absolute; inset: 68px 0 auto 0; flex-direction: column; background: var(--bg-2); padding: 18px 6vw; gap: 18px; border-bottom: 1px solid var(--line); }
.booking-grid, .split { grid-template-columns: 1fr; }
section { padding: 64px 0; }
}
</style>
</head>
<body>
<div class="demo-bar">Beispielseite · So könnte Ihr Online-Auftritt aussehen <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">BEST<span>NAILS</span></a>
<nav id="nav">
<ul>
<li><a href="#leistungen">Leistungen</a></li>
<li><a href="#buchen">Termin buchen</a></li>
<li><a href="#zeiten">Öffnungszeiten</a></li>
<li><a href="#kontakt">Kontakt</a></li>
<li><a href="#buchen" class="btn">Termin buchen</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" id="hero">
<div class="wrap hero-inner">
<span class="eyebrow">Nagelstudio · Nürnberg</span>
<h1>Schöne Nägel.<br><em>Zum Wohlfühlen.</em></h1>
<p>Maniküre, Gel- &amp; Shellac-Nägel und individuelles Nageldesign — mit Liebe und ruhiger Hand gemacht. Sichern Sie sich Ihren Termin in 30 Sekunden online.</p>
<div class="hero-cta">
<a href="#buchen" class="btn">Termin online buchen</a>
<a href="#kontakt" class="btn ghost">✉ Nachricht senden</a>
</div>
<div class="hero-meta">
<span><b>4,9</b> · Top bewertet</span>
<span>💅 Liebe zum Detail</span>
<span>📍 Mitten in Nürnberg</span>
</div>
</div>
</section>
<!-- SERVICES -->
<section id="leistungen">
<div class="wrap">
<div class="sec-head">
<span class="eyebrow">Leistungen</span>
<h2>Was wir für Sie tun</h2>
<p>Faire Preise, gepflegte Atmosphäre — und ein Ergebnis, das man gerne herzeigt.</p>
</div>
<div class="grid services">
<article class="svc"><div class="ic">💅</div><h3>Maniküre</h3><p>Pflege, Form und Lack für gepflegte, natürliche Hände.</p><div class="price">ab 25 €</div></article>
<article class="svc"><div class="ic"></div><h3>Gel- &amp; Shellac-Nägel</h3><p>Langanhaltend, glänzend und in Ihrer Wunschfarbe.</p><div class="price">ab 45 €</div></article>
<article class="svc"><div class="ic">🦶</div><h3>Pediküre</h3><p>Wohltuende Fußpflege — entspannt und gründlich.</p><div class="price">ab 35 €</div></article>
<article class="svc"><div class="ic">🎨</div><h3>Nageldesign &amp; Nail Art</h3><p>Individuelle Designs, French, Steinchen, Muster — ganz nach Wunsch.</p><div class="price">ab 5 € / Nagel</div></article>
</div>
</div>
</section>
<!-- BOOKING -->
<section class="booking" id="buchen">
<div class="wrap booking-grid">
<div>
<span class="eyebrow">Termin buchen</span>
<h2 style="font-size:clamp(2.2rem,5vw,3.2rem);margin:8px 0 6px;">Wählen Sie Ihren Termin.<br>Den Rest machen wir.</h2>
<ul class="points">
<li>Sofortige Bestätigung per E-Mail</li>
<li>Automatische Erinnerung am Vortag</li>
<li>Bequem online — kein Hin und Her per DM</li>
</ul>
<!-- Demo: in der Live-Version öffnet dieser Button die echte Online-Terminbuchung. -->
<a href="#" class="btn" onclick="return false;">Verfügbare Zeiten ansehen →</a>
</div>
<div class="booking-card">
<div class="gcal-head"><span class="gcal-dot"></span> Online-Terminbuchung — <span id="bookDate">heute</span></div>
<div class="slots">
<div class="slot off">09:30</div>
<div class="slot">10:30</div>
<div class="slot">11:30</div>
<div class="slot off">13:00</div>
<div class="slot">14:00</div>
<div class="slot">15:30</div>
<div class="slot">16:30</div>
<div class="slot off">17:30</div>
<div class="slot">18:30</div>
</div>
<p class="form-note" style="margin-top:16px;">Demo-Vorschau. In der Live-Version buchen Ihre Kundinnen hier rund um die Uhr selbst einen freien Termin — mit sofortiger Bestätigung.</p>
</div>
</div>
</section>
<!-- HOURS / ABOUT -->
<section id="zeiten">
<div class="wrap split">
<div>
<span class="eyebrow">Über uns</span>
<h2 style="font-size:clamp(2rem,4.5vw,2.8rem);margin:8px 0 14px;">Ihr Wohlfühl-Moment in Nürnberg.</h2>
<p style="color:var(--muted)">Bei Best Nails nehmen wir uns Zeit für Sie. In ruhiger, gepflegter Atmosphäre kümmern wir uns um Ihre Hände und Füße — sorgfältig, hygienisch und mit einem Auge fürs Detail. Kommen Sie vorbei und gönnen Sie sich eine kleine Auszeit.</p>
</div>
<div>
<span class="eyebrow">Öffnungszeiten</span>
<!-- Platzhalter-Zeiten — in der Live-Version durch die echten Zeiten ersetzen. -->
<table class="hours" style="margin-top:14px;">
<tr><td>Dienstag Freitag</td><td>09:30 19:00</td></tr>
<tr><td>Samstag</td><td>09:00 16:00</td></tr>
<tr><td>Sonntag &amp; Montag</td><td>geschlossen</td></tr>
</table>
<p class="form-note" style="margin-top:16px;">📍 Nürnberg · genaue Adresse in der Live-Version</p>
</div>
</div>
</section>
<!-- CONTACT / LEAD FORM -->
<section class="contact" id="kontakt">
<div class="wrap">
<div class="sec-head">
<span class="eyebrow">Kontakt</span>
<h2>Frage stellen oder Rückruf anfordern</h2>
<p>Schreiben Sie uns kurz — wir melden uns am selben Tag.</p>
</div>
<!-- Demo: dieses Formular postet an einen n8n-Webhook -> CRM (Leads) + Telegram. -->
<form class="lead" onsubmit="return submitLead(event, this)">
<input type="hidden" name="client_id" value="PREVIEW-BESTNAILS" />
<input type="hidden" name="source" value="preview-bestnails" />
<div class="field"><label for="a-name">Name</label><input id="a-name" name="name" type="text" placeholder="Ihr Name" required /></div>
<div class="field"><label for="a-contact">E-Mail oder Telefon</label><input id="a-contact" name="contact" type="text" placeholder="name@example.de" required /></div>
<div class="field"><label for="a-svc">Gewünschte Leistung</label>
<select id="a-svc" name="service"><option>Maniküre</option><option>Gel- &amp; Shellac-Nägel</option><option>Pediküre</option><option>Nageldesign &amp; Nail Art</option><option>Auffüllen / Refill</option><option>Sonstiges</option></select>
</div>
<div class="field"><label for="a-msg">Nachricht (optional)</label><textarea id="a-msg" name="message" rows="3" placeholder="Wunschtermin, Frage, …"></textarea></div>
<button class="btn" type="submit">Anfrage senden</button>
<div class="toast">✓ Danke! Demo-Formular — in der Live-Version landet Ihre Anfrage automatisch im System.</div>
<p class="form-note">Mit dem Absenden stimmen Sie der Verarbeitung Ihrer Angaben zur Kontaktaufnahme zu.</p>
</form>
</div>
</section>
</main>
<footer>
<div class="wrap foot-grid">
<div>
<div class="brand">BEST<span style="color:var(--gold)">NAILS</span></div>
<p style="margin-top:8px;">Nagelstudio · Nürnberg</p>
</div>
<div>
<p><b style="color:var(--ink)">Öffnungszeiten</b></p>
<p>DiFr 09:3019 · Sa 0916</p>
</div>
<div>
<p><b style="color:var(--ink)">Kontakt</b></p>
<p>@bestnailsbeautysalon</p>
</div>
</div>
<div class="wrap" style="margin-top:30px;opacity:.6;font-size:.8rem;">© 2026 Best Nails Nürnberg · Beispielseite · Impressum · Datenschutz</div>
</footer>
<script>
// Lead-Formular -> n8n Webhook -> CRM (Leads) + Telegram-Benachrichtigung.
const LEAD_WEBHOOK = 'https://n8n.mivanchenko.de/webhook/lead-intake';
async function submitLead(e, form) {
e.preventDefault();
const btn = form.querySelector('button[type=submit]');
const toast = form.querySelector('.toast');
const data = Object.fromEntries(new FormData(form).entries());
const old = btn.textContent;
btn.disabled = true; btn.textContent = 'Senden…';
try {
const r = await fetch(LEAD_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!r.ok) throw new Error(r.status);
toast.textContent = '✓ Danke! Ihre Anfrage ist eingegangen — wir melden uns.';
toast.style.display = 'block';
form.reset();
} catch (err) {
toast.textContent = '⚠ Senden fehlgeschlagen — bitte erneut versuchen oder direkt anrufen.';
toast.style.display = 'block';
} finally {
btn.disabled = false; btn.textContent = old;
}
return false;
}
</script>
<script>
// Keep the demo booking header on today's date so the example never looks stale.
(function () {
const M = ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
const t = new Date();
const el = document.getElementById('bookDate');
if (el) el.textContent = 'heute, ' + t.getDate() + '. ' + M[t.getMonth()];
})();
</script>
<script>window.CB_CTX = { client_id: "PREVIEW-BESTNAILS", source: "preview-bestnails-callback" };</script>
<!-- ===== Rückruf-Widget (lead capture popup) ===== -->
<style>
.cb-fab { position: fixed; right: 18px; bottom: 18px; z-index: 900; background: var(--gold); color: #fff; border: 0; border-radius: 999px; padding: 13px 20px; font: 600 .95rem system-ui, -apple-system, Segoe UI, Roboto, sans-serif; box-shadow: 0 10px 26px rgba(199,127,147,.32); cursor: pointer; transition: transform .15s, background .2s; }
.cb-fab:hover { transform: translateY(-2px); background: #b56a80; }
.cb-overlay { position: fixed; inset: 0; z-index: 1000; background: rgba(60,30,40,.45); backdrop-filter: blur(3px); display: none; align-items: center; justify-content: center; padding: 18px; }
.cb-overlay.open { display: flex; }
.cb-modal { position: relative; width: min(440px, 100%); background: #fff; color: #3b2a2e; border-radius: 16px; box-shadow: 0 24px 60px rgba(0,0,0,.30); padding: 28px 26px 22px; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; max-height: 92vh; overflow: auto; animation: cbIn .18s ease; }
@keyframes cbIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
.cb-modal h3 { margin: 0 0 4px; font-size: 1.35rem; }
.cb-sub { margin: 0 0 18px; color: #9a8589; font-size: .9rem; }
.cb-x { position: absolute; top: 10px; right: 13px; background: none; border: 0; font-size: 1.7rem; line-height: 1; color: #c9b3b7; cursor: pointer; }
.cb-x:hover { color: #3b2a2e; }
.cb-modal label { display: block; font-size: .82rem; font-weight: 600; margin-bottom: 13px; }
.cb-modal label .o { color: #b6a3a7; font-weight: 400; }
.cb-modal input, .cb-modal textarea { width: 100%; margin-top: 5px; font: inherit; font-size: .94rem; color: #3b2a2e; background: #fdf3f1; border: 1px solid #f1e1de; border-radius: 9px; padding: 10px 12px; resize: vertical; }
.cb-modal input:focus, .cb-modal textarea:focus { outline: none; border-color: var(--gold); background: #fff; }
.cb-submit { width: 100%; margin-top: 4px; background: var(--gold); color: #fff; border: 0; border-radius: 999px; padding: 13px; font: 600 1rem system-ui; cursor: pointer; transition: background .2s; }
.cb-submit:hover { background: #b56a80; }
.cb-submit:disabled { opacity: .6; cursor: default; }
.cb-toast { display: none; margin-top: 14px; padding: 11px 13px; border-radius: 9px; font-size: .88rem; font-weight: 500; }
.cb-toast.ok { background: #eef9ef; color: #3b7a44; border: 1px solid #cdeccf; }
.cb-toast.err { background: #fdecec; color: #9b2226; border: 1px solid #f3c7c7; }
.cb-open { margin-left: 10px; background: rgba(160,84,104,.10); color: inherit; border: 1px solid rgba(160,84,104,.30); border-radius: 999px; padding: 3px 13px; font: 600 .74rem system-ui; cursor: pointer; letter-spacing: .4px; vertical-align: middle; }
.cb-open:hover { background: rgba(160,84,104,.20); }
@media (max-width: 560px) { .cb-fab { right: 12px; bottom: 12px; padding: 11px 17px; } }
</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">&times;</button>
<h3 id="cbTitle">Rückruf anfordern</h3>
<p class="cb-sub">Name &amp; 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>Gewünschte Leistung <span class="o">(optional)</span>
<input name="service" placeholder="Maniküre, Gelnägel, Pediküre …" />
</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: 'PREVIEW', 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();
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>