/* Multi-step Funnel + Footer */ function StepDot({ active, done }) { return (
); } function ChoiceCard({ icon: Icon, label, selected, onClick, emoji }) { return ( ); } function Field({ label, value, onChange, type = "text", error, placeholder }) { return ( ); } /* Calendar logic helpers */ function isSameDay(a, b) { return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); } function startOfDay(d) { const x = new Date(d); x.setHours(0,0,0,0); return x; } function addDays(d, n) { const x = new Date(d); x.setDate(x.getDate() + n); return x; } function getEarliestSelectable(today) { let candidate = addDays(startOfDay(today), 2); // Must be Mon–Fri while (candidate.getDay() === 0 || candidate.getDay() === 6) { candidate = addDays(candidate, 1); } return candidate; } function isDateDisabled(d, today, earliest) { const day = d.getDay(); if (day === 0 || day === 6) return true; if (startOfDay(d) < earliest) return true; return false; } function CustomCalendar({ value, onChange, today }) { const [view, setView] = useState(() => { const t = startOfDay(today); return new Date(t.getFullYear(), t.getMonth(), 1); }); const earliest = useMemo(() => getEarliestSelectable(today), [today]); const monthName = view.toLocaleDateString("de-DE", { month: "long", year: "numeric" }); // Build grid: Monday-first const firstDay = new Date(view.getFullYear(), view.getMonth(), 1); const lastDay = new Date(view.getFullYear(), view.getMonth() + 1, 0); const startWeekday = (firstDay.getDay() + 6) % 7; // Mon=0 const daysInMonth = lastDay.getDate(); const cells = []; for (let i = 0; i < startWeekday; i++) cells.push(null); for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(view.getFullYear(), view.getMonth(), d)); while (cells.length % 7 !== 0) cells.push(null); const canPrev = !(view.getFullYear() === today.getFullYear() && view.getMonth() === today.getMonth()); return (
{monthName}
{["Mo","Di","Mi","Do","Fr","Sa","So"].map(d =>
{d}
)}
{cells.map((d, i) => { if (!d) return
; const disabled = isDateDisabled(d, today, earliest); const selected = value && isSameDay(d, value); const isToday = isSameDay(d, today); return ( ); })}
); } function TimeSlots({ value, onChange }) { const slots = useMemo(() => { const out = []; for (let h = 9; h <= 18; h++) { out.push(`${String(h).padStart(2,"0")}:00`); out.push(`${String(h).padStart(2,"0")}:30`); } return out; }, []); return (
{slots.map((s) => { const selected = value === s; return ( ); })}
); } /* TODO: Cal.com / Calendly Embed an dieser Stelle einsetzen — Custom-Kalender bis dahin als UI-Platzhalter */ function Funnel() { const today = useMemo(() => new Date(), []); const [step, setStep] = useState(1); const [data, setData] = useState({ type: "", size: "", budget: "", projectDesc: "", name: "", company: "", email: "", phone: "", date: null, time: "", }); const [errors, setErrors] = useState({}); const [submitted, setSubmitted] = useState(false); const [sending, setSending] = useState(false); const totalSteps = 6; const upd = (k, v) => setData((d) => ({ ...d, [k]: v })); // Pre-select from pricing section useEffect(() => { const handler = (e) => { const { budget, type } = e.detail; setData(d => ({ ...d, type, budget })); setStep(3); setSubmitted(false); }; window.addEventListener("selectPackage", handler); return () => window.removeEventListener("selectPackage", handler); }, []); const canNext = () => { if (step === 1) return !!data.type; if (step === 2) return !!data.size; if (step === 3) return !!data.budget; if (step === 4) { const e = {}; if (!data.name.trim()) e.name = "Bitte angeben"; if (!data.company.trim()) e.company = "Bitte angeben"; if (!data.email.trim() || !/^\S+@\S+\.\S+$/.test(data.email)) e.email = "Gültige E-Mail bitte"; if (!data.phone.trim() || data.phone.replace(/\D/g, "").length < 6) e.phone = "Gültige Nummer bitte"; return Object.keys(e).length === 0; } if (step === 5) return !!data.date && !!data.time; return true; }; const next = () => { if (step === 4 && !canNext()) { const e = {}; if (!data.name.trim()) e.name = "Bitte angeben"; if (!data.company.trim()) e.company = "Bitte angeben"; if (!data.email.trim() || !/^\S+@\S+\.\S+$/.test(data.email)) e.email = "Gültige E-Mail bitte"; if (!data.phone.trim() || data.phone.replace(/\D/g, "").length < 6) e.phone = "Gültige Nummer bitte"; setErrors(e); return; } setErrors({}); setStep((s) => Math.min(totalSteps, s + 1)); }; const prev = () => setStep((s) => Math.max(1, s - 1)); const submit = async () => { setSending(true); const dateStr = data.date ? data.date.toLocaleDateString("de-DE", { weekday: "long", day: "2-digit", month: "long", year: "numeric" }) : null; try { await fetch("/api-contact.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...data, date: dateStr }), }); } catch (e) { console.error("Kontakt-Mail Fehler:", e); } setSending(false); setSubmitted(true); }; const ref = useReveal(); const progress = ((step - 1) / (totalSteps - 1)) * 100; return (
Erstgespräch

In 6 Schritten zum Termin.

Kein Sales-Pitch. Wir hören zu, bauen einen klaren Fahrplan und Sie entscheiden.

{!submitted ? ( <> {/* Progress */}
Schritt {step} von {totalSteps}
{Array.from({ length: totalSteps }).map((_, i) => ( ))}
{/* Steps */} {step === 1 && (

Was möchten Sie umsetzen?

Wählen Sie das Hauptprojekt — Mehrfaches geht später.

upd("type", "Webseite")} /> upd("type", "Software")} /> upd("type", "Funnel-System")} />
)} {step === 2 && (

Wie groß ist Ihr Unternehmen?

So können wir den Umfang einschätzen.

{["Solo / Einzelunternehmer", "2–10 Mitarbeiter", "11–50 Mitarbeiter", "50+ Mitarbeiter"].map((opt) => ( upd("size", opt)} /> ))}
)} {step === 3 && data.type !== "Software" && (

Ihr Budget?

Für eine erste Orientierung — kein Festpreis.

{[ ["149–299 € / Monat", "Starter / Standard", false], ["299–599 € / Monat", "Standard / Growth", true], ["599 € + / Monat", "Growth oder individuelle Lösung", false], ].map(([opt, sub, recommended]) => { const selected = data.budget === opt; return ( ); })}
)} {step === 3 && data.type === "Software" && (

Projektbudget?

Individuelle Software ist immer projektbasiert — kein monatlicher Betrag. Das ist nur eine erste Orientierung.

{[ ["unter 5.000 €", "Automatisierungen, kleinere Tools"], ["5.000 – 15.000 €", "Individuelle Software, Dashboards, Apps"], ["15.000 – 30.000 €", "Komplexere Systeme, mehrere Module"], ["30.000 € +", "Enterprise-Lösung, große Plattformen"], ["Noch nicht definiert", "Wir ermitteln es gemeinsam im Gespräch"], ].map(([opt, sub]) => { const selected = data.budget === opt; return ( ); })}
Kurze Projektbeschreibung (optional)