diff --git a/public/favicon.svg b/public/favicon.svg index f157bd1..d0d6bf5 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,9 +1 @@ - - - - + \ No newline at end of file diff --git a/src/assets/header.webp b/src/assets/header.webp new file mode 100755 index 0000000..1980a59 Binary files /dev/null and b/src/assets/header.webp differ diff --git a/src/assets/logo/Favicon.png b/src/assets/logo/Favicon.png new file mode 100644 index 0000000..1846225 Binary files /dev/null and b/src/assets/logo/Favicon.png differ diff --git a/src/assets/logo/Favicon@2x.png b/src/assets/logo/Favicon@2x.png new file mode 100644 index 0000000..23e58ef Binary files /dev/null and b/src/assets/logo/Favicon@2x.png differ diff --git a/src/assets/logo/Icon Black Transparent.png b/src/assets/logo/Icon Black Transparent.png new file mode 100644 index 0000000..1a2f7f5 Binary files /dev/null and b/src/assets/logo/Icon Black Transparent.png differ diff --git a/src/assets/logo/Icon Black Transparent.svg b/src/assets/logo/Icon Black Transparent.svg new file mode 100644 index 0000000..96ee435 --- /dev/null +++ b/src/assets/logo/Icon Black Transparent.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/logo/Icon Black Transparent@2x.png b/src/assets/logo/Icon Black Transparent@2x.png new file mode 100644 index 0000000..b210836 Binary files /dev/null and b/src/assets/logo/Icon Black Transparent@2x.png differ diff --git a/src/assets/logo/Icon Black.png b/src/assets/logo/Icon Black.png new file mode 100644 index 0000000..e2d09fe Binary files /dev/null and b/src/assets/logo/Icon Black.png differ diff --git a/src/assets/logo/Icon Black@2x.png b/src/assets/logo/Icon Black@2x.png new file mode 100644 index 0000000..13ba531 Binary files /dev/null and b/src/assets/logo/Icon Black@2x.png differ diff --git a/src/assets/logo/Icon White Transparent.png b/src/assets/logo/Icon White Transparent.png new file mode 100644 index 0000000..e4f0f78 Binary files /dev/null and b/src/assets/logo/Icon White Transparent.png differ diff --git a/src/assets/logo/Icon White Transparent.svg b/src/assets/logo/Icon White Transparent.svg new file mode 100644 index 0000000..d285a05 --- /dev/null +++ b/src/assets/logo/Icon White Transparent.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/logo/Icon White Transparent@2x.png b/src/assets/logo/Icon White Transparent@2x.png new file mode 100644 index 0000000..b84eced Binary files /dev/null and b/src/assets/logo/Icon White Transparent@2x.png differ diff --git a/src/assets/logo/Icon White.png b/src/assets/logo/Icon White.png new file mode 100644 index 0000000..9ec122d Binary files /dev/null and b/src/assets/logo/Icon White.png differ diff --git a/src/assets/logo/Icon White@2x.png b/src/assets/logo/Icon White@2x.png new file mode 100644 index 0000000..6c72284 Binary files /dev/null and b/src/assets/logo/Icon White@2x.png differ diff --git a/src/assets/logo/Logo Black Transparent.png b/src/assets/logo/Logo Black Transparent.png new file mode 100644 index 0000000..d9bb545 Binary files /dev/null and b/src/assets/logo/Logo Black Transparent.png differ diff --git a/src/assets/logo/Logo Black Transparent.svg b/src/assets/logo/Logo Black Transparent.svg new file mode 100644 index 0000000..808786b --- /dev/null +++ b/src/assets/logo/Logo Black Transparent.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/logo/Logo Black Transparent@2x.png b/src/assets/logo/Logo Black Transparent@2x.png new file mode 100644 index 0000000..6a7285a Binary files /dev/null and b/src/assets/logo/Logo Black Transparent@2x.png differ diff --git a/src/assets/logo/Logo Black.png b/src/assets/logo/Logo Black.png new file mode 100644 index 0000000..0aaba09 Binary files /dev/null and b/src/assets/logo/Logo Black.png differ diff --git a/src/assets/logo/Logo Black.svg b/src/assets/logo/Logo Black.svg new file mode 100644 index 0000000..ec7a97b --- /dev/null +++ b/src/assets/logo/Logo Black.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/logo/Logo Black@2x.png b/src/assets/logo/Logo Black@2x.png new file mode 100644 index 0000000..b59ae9a Binary files /dev/null and b/src/assets/logo/Logo Black@2x.png differ diff --git a/src/assets/logo/Logo White Transparent.png b/src/assets/logo/Logo White Transparent.png new file mode 100644 index 0000000..14361aa Binary files /dev/null and b/src/assets/logo/Logo White Transparent.png differ diff --git a/src/assets/logo/Logo White Transparent.svg b/src/assets/logo/Logo White Transparent.svg new file mode 100644 index 0000000..daf4f2a --- /dev/null +++ b/src/assets/logo/Logo White Transparent.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/logo/Logo White Transparent@2x.png b/src/assets/logo/Logo White Transparent@2x.png new file mode 100644 index 0000000..47324be Binary files /dev/null and b/src/assets/logo/Logo White Transparent@2x.png differ diff --git a/src/assets/logo/Logo White.png b/src/assets/logo/Logo White.png new file mode 100644 index 0000000..72ea894 Binary files /dev/null and b/src/assets/logo/Logo White.png differ diff --git a/src/assets/logo/Logo White@2x.png b/src/assets/logo/Logo White@2x.png new file mode 100644 index 0000000..67ddb9b Binary files /dev/null and b/src/assets/logo/Logo White@2x.png differ diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx index 9232f68..a2bd637 100644 --- a/src/components/ContactForm.tsx +++ b/src/components/ContactForm.tsx @@ -3,237 +3,40 @@ import * as React from '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, Download, Share2 } from 'lucide-react'; -import dynamic from 'next/dynamic'; +import { motion, AnimatePresence } from 'framer-motion'; +import { ChevronRight, ChevronLeft, Send, Check, Share2 } from 'lucide-react'; import * as QRCode from 'qrcode'; -import { EstimationPDF } from './EstimationPDF'; + +import { FormState, Step, ProjectType } from './ContactForm/types'; +import { PRICING, initialState } from './ContactForm/constants'; +import { PriceCalculation } from './ContactForm/components/PriceCalculation'; +import { ShareModal } from './ShareModal'; + +// Steps +import { TypeStep } from './ContactForm/steps/TypeStep'; +import { BaseStep } from './ContactForm/steps/BaseStep'; +import { FeaturesStep } from './ContactForm/steps/FeaturesStep'; +import { DesignStep } from './ContactForm/steps/DesignStep'; +import { AssetsStep } from './ContactForm/steps/AssetsStep'; +import { FunctionsStep } from './ContactForm/steps/FunctionsStep'; +import { ApiStep } from './ContactForm/steps/ApiStep'; +import { ContentStep } from './ContactForm/steps/ContentStep'; +import { LanguageStep } from './ContactForm/steps/LanguageStep'; +import { TimelineStep } from './ContactForm/steps/TimelineStep'; +import { ContactStep } from './ContactForm/steps/ContactStep'; + import { - ConceptWebsite, ConceptTarget, + ConceptWebsite, ConceptPrototyping, + ConceptCommunication, ConceptSystem, ConceptCode, - ConceptCommunication, - ConceptAutomation, - ConceptPrice, - HeroArchitecture + ConceptAutomation, + ConceptPrice, + HeroArchitecture } from './Landing/ConceptIllustrations'; -// Dynamically import PDF components to avoid SSR issues -const PDFDownloadLink = dynamic( - () => import('@react-pdf/renderer').then((mod) => mod.PDFDownloadLink), - { ssr: false } -); - -// Pricing constants from PRICING.md -const PRICING = { - BASE_WEBSITE: 6000, - PAGE: 800, - FEATURE: 2000, - FUNCTION: 1000, - COMPLEX_INTERACTION: 1500, - NEW_DATASET: 400, - 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; - selectedPages: string[]; - otherPages: string[]; - features: string[]; - otherFeatures: string[]; - functions: string[]; - otherFunctions: string[]; - apiSystems: string[]; - otherTech: string[]; - assets: string[]; - otherAssets: string[]; - complexInteractions: number; - newDatasets: number; - cmsSetup: boolean; - 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', - selectedPages: ['Home'], - otherPages: [], - features: [], - otherFeatures: [], - functions: [], - otherFunctions: [], - apiSystems: [], - otherTech: [], - assets: [], - otherAssets: [], - complexInteractions: 0, - newDatasets: 0, - cmsSetup: false, - 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_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: '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: '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: 'hr', label: 'HR / Recruiting', desc: 'Personio, Workday, Recruitee etc.' }, - { id: 'realestate', label: 'Immobilien', desc: 'OpenImmo, FlowFact, Immowelt Sync.' }, - { id: 'calendar', label: 'Termine / Booking', desc: 'Calendly, Shore, Doctolib etc.' }, - { 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(); @@ -242,6 +45,8 @@ export function ContactForm() { const [isSubmitted, setIsSubmitted] = useState(false); const [isClient, setIsClient] = useState(false); const [qrCodeData, setQrCodeData] = useState(''); + const [isShareModalOpen, setIsShareModalOpen] = useState(false); + const [hoveredStep, setHoveredStep] = useState(null); const formContainerRef = useRef(null); useEffect(() => { @@ -365,86 +170,7 @@ export function ContactForm() { } }; - const randomizeColors = () => { - const palette = HARMONIOUS_PALETTES[Math.floor(Math.random() * HARMONIOUS_PALETTES.length)]; - // Maintain length if user added more colors - let finalPalette = [...palette]; - if (state.colorScheme.length > palette.length) { - const diff = state.colorScheme.length - palette.length; - for(let i=0; i 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" - /> - -
-
- - {items.map((item, i) => ( - - {item} - - - ))} - -
-
- ); - }; - - const steps = [ + const steps: Step[] = [ { 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: }, @@ -453,510 +179,45 @@ export function ContactForm() { { 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: 'language', title: 'Sprachen', description: 'Globale Reichweite planen.', 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: }, + { id: 'contact', title: 'Abschluss', 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]]; + return [steps[0], steps[9], steps[10]]; }, [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 }) => ( - - ); - const renderStepContent = () => { const currentStep = activeSteps[stepIndex]; switch (currentStep.id) { case 'type': - return ( -
- {[ - { 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) => ( - - ))} -
- ); + return ; case 'base': - return ( -
-
- {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} - -
- ) : ( - <> - -

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 Seite plus ein "System-Modul" für die Verwaltung der Leistungen.

-
-
-
- ); + return ; case 'features': - return ( -
-
- {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..." - /> -
-
- ); + return ; case 'design': - return ( -
-
-

Design-Vibe wählen

-
- {DESIGN_VIBES.map(vibe => ( - - ))} -
-
- -
-
-

Farbschema

- -
-
- {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} -
- ))} - -
-
- -
-

Referenz-Webseiten

- updateState({ references: [...state.references, v] })} - onRemove={(i) => updateState({ references: state.references.filter((_, idx) => idx !== i) })} - placeholder="https://..." - /> -
- -
-

Besondere Wünsche

-