From 36bb12f656faaa559947cb57880ff7d89491175e Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Fri, 30 Jan 2026 00:47:39 +0100 Subject: [PATCH] contact --- app/contact/page.tsx | 29 +- src/components/ContactForm.tsx | 641 +++++++++++++++++++++++++++++++++ src/components/Section.tsx | 100 +++-- 3 files changed, 720 insertions(+), 50 deletions(-) create mode 100644 src/components/ContactForm.tsx diff --git a/app/contact/page.tsx b/app/contact/page.tsx index 28e90c8..126db47 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -2,18 +2,25 @@ import * as React from 'react'; import { Reveal } from '../../src/components/Reveal'; import { PageHeader } from '../../src/components/PageHeader'; import { Section } from '../../src/components/Section'; +import { ContactForm } from '../../src/components/ContactForm'; export default function ContactPage() { return (
Kontakt
& Anfrage.} - description="Haben Sie ein Projekt im Kopf? Schreiben Sie mir einfach. Ich antworte meistens innerhalb von 24 Stunden." + title={<>Projekt
konfigurieren.} + description="Nutzen Sie den Konfigurator für eine erste Einschätzung oder schreiben Sie mir direkt eine Email." backLink={{ href: '/', label: 'Zurück' }} - backgroundSymbol="@" + backgroundSymbol="?" /> -
+
+ + + +
+ +
@@ -28,20 +35,6 @@ export default function ContactPage() {
- - -
-
!
-
-

Was ich von Ihnen brauche:

-
    -
  • 01 Was ist das Ziel des Projekts?
  • -
  • 02 Gibt es eine Deadline?
  • -
  • 03 Welches Budget ist eingeplant?
  • -
-
-
-
diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx new file mode 100644 index 0000000..10f8e98 --- /dev/null +++ b/src/components/ContactForm.tsx @@ -0,0 +1,641 @@ +'use client'; + +import * as React from 'react'; +import { useState, useMemo, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Check, ChevronRight, ChevronLeft, Send, Info, Plus, Minus } from 'lucide-react'; +import { + ConceptWebsite, + ConceptTarget, + ConceptPrototyping, + ConceptSystem, + ConceptCode, + ConceptCommunication, + ConceptAutomation, + ConceptPrice +} from './Landing/ConceptIllustrations'; + +// Pricing constants from PRICING.md +const PRICING = { + BASE_WEBSITE: 6000, + PAGE: 800, + FEATURE: 2000, + FUNCTION: 1000, + VISUAL_STAGING: 2000, + COMPLEX_INTERACTION: 1500, + NEW_DATASET: 400, + ADJUST_DATASET: 200, + HOSTING_MONTHLY: 120, + STORAGE_EXPANSION_MONTHLY: 10, + CMS_SETUP: 1500, + CMS_CONNECTION_PER_FEATURE: 800, + API_INTEGRATION: 1000, + APP_HOURLY: 120, +}; + +type ProjectType = 'website' | 'app'; + +interface FormState { + projectType: ProjectType; + pages: number; + features: string[]; + functions: string[]; + visualStaging: number; + complexInteractions: number; + newDatasets: number; + adjustDatasets: number; + cmsSetup: boolean; + apiIntegrations: number; + storageExpansion: number; + name: string; + email: string; + message: string; +} + +const initialState: FormState = { + projectType: 'website', + pages: 1, + features: [], + functions: [], + visualStaging: 0, + complexInteractions: 0, + newDatasets: 0, + adjustDatasets: 0, + cmsSetup: false, + apiIntegrations: 0, + storageExpansion: 0, + name: '', + email: '', + message: '', +}; + +const FEATURE_OPTIONS = [ + { id: 'blog', label: 'Blog / Magazin', desc: 'Regelmäßige Artikel und Beiträge.' }, + { id: 'products', label: 'Produktbereich', desc: 'Katalog Ihrer Leistungen oder Produkte.' }, + { id: 'news', label: 'News / Aktuelles', desc: 'Neuigkeiten und Pressemitteilungen.' }, + { id: 'jobs', label: 'Karriere / Jobs', desc: 'Stellenanzeigen und Bewerbungsoptionen.' }, + { id: 'refs', label: 'Referenzen / Cases', desc: 'Präsentation Ihrer Projekte.' }, + { id: 'events', label: 'Events / Termine', desc: 'Veranstaltungskalender.' }, +]; + +const FUNCTION_OPTIONS = [ + { id: 'search', label: 'Suche', desc: 'Volltextsuche über alle Inhalte.' }, + { id: 'filter', label: 'Filter-Systeme', desc: 'Kategorisierung und Sortierung.' }, + { id: 'i18n', label: 'Mehrsprachigkeit', desc: 'Inhalte in mehreren Sprachen.' }, + { id: 'pdf', label: 'PDF-Export', desc: 'Automatisierte PDF-Erstellung.' }, + { id: 'api', label: 'API-Anbindung', desc: 'Daten-Sync mit Drittsystemen.' }, + { id: 'forms', label: 'Erweiterte Formulare', desc: 'Komplexe Abfragen & Logik.' }, +]; + +export function ContactForm() { + const [stepIndex, setStepIndex] = useState(0); + const [state, setState] = useState(initialState); + const [isSubmitted, setIsSubmitted] = useState(false); + + const totalPrice = useMemo(() => { + if (state.projectType !== 'website') return 0; + + let total = PRICING.BASE_WEBSITE; + total += state.pages * PRICING.PAGE; + total += state.features.length * PRICING.FEATURE; + total += state.functions.length * PRICING.FUNCTION; + total += state.visualStaging * PRICING.VISUAL_STAGING; + total += state.complexInteractions * PRICING.COMPLEX_INTERACTION; + total += state.newDatasets * PRICING.NEW_DATASET; + total += state.adjustDatasets * PRICING.ADJUST_DATASET; + total += state.apiIntegrations * PRICING.API_INTEGRATION; + + if (state.cmsSetup) { + total += PRICING.CMS_SETUP; + // CMS connection per feature that is selected + total += state.features.length * PRICING.CMS_CONNECTION_PER_FEATURE; + } + + return total; + }, [state]); + + const monthlyPrice = useMemo(() => { + if (state.projectType !== 'website') return 0; + return PRICING.HOSTING_MONTHLY + (state.storageExpansion * PRICING.STORAGE_EXPANSION_MONTHLY); + }, [state]); + + const updateState = (updates: Partial) => { + setState((s) => ({ ...s, ...updates })); + }; + + const toggleFeature = (id: string) => { + setState(s => ({ + ...s, + features: s.features.includes(id) + ? s.features.filter(f => f !== id) + : [...s.features, id] + })); + }; + + const toggleFunction = (id: string) => { + setState(s => ({ + ...s, + functions: s.functions.includes(id) + ? s.functions.filter(f => f !== id) + : [...s.functions, id] + })); + }; + + const steps = [ + { + id: 'type', + title: 'Das Ziel', + description: 'Was möchten Sie realisieren?', + illustration: + }, + { + id: 'structure', + title: 'Die Basis', + description: 'Wie viele individuelle Seiten-Layouts benötigen wir?', + illustration: + }, + { + id: 'features', + title: 'Die Systeme', + description: 'Welche inhaltlichen Bereiche soll die Website haben?', + illustration: + }, + { + id: 'functions', + title: 'Die Logik', + description: 'Welche Funktionen werden benötigt?', + illustration: + }, + { + id: 'experience', + title: 'Das Erlebnis', + description: 'Soll die Seite durch besondere Effekte glänzen?', + illustration: + }, + { + id: 'content', + title: 'Die Pflege', + description: 'Wer kümmert sich um die Daten?', + illustration: + }, + { + id: 'contact', + title: 'Der Start', + description: 'Erzählen Sie mir mehr über Ihr Vorhaben.', + illustration: + }, + ]; + + const activeSteps = useMemo(() => { + if (state.projectType === 'website') return steps; + return [steps[0], steps[6]]; + }, [state.projectType]); + + useEffect(() => { + if (stepIndex >= activeSteps.length) { + setStepIndex(activeSteps.length - 1); + } + }, [activeSteps, stepIndex]); + + const nextStep = () => { + if (stepIndex < activeSteps.length - 1) { + setStepIndex(stepIndex + 1); + } + }; + + const prevStep = () => { + if (stepIndex > 0) { + setStepIndex(stepIndex - 1); + } + }; + + const Counter = ({ + label, + value, + onChange, + description, + }: { + label: string; + value: number; + onChange: (val: number) => void; + description?: string; + }) => ( +
+
+

{label}

+ {description &&

{description}

} +
+
+ + {value} + +
+
+ ); + + const Checkbox = ({ + label, + desc, + checked, + onChange + }: { + label: string; + desc: string; + checked: boolean; + onChange: () => void; + }) => ( + + ); + + const renderStepContent = () => { + const currentStep = activeSteps[stepIndex]; + + switch (currentStep.id) { + case 'type': + return ( +
+ {[ + { id: 'website', label: 'Website', desc: 'Eine klassische Webpräsenz, ein Portfolio oder ein Blog.' }, + { id: 'app', label: 'App / Software', desc: 'Ein internes Tool, ein Dashboard oder eine komplexe Prozess-Logik.' }, + ].map((type) => ( + + ))} +
+ ); + + case 'structure': + return ( +
+ updateState({ pages: v })} + description="Wie viele grundlegend verschiedene Seiten benötigen wir? (z.B. Startseite, Leistungs-Übersicht, Kontakt, Über uns). Jede Seite wird individuell gestaltet und umgesetzt." + /> +
+ +
+

Was zählt als Seite?

+

Eine Seite ist ein eigenständiges Layout. Wenn Sie 10 Leistungen haben, die alle das gleiche Layout nutzen, zählt das als 1 Layout (Seite) plus ein "Feature" für die Verwaltung der Leistungen.

+
+
+
+ ); + + case 'features': + return ( +
+ {FEATURE_OPTIONS.map(opt => ( + toggleFeature(opt.id)} + /> + ))} +
+ ); + + case 'functions': + return ( +
+ {FUNCTION_OPTIONS.map(opt => ( + toggleFunction(opt.id)} + /> + ))} +
+ ); + + case 'experience': + return ( +
+ updateState({ visualStaging: v })} + description="Wie viele Sektionen sollen ein High-End Erlebnis bieten? (z.B. komplexe Scroll-Animationen, interaktive Storytelling-Elemente)." + /> + updateState({ complexInteractions: v })} + description="Benötigen Sie spezielle Tools wie einen Produkt-Konfigurator oder eine Live-Vorschau?" + /> +
+ ); + + case 'content': + return ( +
+
+
+

Selbst verwalten (CMS)

+

Möchten Sie Texte und Bilder jederzeit selbst über eine einfache Oberfläche ändern können?

+
+ +
+ + updateState({ newDatasets: v })} + description="Für wie viele Datensätze (z.B. Blogartikel, Produkte) soll ich die initiale Befüllung übernehmen?" + /> +
+ ); + + case 'contact': + return ( +
+
+ updateState({ name: e.target.value })} + className="w-full p-8 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-colors" + /> + updateState({ email: e.target.value })} + className="w-full p-8 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-colors" + /> +
+