diff --git a/app/globals.css b/app/globals.css
index f5b1c74..27a8295 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -11,6 +11,7 @@
body {
@apply bg-white text-slate-800 font-serif antialiased selection:bg-slate-900 selection:text-white;
line-height: 1.8;
+ -webkit-tap-highlight-color: transparent;
}
/* Typography */
@@ -64,10 +65,30 @@
}
/* Focus states */
+ a, button, input, textarea {
+ -webkit-tap-highlight-color: transparent;
+ }
+
a:focus,
button:focus,
- input:focus {
- @apply outline-none ring-2 ring-slate-900 ring-offset-2 rounded-sm;
+ input:focus,
+ textarea:focus,
+ a:active,
+ button:active,
+ input:active,
+ textarea:active,
+ a:focus-visible,
+ button:focus-visible,
+ input:focus-visible,
+ textarea:focus-visible {
+ outline: none !important;
+ outline: 0 !important;
+ box-shadow: none !important;
+ border-radius: inherit !important;
+ }
+
+ button::-moz-focus-inner {
+ border: 0 !important;
}
}
diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx
index 10f8e98..86a2a8a 100644
--- a/src/components/ContactForm.tsx
+++ b/src/components/ContactForm.tsx
@@ -1,9 +1,10 @@
'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 { useState, useMemo, useEffect, useRef } from 'react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { motion, AnimatePresence, useSpring, useTransform } from 'framer-motion';
+import { Check, ChevronRight, ChevronLeft, Send, Info, Plus, Minus, Upload, FileText, X, Trash2, Palette, RefreshCw, Calendar, Zap, ShieldCheck, AlertCircle, ShoppingCart, Globe } from 'lucide-react';
import {
ConceptWebsite,
ConceptTarget,
@@ -12,7 +13,8 @@ import {
ConceptCode,
ConceptCommunication,
ConceptAutomation,
- ConceptPrice
+ ConceptPrice,
+ HeroArchitecture
} from './Landing/ConceptIllustrations';
// Pricing constants from PRICING.md
@@ -21,10 +23,8 @@ const PRICING = {
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,
@@ -37,42 +37,81 @@ type ProjectType = 'website' | 'app';
interface FormState {
projectType: ProjectType;
- pages: number;
+ selectedPages: string[];
+ otherPages: string[];
features: string[];
+ otherFeatures: string[];
functions: string[];
- visualStaging: number;
+ otherFunctions: string[];
+ apiSystems: string[];
+ otherTech: string[];
+ assets: string[];
+ otherAssets: string[];
complexInteractions: number;
newDatasets: number;
- adjustDatasets: number;
cmsSetup: boolean;
- apiIntegrations: number;
storageExpansion: number;
name: string;
email: string;
+ role: string;
message: string;
+ sitemapFile: File | null;
+ contactFiles: File[];
+ // Design
+ designVibe: string;
+ colorScheme: string[];
+ references: string[];
+ designWishes: string;
+ // Maintenance
+ expectedAdjustments: string;
+ languagesCount: number;
+ // Timeline
+ deadline: string;
}
const initialState: FormState = {
projectType: 'website',
- pages: 1,
+ selectedPages: ['Home'],
+ otherPages: [],
features: [],
+ otherFeatures: [],
functions: [],
- visualStaging: 0,
+ otherFunctions: [],
+ apiSystems: [],
+ otherTech: [],
+ assets: [],
+ otherAssets: [],
complexInteractions: 0,
newDatasets: 0,
- adjustDatasets: 0,
cmsSetup: false,
- apiIntegrations: 0,
storageExpansion: 0,
name: '',
email: '',
+ role: '',
message: '',
+ sitemapFile: null,
+ contactFiles: [],
+ designVibe: 'minimal',
+ colorScheme: ['#ffffff', '#f8fafc', '#0f172a'],
+ references: [],
+ designWishes: '',
+ expectedAdjustments: 'low',
+ languagesCount: 1,
+ deadline: 'flexible',
};
+const PAGE_SAMPLES = [
+ { id: 'Home', label: 'Startseite', desc: 'Der erste Eindruck Ihrer Marke.' },
+ { id: 'About', label: 'Über uns', desc: 'Ihre Geschichte und Ihr Team.' },
+ { id: 'Services', label: 'Leistungen', desc: 'Übersicht Ihres Angebots.' },
+ { id: 'Contact', label: 'Kontakt', desc: 'Anlaufstelle für Ihre Kunden.' },
+ { id: 'Landing', label: 'Landingpage', desc: 'Optimiert für Marketing-Kampagnen.' },
+ { id: 'Legal', label: 'Rechtliches', desc: 'Impressum & Datenschutz.' },
+];
+
const FEATURE_OPTIONS = [
- { id: 'blog', label: 'Blog / Magazin', desc: 'Regelmäßige Artikel und Beiträge.' },
+ { id: 'blog_news', label: 'Blog / News', desc: 'Ein Bereich für aktuelle Beiträge und Neuigkeiten.' },
{ 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.' },
@@ -83,36 +122,180 @@ const FUNCTION_OPTIONS = [
{ 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.' },
];
+const API_OPTIONS = [
+ { id: 'crm', label: 'CRM System', desc: 'HubSpot, Salesforce, Pipedrive etc.' },
+ { id: 'erp', label: 'ERP / Warenwirtschaft', desc: 'SAP, Microsoft Dynamics, Xentral etc.' },
+ { id: 'stripe', label: 'Stripe / Payment', desc: 'Zahlungsabwicklung und Abonnements.' },
+ { id: 'newsletter', label: 'Newsletter / Marketing', desc: 'Mailchimp, Brevo, ActiveCampaign etc.' },
+ { id: 'ecommerce', label: 'E-Commerce / Shop', desc: 'Shopify, WooCommerce, Shopware Sync.' },
+ { id: 'social', label: 'Social Media Sync', desc: 'Automatisierte Posts oder Feeds.' },
+];
+
+const ASSET_OPTIONS = [
+ { id: 'logo', label: 'Logo', desc: 'Vektordatei Ihres Logos.' },
+ { id: 'styleguide', label: 'Styleguide', desc: 'Farben, Schriften, Design-Vorgaben.' },
+ { id: 'content_concept', label: 'Inhalts-Konzept', desc: 'Struktur und Texte sind bereits geplant.' },
+ { id: 'media', label: 'Bild/Video-Material', desc: 'Professionelles Bildmaterial vorhanden.' },
+ { id: 'icons', label: 'Icons', desc: 'Eigene Icon-Sets vorhanden.' },
+ { id: 'illustrations', label: 'Illustrationen', desc: 'Eigene Illustrationen vorhanden.' },
+ { id: 'fonts', label: 'Fonts', desc: 'Lizenzen für Hausschriften vorhanden.' },
+];
+
+const DESIGN_VIBES = [
+ {
+ id: 'minimal',
+ label: 'Minimalistisch',
+ desc: 'Viel Weißraum, klare Typografie.',
+ illustration: (
+
+
+
+
+
+ )
+ },
+ {
+ id: 'bold',
+ label: 'Mutig & Laut',
+ desc: 'Starke Kontraste, große Schriften.',
+ illustration: (
+
+
+
+
+ )
+ },
+ {
+ id: 'nature',
+ label: 'Natürlich',
+ desc: 'Sanfte Erdtöne, organische Formen.',
+ illustration: (
+
+
+
+
+ )
+ },
+ {
+ id: 'tech',
+ label: 'Technisch',
+ desc: 'Präzise Linien, dunkle Akzente.',
+ illustration: (
+
+
+
+
+
+ )
+ },
+];
+
+const HARMONIOUS_PALETTES = [
+ ['#ffffff', '#f8fafc', '#0f172a'],
+ ['#000000', '#facc15', '#ffffff'],
+ ['#fdfcfb', '#e2e8f0', '#1e293b'],
+ ['#0f172a', '#38bdf8', '#ffffff'],
+ ['#fafaf9', '#78716c', '#1c1917'],
+ ['#f0fdf4', '#16a34a', '#064e3b'],
+ ['#fff7ed', '#ea580c', '#7c2d12'],
+ ['#f5f3ff', '#7c3aed', '#2e1065'],
+];
+
+function AnimatedNumber({ value }: { value: number }) {
+ const spring = useSpring(value, { stiffness: 50, damping: 20 });
+ const display = useTransform(spring, (v) => Math.round(v).toLocaleString());
+ const ref = useRef
(null);
+
+ useEffect(() => {
+ spring.set(value);
+ }, [value, spring]);
+
+ useEffect(() => {
+ return display.on('change', (v) => {
+ if (ref.current) ref.current.textContent = v;
+ });
+ }, [display]);
+
+ return {value.toLocaleString()} ;
+}
+
export function ContactForm() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
const [stepIndex, setStepIndex] = useState(0);
const [state, setState] = useState(initialState);
const [isSubmitted, setIsSubmitted] = useState(false);
+ const formContainerRef = useRef(null);
+
+ // URL Binding
+ useEffect(() => {
+ const step = searchParams.get('step');
+ if (step) setStepIndex(parseInt(step));
+
+ const config = searchParams.get('config');
+ if (config) {
+ try {
+ const decoded = JSON.parse(decodeURIComponent(escape(atob(config))));
+ setState(s => ({ ...s, ...decoded }));
+ } catch (e) {
+ console.error("Failed to decode config", e);
+ }
+ }
+ }, []);
+
+ useEffect(() => {
+ const params = new URLSearchParams(searchParams);
+ params.set('step', stepIndex.toString());
+
+ const configData = {
+ projectType: state.projectType,
+ selectedPages: state.selectedPages,
+ features: state.features,
+ functions: state.functions,
+ apiSystems: state.apiSystems,
+ cmsSetup: state.cmsSetup,
+ languagesCount: state.languagesCount,
+ deadline: state.deadline,
+ designVibe: state.designVibe,
+ colorScheme: state.colorScheme
+ };
+
+ const stateString = btoa(unescape(encodeURIComponent(JSON.stringify(configData))));
+ params.set('config', stateString);
+
+ router.replace(`?${params.toString()}`, { scroll: false });
+ }, [state.projectType, state.selectedPages, state.features, state.functions, state.apiSystems, state.cmsSetup, state.languagesCount, state.deadline, state.designVibe, state.colorScheme, stepIndex]);
+
+ const totalPagesCount = useMemo(() => {
+ return state.selectedPages.length + state.otherPages.length;
+ }, [state.selectedPages, state.otherPages]);
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 += totalPagesCount * PRICING.PAGE;
+ total += (state.features.length + state.otherFeatures.length) * PRICING.FEATURE;
+ total += (state.functions.length + state.otherFunctions.length) * PRICING.FUNCTION;
total += state.complexInteractions * PRICING.COMPLEX_INTERACTION;
total += state.newDatasets * PRICING.NEW_DATASET;
- total += state.adjustDatasets * PRICING.ADJUST_DATASET;
- total += state.apiIntegrations * PRICING.API_INTEGRATION;
+ total += (state.apiSystems.length + state.otherTech.length) * 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;
+ total += (state.features.length + state.otherFeatures.length) * PRICING.CMS_CONNECTION_PER_FEATURE;
}
- return total;
- }, [state]);
+ // Multi-language factor (e.g. +20% per additional language)
+ if (state.languagesCount > 1) {
+ total *= (1 + (state.languagesCount - 1) * 0.2);
+ }
+
+ return Math.round(total);
+ }, [state, totalPagesCount]);
const monthlyPrice = useMemo(() => {
if (state.projectType !== 'website') return 0;
@@ -123,517 +306,702 @@ export function ContactForm() {
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 toggleItem = (list: string[], id: string) => {
+ return list.includes(id) ? list.filter(i => i !== id) : [...list, id];
};
- const toggleFunction = (id: string) => {
- setState(s => ({
- ...s,
- functions: s.functions.includes(id)
- ? s.functions.filter(f => f !== id)
- : [...s.functions, id]
- }));
- };
+ const scrollToTop = () => {
+ if (formContainerRef.current) {
+ const offset = 120;
+ const bodyRect = document.body.getBoundingClientRect().top;
+ const elementRect = formContainerRef.current.getBoundingClientRect().top;
+ const elementPosition = elementRect - bodyRect;
+ const offsetPosition = elementPosition - offset;
- 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);
+ window.scrollTo({
+ top: offsetPosition,
+ behavior: 'smooth'
+ });
}
- }, [activeSteps, stepIndex]);
+ };
const nextStep = () => {
if (stepIndex < activeSteps.length - 1) {
setStepIndex(stepIndex + 1);
+ setTimeout(scrollToTop, 50);
}
};
const prevStep = () => {
if (stepIndex > 0) {
setStepIndex(stepIndex - 1);
+ setTimeout(scrollToTop, 50);
}
};
- const Counter = ({
- label,
- value,
- onChange,
- description,
- }: {
- label: string;
- value: number;
- onChange: (val: number) => void;
- description?: string;
- }) => (
-
-
-
{label}
- {description &&
{description}
}
-
-
-
onChange(Math.max(0, value - 1))}
- className="w-12 h-12 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors"
- >
-
-
-
{value}
-
onChange(value + 1)}
- className="w-12 h-12 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors"
- >
-
-
-
-
- );
+ const randomizeColors = () => {
+ const palette = HARMONIOUS_PALETTES[Math.floor(Math.random() * HARMONIOUS_PALETTES.length)];
+ updateState({ colorScheme: palette });
+ };
- const Checkbox = ({
- label,
- desc,
- checked,
- onChange
+ const RepeatableList = ({
+ items,
+ onAdd,
+ onRemove,
+ placeholder
}: {
- label: string;
- desc: string;
- checked: boolean;
- onChange: () => void;
- }) => (
+ items: string[];
+ onAdd: (val: string) => void;
+ onRemove: (index: number) => void;
+ placeholder: string;
+ }) => {
+ const [input, setInput] = useState('');
+ return (
+
+
+
setInput(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ if (input.trim()) {
+ onAdd(input.trim());
+ setInput('');
+ }
+ }
+ }}
+ placeholder={placeholder}
+ className="flex-1 p-6 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-colors text-sm"
+ />
+
{
+ if (input.trim()) {
+ onAdd(input.trim());
+ setInput('');
+ }
+ }}
+ className="w-16 h-16 rounded-full bg-slate-900 text-white flex items-center justify-center hover:bg-slate-800 transition-colors shrink-0 focus:outline-none overflow-hidden relative"
+ >
+
+
+
+
+
+ {items.map((item, i) => (
+
+ {item}
+ onRemove(i)} className="text-slate-400 hover:text-slate-900 transition-colors focus:outline-none overflow-hidden relative">
+
+
+
+ ))}
+
+
+
+ );
+ };
+
+ const steps = [
+ { id: 'type', title: 'Das Ziel', description: 'Was möchten Sie realisieren?', illustration: },
+ { id: 'base', title: 'Die Seiten', description: 'Welche Seiten benötigen wir?', illustration: },
+ { id: 'features', title: 'Die Systeme', description: 'Welche inhaltlichen Bereiche planen wir?', illustration: },
+ { id: 'design', title: 'Design-Wünsche', description: 'Wie soll die Seite wirken?', illustration: },
+ { id: 'assets', title: 'Ihre Assets', description: 'Was bringen Sie bereits mit?', illustration: },
+ { id: 'functions', title: 'Die Logik', description: 'Welche Funktionen werden benötigt?', illustration: },
+ { id: 'api', title: 'Schnittstellen', description: 'Datenaustausch mit Drittsystemen.', illustration: },
+ { id: 'content', title: 'Die Pflege', description: 'Wer kümmert sich um die Daten?', illustration: },
+ { id: 'timeline', title: 'Zeitplan', description: 'Wann soll das Projekt live gehen?', 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[8], steps[9]];
+ }, [state.projectType]);
+
+ useEffect(() => {
+ if (stepIndex >= activeSteps.length) setStepIndex(activeSteps.length - 1);
+ }, [activeSteps, stepIndex]);
+
+ const Checkbox = ({ label, desc, checked, onChange }: { label: string; desc: string; checked: boolean; onChange: () => void }) => (
{checked && }
-
{label}
-
{desc}
+
{label}
+
{desc}
);
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.' },
+ { id: 'website', label: 'Website', desc: 'Klassische Webpräsenz, Portfolio oder Blog.', illustration:
},
+ { id: 'app', label: 'App / Software', desc: 'Internes Tool, Dashboard oder Prozess-Logik.', illustration:
},
].map((type) => (
updateState({ projectType: type.id as ProjectType })}
- className={`p-10 rounded-[2.5rem] border-2 text-left transition-all duration-300 ${
- state.projectType === type.id
- ? 'border-slate-900 bg-slate-900 text-white'
- : 'border-slate-100 bg-white hover:border-slate-200'
+ className={`p-10 rounded-[2.5rem] border-2 text-left transition-all duration-300 focus:outline-none overflow-hidden relative ${
+ state.projectType === type.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-100 bg-white hover:border-slate-200'
}`}
>
- {type.label}
- {type.desc}
+ {type.illustration}
+ {type.label}
+ {type.desc}
))}
);
-
- case 'structure':
+ case 'base':
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."
- />
+
+ {PAGE_SAMPLES.map(p => (
+ updateState({ selectedPages: toggleItem(state.selectedPages, p.id) })}
+ />
+ ))}
+
+
+
+
Weitere Seiten?
+
updateState({ otherPages: [...state.otherPages, v] })}
+ onRemove={(i) => updateState({ otherPages: state.otherPages.filter((_, idx) => idx !== i) })}
+ placeholder="z.B. Team-Detail, FAQ..."
+ />
+
+
+
Sitemap hochladen (optional)
+
{ e.preventDefault(); e.stopPropagation(); }}
+ onDrop={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const file = e.dataTransfer.files?.[0];
+ if (file) updateState({ sitemapFile: file });
+ }}
+ onClick={() => document.getElementById('sitemap-upload')?.click()}
+ >
+
{
+ const file = e.target.files?.[0];
+ if (file) updateState({ sitemapFile: file });
+ }} />
+ {state.sitemapFile ? (
+
+
+ {state.sitemapFile.name}
+ { e.stopPropagation(); updateState({ sitemapFile: null }); }} className="p-1 hover:bg-slate-200 rounded-full transition-colors focus:outline-none overflow-hidden relative">
+
+ ) : (
+ <>
+
+
Sitemap hierher ziehen oder klicken
+ >
+ )}
+
+
+
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.
+
Eine Seite ist ein eigenständiges Layout. Wenn Sie 10 Leistungen haben, die alle das gleiche Layout nutzen, zählt das als 1 Seite plus ein "System-Modul" für die Verwaltung der Leistungen.
);
-
case 'features':
return (
-
- {FEATURE_OPTIONS.map(opt => (
-
toggleFeature(opt.id)}
+
+
+ {FEATURE_OPTIONS.map(opt => (
+ updateState({ features: toggleItem(state.features, opt.id) })}
+ />
+ ))}
+
+
+
Weitere inhaltliche Bereiche?
+
updateState({ otherFeatures: [...state.otherFeatures, v] })}
+ onRemove={(i) => updateState({ otherFeatures: state.otherFeatures.filter((_, idx) => idx !== i) })}
+ placeholder="z.B. Partner-Portal, Download-Center..."
/>
- ))}
+
);
+ case 'design':
+ return (
+
+
+
Design-Vibe wählen
+
+ {DESIGN_VIBES.map(vibe => (
+
updateState({ designVibe: vibe.id })}
+ className={`p-6 rounded-[2rem] border-2 text-left transition-all duration-300 focus:outline-none overflow-hidden relative group ${
+ state.designVibe === vibe.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-100 bg-white hover:border-slate-200'
+ }`}
+ >
+
+
+ {vibe.illustration}
+
+
{vibe.label}
+
+ {vibe.desc}
+
+ ))}
+
+
+
+
+
Farbschema
+
+ Farbschema würfeln
+
+
+
+ {state.colorScheme.map((color, i) => (
+
+
+ {
+ const newColors = [...state.colorScheme];
+ newColors[i] = e.target.value;
+ updateState({ colorScheme: newColors });
+ }}
+ className="absolute inset-0 w-[200%] h-[200%] -translate-x-1/4 -translate-y-1/4 cursor-pointer"
+ />
+
+
{color}
+
+ ))}
+
updateState({ colorScheme: [...state.colorScheme, '#000000'] })}
+ className="w-16 h-16 rounded-2xl border-2 border-dashed border-slate-200 flex items-center justify-center text-slate-300 hover:border-slate-900 hover:text-slate-900 transition-colors focus:outline-none overflow-hidden relative"
+ >
+
+
+
+
+
+
+
Referenz-Webseiten
+
updateState({ references: [...state.references, v] })}
+ onRemove={(i) => updateState({ references: state.references.filter((_, idx) => idx !== i) })}
+ placeholder="https://..."
+ />
+
+
+
+
+ );
+ case 'assets':
+ return (
+
+
+ {ASSET_OPTIONS.map(opt => (
+ updateState({ assets: toggleItem(state.assets, opt.id) })}
+ />
+ ))}
+
+
+
Weitere Materialien?
+
updateState({ otherAssets: [...state.otherAssets, v] })}
+ onRemove={(i) => updateState({ otherAssets: state.otherAssets.filter((_, idx) => idx !== i) })}
+ placeholder="z.B. Stock-Fotos, Video-Footage, Präsentationen..."
+ />
+
+
+ );
case 'functions':
return (
-
- {FUNCTION_OPTIONS.map(opt => (
-
toggleFunction(opt.id)}
+
+
+ {FUNCTION_OPTIONS.map(opt => (
+ updateState({ functions: toggleItem(state.functions, opt.id) })}
+ />
+ ))}
+
+
+
Weitere Funktionen?
+
updateState({ otherFunctions: [...state.otherFunctions, v] })}
+ onRemove={(i) => updateState({ otherFunctions: state.otherFunctions.filter((_, idx) => idx !== i) })}
+ placeholder="z.B. Login-Bereich, Buchungssystem..."
/>
- ))}
+
);
-
- case 'experience':
+ case 'api':
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?"
- />
+
+
+
+
+
Wichtig zu wissen
+
Ich biete diese Drittsysteme nicht selbst an, sondern entwickle die Schnittstelle (API) , damit Ihre Website nahtlos mit ihnen kommunizieren kann.
+
+
+
+ {API_OPTIONS.map(opt => (
+ updateState({ apiSystems: toggleItem(state.apiSystems, opt.id) })}
+ />
+ ))}
+
+
+
Weitere Systeme?
+
updateState({ otherTech: [...state.otherTech, v] })}
+ onRemove={(i) => updateState({ otherTech: state.otherTech.filter((_, idx) => idx !== i) })}
+ placeholder="z.B. Personio, DATEV, Salesforce..."
+ />
+
);
-
case 'content':
return (
-
+
-
Selbst verwalten (CMS)
-
Möchten Sie Texte und Bilder jederzeit selbst über eine einfache Oberfläche ändern können?
+
Inhalte selbst verwalten (CMS)
+
Möchten Sie Datensätze (z.B. Blogartikel, Produkte) selbst über eine einfache Oberfläche pflegen?
updateState({ cmsSetup: !state.cmsSetup })}
- className={`w-16 h-9 rounded-full transition-colors relative ${state.cmsSetup ? 'bg-slate-900' : 'bg-slate-200'}`}
+ className={`w-16 h-9 rounded-full transition-colors relative focus:outline-none overflow-hidden relative ${state.cmsSetup ? 'bg-slate-900' : 'bg-slate-200'}`}
>
-
updateState({ newDatasets: v })}
- description="Für wie viele Datensätze (z.B. Blogartikel, Produkte) soll ich die initiale Befüllung übernehmen?"
- />
+
+
Wie oft ändern sich Ihre Inhalte?
+
+ {[
+ { id: 'low', label: 'Selten', desc: 'Wenige Male im Jahr.' },
+ { id: 'medium', label: 'Regelmäßig', desc: 'Monatliche Updates.' },
+ { id: 'high', label: 'Häufig', desc: 'Wöchentlich oder täglich.' },
+ ].map(opt => (
+
updateState({ expectedAdjustments: opt.id })}
+ className={`p-4 rounded-2xl border-2 text-left transition-all focus:outline-none overflow-hidden relative ${
+ state.expectedAdjustments === opt.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-200 bg-white hover:border-slate-400'
+ }`}
+ >
+ {opt.label}
+ {opt.desc}
+
+ ))}
+
+
+
+
+ Vorteil CMS
+
+
+ Volle Kontrolle über Ihre Inhalte und keine laufenden Kosten für kleine Textänderungen oder neue Blog-Beiträge.
+
+
+
+
+
+ Ohne CMS bleibt die technische Komplexität geringer und das Design ist maximal geschützt vor ungewollten Änderungen.
+
+
+
+
+
+ {state.functions.includes('i18n') && (
+
+
Mehrsprachigkeit
+
Wie viele Sprachen soll die Website unterstützen?
+
+
updateState({ languagesCount: Math.max(1, state.languagesCount - 1) })} className="w-12 h-12 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none overflow-hidden relative">
+
{state.languagesCount}
+
updateState({ languagesCount: state.languagesCount + 1 })} className="w-12 h-12 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none overflow-hidden relative">
+
+
+ )}
+
+
+
+
Inhalte einpflegen
+
Für wie viele Datensätze soll ich die initiale Befüllung übernehmen?
+
+
+
updateState({ newDatasets: Math.max(0, state.newDatasets - 1) })} className="w-12 h-12 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none overflow-hidden relative">
+
{state.newDatasets}
+
updateState({ newDatasets: state.newDatasets + 1 })} className="w-12 h-12 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none overflow-hidden relative">
+
+
+
+ );
+ case 'timeline':
+ return (
+
+
+ {[
+ { id: 'asap', label: 'So schnell wie möglich', desc: 'Priorisierter Start gewünscht.' },
+ { id: '2-3-months', label: 'In 2-3 Monaten', desc: 'Normaler Projektvorlauf.' },
+ { id: '3-6-months', label: 'In 3-6 Monaten', desc: 'Langfristige Planung.' },
+ { id: 'flexible', label: 'Flexibel', desc: 'Kein fester Termindruck.' },
+ ].map(opt => (
+
updateState({ deadline: opt.id })}
+ className={`p-8 rounded-[2rem] border-2 text-left transition-all focus:outline-none overflow-hidden relative ${
+ state.deadline === opt.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-100 bg-white hover:border-slate-200'
+ }`}
+ >
+ {opt.label}
+ {opt.desc}
+
+ ))}
+
+ {state.deadline === 'asap' && (
+
+
+
+ Hinweis: Bei sehr kurzfristigen Deadlines kann ein Express-Zuschlag anfallen, um die Kapazitäten entsprechend zu priorisieren.
+
+
+ )}
);
-
case 'contact':
return (
);
-
- default:
- return null;
+ default: return null;
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
- if (stepIndex === activeSteps.length - 1) {
- setIsSubmitted(true);
- } else {
- nextStep();
- }
+ if (stepIndex === activeSteps.length - 1) setIsSubmitted(true);
+ else nextStep();
};
if (isSubmitted) {
return (
-
-
-
-
-
-
Anfrage gesendet!
-
- Vielen Dank, {state.name.split(' ')[0]}. Ich habe Ihre Konfiguration erhalten und melde mich innerhalb von 24 Stunden bei Ihnen.
-
-
- { setIsSubmitted(false); setStepIndex(0); setState(initialState); }}
- className="text-slate-400 hover:text-white transition-colors underline underline-offset-8 text-lg"
- >
- Neue Anfrage starten
-
+
+
+ Anfrage gesendet! Vielen Dank, {state.name.split(' ')[0]}. Ich melde mich innerhalb von 24 Stunden bei Ihnen.
+ { setIsSubmitted(false); setStepIndex(0); setState(initialState); }} className="text-slate-400 hover:text-white transition-colors underline underline-offset-8 text-lg focus:outline-none overflow-hidden relative">Neue Anfrage starten
);
}
return (
-
+
- {/* Progress Header */}
-
- {activeSteps[stepIndex].illustration}
-
+
{activeSteps[stepIndex].illustration}
Schritt {stepIndex + 1} von {activeSteps.length}
-
{activeSteps[stepIndex].title}
+
{activeSteps[stepIndex].title}
{activeSteps[stepIndex].description}
- {activeSteps.map((_, i) => (
-
(
+
+ onClick={() => i < stepIndex && setStepIndex(i)}
+ title={step.title}
+ disabled={i >= stepIndex}
+ className={`h-1.5 flex-1 rounded-full transition-all duration-700 relative group ${i <= stepIndex ? 'bg-slate-900' : 'bg-slate-100'} ${i < stepIndex ? 'cursor-pointer' : 'cursor-default'}`}
+ >
+ {i < stepIndex && (
+
+ {step.title}
+
+ )}
+
))}
+
+ {stepIndex > 0 ? (
+
+ Zurück
+
+ ) :
}
+ {stepIndex < activeSteps.length - 1 && (
+
+ Weiter
+
+ )}
+
+
-
-
- {renderStepContent()}
-
-
-
+ {renderStepContent()}
- {stepIndex > 0 ? (
-
- Zurück
-
- ) :
}
-
- {stepIndex < activeSteps.length - 1 ? (
-
- Weiter
-
- ) : (
-
- Anfrage senden
-
- )}
+ {stepIndex > 0 ? (
Zurück) :
}
+ {stepIndex < activeSteps.length - 1 ? (
Weiter ) : (
Anfrage senden )}
-
-
-
-
-
Kalkulation
-
-
Basierend auf Ihren aktuellen Angaben.
-
-
+
Kalkulation Unverbindliche Schätzung.
{state.projectType === 'website' ? (
<>
-
- Basis Website
- {PRICING.BASE_WEBSITE.toLocaleString()} €
-
-
+
Basis Website {PRICING.BASE_WEBSITE.toLocaleString()} €
- {state.pages > 0 && (
-
- {state.pages}x Seiten-Layout
- {(state.pages * PRICING.PAGE).toLocaleString()} €
-
- )}
- {state.features.length > 0 && (
-
- {state.features.length}x System-Modul
- {(state.features.length * PRICING.FEATURE).toLocaleString()} €
-
- )}
- {state.functions.length > 0 && (
-
- {state.functions.length}x Logik-Funktion
- {(state.functions.length * PRICING.FUNCTION).toLocaleString()} €
-
- )}
- {state.visualStaging > 0 && (
-
- {state.visualStaging}x Design-Inszenierung
- {(state.visualStaging * PRICING.VISUAL_STAGING).toLocaleString()} €
-
- )}
- {state.complexInteractions > 0 && (
-
- {state.complexInteractions}x Interaktion
- {(state.complexInteractions * PRICING.COMPLEX_INTERACTION).toLocaleString()} €
-
- )}
- {state.cmsSetup && (
-
- CMS Setup & Anbindung
- {(PRICING.CMS_SETUP + state.features.length * PRICING.CMS_CONNECTION_PER_FEATURE).toLocaleString()} €
-
- )}
-
-
-
-
-
Gesamt
-
-
{totalPrice.toLocaleString()} €
-
Einmalig / Netto
-
-
-
-
-
-
- Betrieb & Hosting
- {monthlyPrice.toLocaleString()} € / Monat
-
-
-
-
Inklusive Hosting, Sicherheitsupdates, Backups und Analytics-Reports.
-
+ {totalPagesCount > 0 && (
{totalPagesCount}x Seite {(totalPagesCount * PRICING.PAGE).toLocaleString()} €
)}
+ {state.features.length + state.otherFeatures.length > 0 && (
{state.features.length + state.otherFeatures.length}x System-Modul {((state.features.length + state.otherFeatures.length) * PRICING.FEATURE).toLocaleString()} €
)}
+ {state.functions.length + state.otherFunctions.length > 0 && (
{state.functions.length + state.otherFunctions.length}x Logik-Funktion {((state.functions.length + state.otherFunctions.length) * PRICING.FUNCTION).toLocaleString()} €
)}
+ {state.apiSystems.length + state.otherTech.length > 0 && (
{state.apiSystems.length + state.otherTech.length}x API Sync {((state.apiSystems.length + state.otherTech.length) * PRICING.API_INTEGRATION).toLocaleString()} €
)}
+ {state.cmsSetup && (
CMS Setup & Anbindung {(PRICING.CMS_SETUP + (state.features.length + state.otherFeatures.length) * PRICING.CMS_CONNECTION_PER_FEATURE).toLocaleString()} €
)}
+ {state.newDatasets > 0 && (
{state.newDatasets}x Inhalte einpflegen {(state.newDatasets * PRICING.NEW_DATASET).toLocaleString()} €
)}
+ {state.languagesCount > 1 && (
Mehrsprachigkeit ({state.languagesCount}x) +{(totalPrice - (totalPrice / (1 + (state.languagesCount - 1) * 0.2))).toLocaleString()} €
)}
+
+
Betrieb & Hosting {monthlyPrice.toLocaleString()} € / Monat
Inklusive Hosting, Sicherheitsupdates, Backups und Analytics-Reports.
>
- ) : (
-
-
-
-
-
-
- Apps und Individual-Software werden nach tatsächlichem Aufwand abgerechnet.
-
-
{PRICING.APP_HOURLY} € / Std.
-
-
- )}
+ ) : (
Apps und Individual-Software werden nach tatsächlichem Aufwand abgerechnet.
{PRICING.APP_HOURLY} € / Std.
)}
-
-
- Ein verbindliches Angebot erstelle ich nach einem persönlichen Gespräch.
-
+
Ein verbindliches Angebot erstelle ich nach einem persönlichen Gespräch.