form
Some checks failed
Build & Deploy Mintel Blog / build-and-deploy (push) Failing after 56s

This commit is contained in:
2026-01-30 18:11:52 +01:00
parent 8c1a7f6b5a
commit 8124e9cc95
16 changed files with 326 additions and 313 deletions

View File

@@ -6,7 +6,7 @@ import { ContactForm } from '../../src/components/ContactForm';
export default function ContactPage() {
return (
<div className="flex flex-col gap-12 py-12 md:py-24 overflow-hidden">
<div className="flex flex-col gap-12 py-12 md:py-24">
<PageHeader
title={<>Projekt <br /><span className="text-slate-200">konfigurieren.</span></>}
description="Nutzen Sie den Konfigurator für eine erste Einschätzung oder schreiben Sie mir direkt eine Email."
@@ -15,9 +15,7 @@ export default function ContactPage() {
/>
<Section number="01" title="Konfigurator" containerVariant="wide" className="!py-12">
<Reveal delay={0.2}>
<ContactForm />
</Reveal>
<ContactForm />
</Section>
<Section number="02" title="Direkt" className="!py-12">

View File

@@ -0,0 +1,36 @@
'use client';
import * as React from 'react';
import { LucideIcon } from 'lucide-react';
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement> {
label?: string;
icon?: LucideIcon;
isTextArea?: boolean;
rows?: number;
}
export function Input({ label, icon: Icon, isTextArea, className = '', ...props }: InputProps) {
const InputComponent = isTextArea ? 'textarea' : 'input';
return (
<div className="space-y-4 w-full">
{label && (
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">
{label}
</label>
)}
<div className="relative group">
{Icon && (
<div className="absolute left-6 top-[1.8rem] -translate-y-1/2 text-black transition-colors">
<Icon size={24} />
</div>
)}
<InputComponent
{...(props as any)}
className={`w-full p-8 ${Icon ? 'pl-16' : 'px-10'} bg-white border border-slate-100 rounded-[2.5rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-xl focus:shadow-2xl ${isTextArea ? 'resize-none' : ''} ${className}`}
/>
</div>
</div>
);
}

View File

@@ -39,39 +39,56 @@ export function PriceCalculation({
const totalFunctions = state.functions.length + state.otherFunctions.length + (state.otherFunctionsCount || 0);
const totalApis = state.apiSystems.length + state.otherTech.length + (state.otherTechCount || 0);
const [pdfLoading, setPdfLoading] = React.useState(false);
const languagesCount = state.languagesList.length || 1;
return (
<div className="lg:col-span-4 lg:sticky lg:top-24">
<div className="p-10 bg-slate-50 border border-slate-100 rounded-[3rem] space-y-10">
<div className="space-y-3"><div className="flex items-center gap-3"><ConceptPrice className="w-8 h-8" /><h3 className="text-2xl font-bold text-slate-900">Kalkulation</h3></div><p className="text-sm text-slate-500 leading-relaxed">Unverbindliche Schätzung.</p></div>
<div className="lg:col-span-4 lg:sticky lg:top-32 self-start z-30">
<div className="bg-slate-50 border border-slate-100 rounded-[3rem] p-6 space-y-6">
<div className="space-y-6">
{state.projectType === 'website' ? (
<>
<div className="flex justify-between items-center py-4 border-b border-slate-200"><span className="text-slate-600 font-medium">Basis Website</span><span className="font-bold text-lg text-slate-900">{PRICING.BASE_WEBSITE.toLocaleString()} </span></div>
<div className="space-y-4 max-h-[400px] overflow-y-auto pr-2 hide-scrollbar">
<div className="space-y-4 overflow-y-auto pr-2 hide-scrollbar max-h-[120px]">
{totalPages > 0 && (<div className="flex justify-between items-center text-sm"><span className="text-slate-500">{totalPages}x Seite</span><span className="font-medium text-slate-900">{(totalPages * PRICING.PAGE).toLocaleString()} </span></div>)}
{totalFeatures > 0 && (<div className="flex justify-between items-center text-sm"><span className="text-slate-500">{totalFeatures}x System-Modul</span><span className="font-medium text-slate-900">{(totalFeatures * PRICING.FEATURE).toLocaleString()} </span></div>)}
{totalFunctions > 0 && (<div className="flex justify-between items-center text-sm"><span className="text-slate-500">{totalFunctions}x Logik-Funktion</span><span className="font-medium text-slate-900">{(totalFunctions * PRICING.FUNCTION).toLocaleString()} </span></div>)}
{state.visualStaging > 0 && (<div className="flex justify-between items-center text-sm"><span className="text-slate-500">{state.visualStaging}x Visuelle Inszenierung</span><span className="font-medium text-slate-900">{(state.visualStaging * PRICING.VISUAL_STAGING).toLocaleString()} </span></div>)}
{state.complexInteractions > 0 && (<div className="flex justify-between items-center text-sm"><span className="text-slate-500">{state.complexInteractions}x Komplexe Interaktion</span><span className="font-medium text-slate-900">{(state.complexInteractions * PRICING.COMPLEX_INTERACTION).toLocaleString()} </span></div>)}
{totalApis > 0 && (<div className="flex justify-between items-center text-sm"><span className="text-slate-500">{totalApis}x API Sync</span><span className="font-medium text-slate-900">{(totalApis * PRICING.API_INTEGRATION).toLocaleString()} </span></div>)}
{state.cmsSetup && (<div className="flex justify-between items-center text-sm"><span className="text-slate-500">CMS Setup & Anbindung</span><span className="font-medium text-slate-900">{(PRICING.CMS_SETUP + totalFeatures * PRICING.CMS_CONNECTION_PER_FEATURE).toLocaleString()} </span></div>)}
{state.newDatasets > 0 && (<div className="flex justify-between items-center text-sm"><span className="text-slate-500">{state.newDatasets}x Inhalte einpflegen</span><span className="font-medium text-slate-900">{(state.newDatasets * PRICING.NEW_DATASET).toLocaleString()} </span></div>)}
{state.languagesCount > 1 && (<div className="flex justify-between items-center text-sm text-slate-900 font-bold pt-2 border-t border-slate-100"><span className="text-slate-500">Mehrsprachigkeit ({state.languagesCount}x)</span><span>+{(totalPrice - (totalPrice / (1 + (state.languagesCount - 1) * 0.2))).toLocaleString()} </span></div>)}
{languagesCount > 1 && (<div className="flex justify-between items-center text-sm text-slate-900 font-bold pt-2 border-t border-slate-100"><span className="text-slate-500">Mehrsprachigkeit ({languagesCount}x)</span><span>+{(totalPrice - (totalPrice / (1 + (languagesCount - 1) * 0.2))).toLocaleString()} </span></div>)}
</div>
<div className="pt-4 space-y-2">
<div className="flex justify-between items-end">
<span className="text-lg font-bold text-slate-900">Gesamt</span>
<div className="text-right">
<div className="text-2xl font-bold tracking-tighter text-slate-900">
<AnimatedNumber value={totalPrice} />
</div>
</div>
</div>
</div>
<div className="pt-4 border-t border-slate-200 space-y-4">
<div className="flex justify-between items-center">
<span className="text-slate-600 font-medium text-sm">Betrieb & Hosting</span>
<span className="text-base font-bold text-slate-900">{monthlyPrice.toLocaleString()} / Monat</span>
</div>
</div>
<div className="pt-8 space-y-2"><div className="flex justify-between items-end"><span className="text-2xl font-bold text-slate-900">Gesamt</span><div className="text-right"><div className="text-4xl font-bold tracking-tighter text-slate-900"><AnimatedNumber value={totalPrice} /> </div><p className="text-[10px] text-slate-400 mt-1 uppercase tracking-widest font-bold">Einmalig / Netto</p></div></div></div>
<div className="pt-8 border-t border-slate-200 space-y-4"><div className="flex justify-between items-center"><span className="text-slate-600 font-medium">Betrieb & Hosting</span><span className="font-bold text-lg text-slate-900">{monthlyPrice.toLocaleString()} / Monat</span></div><div className="p-6 bg-white rounded-[2rem] text-xs text-slate-500 flex gap-4 leading-relaxed border border-slate-100"><Info size={18} className="shrink-0 text-slate-300" /><p>Inklusive Hosting, Sicherheitsupdates, Backups und Analytics-Reports.</p></div></div>
<div className="pt-6 space-y-4">
<div className="pt-4 space-y-4">
{isClient && (
<PDFDownloadLink
document={<EstimationPDF state={state} totalPrice={totalPrice} monthlyPrice={monthlyPrice} totalPagesCount={totalPagesCount} pricing={PRICING} qrCodeData={qrCodeData} />}
fileName={`kalkulation-${state.name.toLowerCase().replace(/\s+/g, '-') || 'projekt'}.pdf`}
className="w-full flex items-center justify-center gap-3 px-8 py-4 rounded-full border border-slate-200 text-slate-900 font-bold text-sm uppercase tracking-widest hover:bg-white hover:border-slate-900 transition-all focus:outline-none overflow-hidden relative rounded-full"
className="w-full flex items-center justify-center gap-3 px-8 py-4 rounded-full border border-slate-200 text-slate-900 font-bold text-sm uppercase tracking-widest hover:bg-white hover:border-slate-900 transition-all focus:outline-none overflow-hidden relative"
onClick={() => {
setPdfLoading(true);
setTimeout(() => setPdfLoading(false), 2000);
}}
>
{({ loading }) => (
<div className="flex items-center gap-3">
<Download size={18} />
<span>{loading ? 'PDF wird erstellt...' : 'Als PDF speichern'}</span>
<span>{loading || pdfLoading ? 'PDF wird erstellt...' : 'Als PDF speichern'}</span>
</div>
)}
</PDFDownloadLink>
@@ -81,33 +98,33 @@ export function PriceCalculation({
<button
type="button"
onClick={onShare}
className="w-full flex items-center justify-center gap-3 px-8 py-4 rounded-full bg-slate-900 text-white font-bold text-sm uppercase tracking-widest hover:bg-slate-800 transition-all shadow-xl shadow-slate-200 focus:outline-none"
className="w-full flex items-center justify-center gap-3 rounded-full bg-slate-900 text-white font-bold text-sm uppercase tracking-widest hover:bg-slate-800 transition-all focus:outline-none px-6 py-3"
>
<Share2 size={18} />
<span>Konfiguration teilen</span>
<span>Teilen</span>
</button>
)}
</div>
</>
) : (
<div className="space-y-6">
<div className="py-12 text-center space-y-6">
<div className="w-20 h-20 bg-white rounded-full flex items-center justify-center mx-auto shadow-sm">
<ConceptAutomation className="w-12 h-12" />
<div className="py-6 text-center space-y-4">
<div className="w-16 h-16 bg-white rounded-full flex items-center justify-center mx-auto border border-slate-100">
<ConceptAutomation className="w-10 h-10 text-black" />
</div>
<div className="space-y-2">
<p className="text-slate-600 text-sm leading-relaxed">Web Apps und Individual-Software werden nach tatsächlichem Aufwand abgerechnet.</p>
<p className="text-3xl font-bold text-slate-900">{PRICING.APP_HOURLY} <span className="text-lg text-slate-400 font-normal">/ Std.</span></p>
<div className="space-y-1">
<p className="text-slate-600 text-xs leading-relaxed">Web Apps werden nach Aufwand abgerechnet.</p>
<p className="text-2xl font-bold text-slate-900">{PRICING.APP_HOURLY} <span className="text-base text-slate-400 font-normal">/ Std.</span></p>
</div>
</div>
{onShare && (
<button
type="button"
onClick={onShare}
className="w-full flex items-center justify-center gap-3 px-8 py-4 rounded-full bg-slate-900 text-white font-bold text-sm uppercase tracking-widest hover:bg-slate-800 transition-all shadow-xl shadow-slate-200 focus:outline-none"
className="w-full flex items-center justify-center gap-3 px-6 py-3 rounded-full bg-slate-900 text-white font-bold text-sm uppercase tracking-widest hover:bg-slate-800 transition-all focus:outline-none"
>
<Share2 size={18} />
<span>Konfiguration teilen</span>
<span>Teilen</span>
</button>
)}
</div>

View File

@@ -4,8 +4,8 @@ import * as React from 'react';
import { FormState } from '../types';
import { Checkbox } from '../components/Checkbox';
import { RepeatableList } from '../components/RepeatableList';
import { Minus, Plus, Share2, ListPlus } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Share2, ListPlus } from 'lucide-react';
import { motion } from 'framer-motion';
import { Reveal } from '../../Reveal';
interface ApiStepProps {
@@ -32,7 +32,7 @@ export function ApiStep({ state, updateState, toggleItem }: ApiStepProps) {
<div className="space-y-8">
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Share2 size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">
@@ -44,7 +44,7 @@ export function ApiStep({ state, updateState, toggleItem }: ApiStepProps) {
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('api')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('api') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
@@ -86,7 +86,7 @@ export function ApiStep({ state, updateState, toggleItem }: ApiStepProps) {
<div className="space-y-12">
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<ListPlus size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Weitere Systeme oder eigene APIs?</h4>
@@ -98,51 +98,6 @@ export function ApiStep({ state, updateState, toggleItem }: ApiStepProps) {
placeholder="z.B. Microsoft Graph, Google Search Console, Custom REST API..."
/>
</div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 shadow-sm hover:shadow-xl transition-all duration-500"
>
<div className="flex flex-col md:flex-row justify-between items-center gap-8">
<div>
<h4 className="text-xl font-bold text-slate-900">Anzahl weiterer Schnittstellen</h4>
<p className="text-base text-slate-500 mt-1">Falls Sie weitere Integrationen planen, diese aber noch nicht benennen können.</p>
</div>
<div className="flex items-center gap-8">
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
type="button"
onClick={() => updateState({ otherTechCount: Math.max(0, state.otherTechCount - 1) })}
className="w-14 h-14 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none"
>
<Minus size={24} />
</motion.button>
<AnimatePresence mode="wait">
<motion.span
key={state.otherTechCount}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="text-5xl font-bold w-12 text-center"
>
{state.otherTechCount}
</motion.span>
</AnimatePresence>
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
type="button"
onClick={() => updateState({ otherTechCount: state.otherTechCount + 1 })}
className="w-14 h-14 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none"
>
<Plus size={24} />
</motion.button>
</div>
</div>
</motion.div>
</div>
</Reveal>
</div>

View File

@@ -31,7 +31,7 @@ export function AssetsStep({ state, updateState, toggleItem }: AssetsStepProps)
<div className="space-y-8">
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Briefcase size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Vorhandene Assets</h4>
@@ -41,7 +41,7 @@ export function AssetsStep({ state, updateState, toggleItem }: AssetsStepProps)
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('assets')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('assets') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
@@ -71,7 +71,7 @@ export function AssetsStep({ state, updateState, toggleItem }: AssetsStepProps)
<div className="space-y-12">
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<ListPlus size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Weitere vorhandene Unterlagen?</h4>
@@ -88,7 +88,7 @@ export function AssetsStep({ state, updateState, toggleItem }: AssetsStepProps)
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 shadow-sm hover:shadow-xl transition-all duration-500"
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 hover:shadow-xl transition-all duration-500"
>
<div className="flex flex-col md:flex-row justify-between items-center gap-8">
<div>

View File

@@ -6,6 +6,7 @@ import { Checkbox } from '../components/Checkbox';
import { RepeatableList } from '../components/RepeatableList';
import { motion, AnimatePresence } from 'framer-motion';
import { Minus, Plus, FileText, ListPlus } from 'lucide-react';
import { Input } from '../components/Input';
interface BaseStepProps {
state: FormState;
@@ -30,20 +31,18 @@ export function BaseStep({ state, updateState, toggleItem }: BaseStepProps) {
animate={{ opacity: 1, y: 0 }}
className="space-y-8"
>
<h4 className="text-2xl font-bold text-slate-900">Thema der Website</h4>
<input
type="text"
<Input
label="Thema der Website"
placeholder="z.B. Portfolio für Architektur, Onlineshop für Bio-Tee..."
value={state.websiteTopic}
onChange={(e) => updateState({ websiteTopic: e.target.value })}
className="w-full p-10 bg-white border border-slate-100 rounded-[3rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-xl shadow-sm focus:shadow-2xl"
/>
</motion.div>
<div className="space-y-8">
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<FileText size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Die Seiten</h4>
@@ -53,7 +52,7 @@ export function BaseStep({ state, updateState, toggleItem }: BaseStepProps) {
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('pages')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('pages') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
@@ -88,7 +87,7 @@ export function BaseStep({ state, updateState, toggleItem }: BaseStepProps) {
<div className="space-y-12">
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<ListPlus size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Weitere individuelle Seiten?</h4>
@@ -105,7 +104,7 @@ export function BaseStep({ state, updateState, toggleItem }: BaseStepProps) {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 shadow-sm hover:shadow-xl transition-all duration-500"
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 hover:shadow-xl transition-all duration-500"
>
<div className="flex flex-col md:flex-row justify-between items-center gap-8">
<div>

View File

@@ -6,6 +6,7 @@ import { EMPLOYEE_OPTIONS } from '../constants';
import { motion } from 'framer-motion';
import { Building2, Users } from 'lucide-react';
import { Reveal } from '../../Reveal';
import { Input } from '../components/Input';
interface CompanyStepProps {
state: FormState;
@@ -18,28 +19,24 @@ export function CompanyStep({ state, updateState }: CompanyStepProps) {
<Reveal width="100%" delay={0.1}>
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Building2 size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Unternehmen</h4>
</div>
<div className="space-y-4">
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">Name des Unternehmens</label>
<input
type="text"
placeholder="z.B. Muster GmbH"
value={state.companyName}
onChange={(e) => updateState({ companyName: e.target.value })}
className="w-full p-8 bg-white border border-slate-100 rounded-[3rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-xl shadow-sm focus:shadow-2xl"
/>
</div>
<Input
label="Name des Unternehmens"
placeholder="z.B. Muster GmbH"
value={state.companyName}
onChange={(e) => updateState({ companyName: e.target.value })}
/>
</div>
</Reveal>
<Reveal width="100%" delay={0.2}>
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Users size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Mitarbeiteranzahl</h4>
@@ -48,12 +45,12 @@ export function CompanyStep({ state, updateState }: CompanyStepProps) {
{EMPLOYEE_OPTIONS.map((option, index) => (
<motion.button
key={option.id}
whileHover={{ y: -5, boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }}
whileHover={{ y: -5 }}
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => updateState({ employeeCount: option.id })}
className={`p-6 rounded-[2rem] border-2 transition-all duration-300 font-bold text-lg ${
state.employeeCount === option.id ? 'border-slate-900 bg-slate-900 text-white shadow-lg' : 'border-slate-100 bg-white hover:border-slate-300 text-slate-600'
state.employeeCount === option.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-100 bg-white hover:border-slate-300 text-slate-600'
}`}
>
{option.label}

View File

@@ -5,6 +5,7 @@ import { FormState } from '../types';
import { FileText, Upload, X, User, Mail, Briefcase, MessageSquare } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Reveal } from '../../Reveal';
import { Input } from '../components/Input';
interface ContactStepProps {
state: FormState;
@@ -16,78 +17,49 @@ export function ContactStep({ state, updateState }: ContactStepProps) {
<div className="space-y-12">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<Reveal width="100%" delay={0.1}>
<div className="space-y-4">
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">Ihr Name</label>
<div className="relative group">
<div className="absolute left-6 top-1/2 -translate-y-1/2 text-slate-300 group-focus-within:text-slate-900 transition-colors">
<User size={24} />
</div>
<input
type="text"
placeholder="Max Mustermann"
required
value={state.name}
onChange={(e) => updateState({ name: e.target.value })}
className="w-full p-8 pl-16 bg-white border border-slate-100 rounded-[2.5rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-xl shadow-sm focus:shadow-2xl"
/>
</div>
</div>
<Input
label="Ihr Name"
icon={User}
placeholder="Max Mustermann"
required
value={state.name}
onChange={(e) => updateState({ name: e.target.value })}
/>
</Reveal>
<Reveal width="100%" delay={0.1}>
<div className="space-y-4">
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">Ihre Email</label>
<div className="relative group">
<div className="absolute left-6 top-1/2 -translate-y-1/2 text-slate-300 group-focus-within:text-slate-900 transition-colors">
<Mail size={24} />
</div>
<input
type="email"
placeholder="max@beispiel.de"
required
value={state.email}
onChange={(e) => updateState({ email: e.target.value })}
className="w-full p-8 pl-16 bg-white border border-slate-100 rounded-[2.5rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-xl shadow-sm focus:shadow-2xl"
/>
</div>
</div>
<Input
label="Ihre Email"
icon={Mail}
type="email"
placeholder="max@beispiel.de"
required
value={state.email}
onChange={(e) => updateState({ email: e.target.value })}
/>
</Reveal>
</div>
<Reveal width="100%" delay={0.2}>
<div className="space-y-4">
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">Ihre Rolle</label>
<div className="relative group">
<div className="absolute left-6 top-1/2 -translate-y-1/2 text-slate-300 group-focus-within:text-slate-900 transition-colors">
<Briefcase size={24} />
</div>
<input
type="text"
placeholder="z.B. CEO, Marketing Manager..."
value={state.role}
onChange={(e) => updateState({ role: e.target.value })}
className="w-full p-8 pl-16 bg-white border border-slate-100 rounded-[2.5rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-xl shadow-sm focus:shadow-2xl"
/>
</div>
</div>
<Input
label="Ihre Rolle"
icon={Briefcase}
placeholder="z.B. CEO, Marketing Manager..."
value={state.role}
onChange={(e) => updateState({ role: e.target.value })}
/>
</Reveal>
<Reveal width="100%" delay={0.3}>
<div className="space-y-4">
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">Nachricht</label>
<div className="relative group">
<div className="absolute left-6 top-10 text-slate-300 group-focus-within:text-slate-900 transition-colors">
<MessageSquare size={24} />
</div>
<textarea
placeholder="Erzählen Sie mir kurz von Ihrem Projekt..."
value={state.message}
onChange={(e) => updateState({ message: e.target.value })}
rows={5}
className="w-full p-8 pl-16 bg-white border border-slate-100 rounded-[2.5rem] focus:outline-none focus:border-slate-900 transition-all duration-500 resize-none text-xl shadow-sm focus:shadow-2xl"
/>
</div>
</div>
<Input
label="Nachricht"
icon={MessageSquare}
isTextArea
rows={5}
placeholder="Erzählen Sie mir kurz von Ihrem Projekt..."
value={state.message}
onChange={(e) => updateState({ message: e.target.value })}
/>
</Reveal>
<Reveal width="100%" delay={0.4}>
@@ -95,7 +67,7 @@ export function ContactStep({ state, updateState }: ContactStepProps) {
<p className="text-lg font-bold text-slate-900 ml-2">Dateien hochladen (optional)</p>
<div
className={`relative group border-2 border-dashed rounded-[3rem] p-12 transition-all duration-500 flex flex-col items-center justify-center gap-6 cursor-pointer min-h-[250px] ${
state.contactFiles.length > 0 ? 'border-slate-900 bg-slate-50 shadow-inner' : 'border-slate-200 hover:border-slate-400 bg-white hover:shadow-xl'
state.contactFiles.length > 0 ? 'border-slate-900 bg-slate-50' : 'border-slate-200 hover:border-slate-400 bg-white hover:shadow-xl'
}`}
onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }}
onDrop={(e) => {
@@ -126,7 +98,7 @@ export function ContactStep({ state, updateState }: ContactStepProps) {
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: idx * 0.05 }}
className="flex items-center justify-between p-5 bg-white border border-slate-100 rounded-2xl shadow-sm group/file"
className="flex items-center justify-between p-5 bg-white border border-slate-100 rounded-2xl group/file"
>
<div className="flex items-center gap-4 text-slate-900">
<div className="w-10 h-10 bg-slate-50 rounded-xl flex items-center justify-center text-slate-400 group-hover/file:text-slate-900 transition-colors">

View File

@@ -24,10 +24,10 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
return (
<div className="space-y-16">
<Reveal width="100%" delay={0.1}>
<div className="flex flex-col md:flex-row items-center justify-between p-10 bg-white border border-slate-100 rounded-[3rem] shadow-sm gap-8">
<div className="flex flex-col md:flex-row items-center justify-between p-10 bg-white border border-slate-100 rounded-[3rem] gap-8">
<div className="max-w-2xl space-y-4">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-inner">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Settings2 size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Inhalte selbst verwalten (CMS)</h4>
@@ -40,7 +40,7 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('cms')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('cms') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
@@ -49,12 +49,12 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
<button
type="button"
onClick={() => updateState({ cmsSetup: !state.cmsSetup })}
className={`w-24 h-12 rounded-full transition-all duration-500 relative focus:outline-none shadow-inner ${state.cmsSetup ? 'bg-slate-900' : 'bg-slate-200'}`}
className={`w-24 h-12 rounded-full transition-all duration-500 relative focus:outline-none ${state.cmsSetup ? 'bg-slate-900' : 'bg-slate-200'}`}
>
<motion.div
animate={{ x: state.cmsSetup ? 48 : 0 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
className="absolute top-1.5 left-1.5 w-9 h-9 bg-white rounded-full shadow-lg"
className="absolute top-1.5 left-1.5 w-9 h-9 bg-white rounded-full"
/>
</button>
</div>
@@ -64,7 +64,7 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
<Reveal width="100%" delay={0.2}>
<div className="p-10 bg-slate-50 rounded-[3rem] border border-slate-100 space-y-10">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-white rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-white rounded-2xl flex items-center justify-center text-black">
<BarChart3 size={24} />
</div>
<p className="text-xl font-bold text-slate-900">Wie oft ändern sich Ihre Inhalte?</p>
@@ -78,12 +78,12 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
].map((opt, index) => (
<motion.button
key={opt.id}
whileHover={{ y: -5, boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }}
whileHover={{ y: -5 }}
whileTap={{ scale: 0.98 }}
type="button"
onClick={() => updateState({ expectedAdjustments: opt.id })}
className={`p-6 rounded-[2rem] border-2 text-left transition-all duration-300 focus:outline-none ${
state.expectedAdjustments === opt.id ? 'border-slate-900 bg-slate-900 text-white shadow-xl' : 'border-slate-200 bg-white hover:border-slate-400'
state.expectedAdjustments === opt.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-200 bg-white hover:border-slate-400'
}`}
>
<p className={`font-bold text-lg ${state.expectedAdjustments === opt.id ? 'text-white' : 'text-slate-900'}`}>{opt.label}</p>
@@ -98,9 +98,9 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
initial={{ opacity: 0, height: 0, y: 20 }}
animate={{ opacity: 1, height: 'auto', y: 0 }}
exit={{ opacity: 0, height: 0, y: 20 }}
className="p-8 bg-amber-50 rounded-[2.5rem] border border-amber-100 flex gap-6 items-start shadow-sm"
className="p-8 bg-amber-50 rounded-[2.5rem] border border-amber-100 flex gap-6 items-start"
>
<div className="w-12 h-12 bg-white rounded-xl flex items-center justify-center text-amber-600 shadow-sm shrink-0">
<div className="w-12 h-12 bg-white rounded-xl flex items-center justify-center text-amber-600 shrink-0">
<AlertCircle size={24} />
</div>
<div className="space-y-2">
@@ -114,7 +114,7 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
</AnimatePresence>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-8">
<div className="p-8 bg-white rounded-[2.5rem] border border-slate-100 space-y-4 shadow-sm">
<div className="p-8 bg-white rounded-[2.5rem] border border-slate-100 space-y-4">
<div className="flex items-center gap-3 text-slate-900 font-bold text-sm uppercase tracking-[0.2em]">
<Zap size={18} /> Vorteil CMS
</div>
@@ -122,7 +122,7 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
Volle Kontrolle über Ihre Inhalte und keine laufenden Kosten für kleine Textänderungen oder neue Blog-Beiträge.
</p>
</div>
<div className="p-8 bg-white rounded-[2.5rem] border border-slate-100 space-y-4 shadow-sm">
<div className="p-8 bg-white rounded-[2.5rem] border border-slate-100 space-y-4">
<div className="flex items-center gap-3 text-slate-900 font-bold text-sm uppercase tracking-[0.2em]">
<AlertCircle size={18} /> Fokus Design
</div>
@@ -135,7 +135,7 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
</Reveal>
<Reveal width="100%" delay={0.3}>
<div className="flex flex-col gap-8 p-10 bg-white border border-slate-100 rounded-[3rem] shadow-sm">
<div className="flex flex-col gap-8 p-10 bg-white border border-slate-100 rounded-[3rem]">
<div className="space-y-2">
<h4 className="text-2xl font-bold text-slate-900">Inhalte einpflegen</h4>
<p className="text-lg text-slate-500 leading-relaxed">Für wie viele Datensätze soll ich die initiale Befüllung übernehmen?</p>
@@ -146,7 +146,7 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
whileTap={{ scale: 0.9 }}
type="button"
onClick={() => updateState({ newDatasets: Math.max(0, state.newDatasets - 1) })}
className="w-16 h-16 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none shadow-sm"
className="w-16 h-16 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none"
>
<Minus size={28} />
</motion.button>
@@ -166,7 +166,7 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
whileTap={{ scale: 0.9 }}
type="button"
onClick={() => updateState({ newDatasets: state.newDatasets + 1 })}
className="w-16 h-16 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none shadow-sm"
className="w-16 h-16 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center hover:border-slate-900 transition-colors focus:outline-none"
>
<Plus size={28} />
</motion.button>

View File

@@ -6,6 +6,7 @@ import { DESIGN_VIBES } from '../constants';
import { motion, AnimatePresence } from 'framer-motion';
import { Plus, X, Palette, Pipette, RefreshCw } from 'lucide-react';
import { Reveal } from '../../Reveal';
import { Input } from '../components/Input';
interface DesignStepProps {
state: FormState;
@@ -82,7 +83,7 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('design_vibe')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('design_vibe') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
@@ -93,14 +94,14 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
{DESIGN_VIBES.map((vibe, index) => (
<motion.button
key={vibe.id}
whileHover={{ y: -5, boxShadow: '0 20px 25px -5px rgb(0 0 0 / 0.1)' }}
whileHover={{ y: -5 }}
type="button"
onClick={() => updateState({ designVibe: vibe.id })}
className={`p-8 rounded-[2.5rem] border-2 text-left transition-all duration-500 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-300'
}`}
>
<div className={`w-16 h-10 mb-6 transition-transform duration-500 group-hover:scale-110 ${state.designVibe === vibe.id ? 'text-white' : 'text-slate-900'}`}>{vibe.illustration}</div>
<div className={`w-16 h-10 mb-6 transition-transform duration-500 group-hover:scale-110 ${state.designVibe === vibe.id ? 'text-white' : 'text-black'}`}>{vibe.illustration}</div>
<h4 className={`text-2xl font-bold mb-3 ${state.designVibe === vibe.id ? 'text-white' : 'text-slate-900'}`}>{vibe.label}</h4>
<p className={`text-lg leading-relaxed ${state.designVibe === vibe.id ? 'text-slate-200' : 'text-slate-500'}`}>{vibe.desc}</p>
{state.designVibe === vibe.id && (
@@ -118,45 +119,31 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
<div className="flex justify-between items-center">
<div className="space-y-1">
<h4 className="text-2xl font-bold text-slate-900">Farbschema</h4>
<p className="text-slate-500">Generieren Sie eine harmonische Palette oder definieren Sie eigene Farben.</p>
<p className="text-slate-500">Definieren Sie Ihre Markenfarben oder lassen Sie sich inspirieren.</p>
</div>
<div className="flex items-center gap-4">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
type="button"
onClick={generateHarmonicPalette}
className="flex items-center gap-2 px-6 py-3 rounded-full text-sm font-bold bg-white border border-slate-200 text-slate-900 hover:border-slate-900 transition-all"
>
<RefreshCw size={16} />
Zufall
</motion.button>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('color_scheme')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('color_scheme') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
Ich weiß es nicht
</motion.button>
</div>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('color_scheme')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
state.dontKnows?.includes('color_scheme') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
Ich weiß es nicht
</motion.button>
</div>
{/* Generator */}
<div className="space-y-6">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
type="button"
onClick={generateHarmonicPalette}
className="w-full p-8 rounded-[3rem] border-2 border-slate-100 bg-white hover:border-slate-900 transition-all duration-500 flex items-center justify-between group shadow-sm hover:shadow-xl"
>
<div className="flex items-center gap-6">
<div className="w-14 h-14 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 group-hover:rotate-180 transition-transform duration-700">
<RefreshCw size={28} />
</div>
<div className="text-left">
<p className="text-xl font-bold text-slate-900">Harmonischer Zufall</p>
<p className="text-slate-500">Erzeugt eine mathematisch abgestimmte Palette.</p>
</div>
</div>
<div className="flex h-12 w-48 rounded-xl overflow-hidden shadow-inner border border-slate-100">
{state.colorScheme.map((color, j) => (
<div key={j} className="flex-1" style={{ backgroundColor: color }} />
))}
</div>
</motion.button>
</div>
{/* Custom Picker */}
@@ -176,14 +163,14 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
exit={{ opacity: 0, scale: 0.8 }}
className="relative group"
>
<div className="relative">
<div className="relative w-24 h-24 rounded-3xl overflow-hidden border-2 border-white group-hover:scale-105 transition-transform duration-300">
<input
type="color"
value={color}
onChange={(e) => updateColor(i, e.target.value)}
className="w-24 h-24 rounded-3xl cursor-pointer border-4 border-white shadow-lg overflow-hidden transition-transform duration-300 group-hover:scale-105"
className="absolute inset-[-100%] w-[300%] h-[300%] cursor-pointer outline-none border-none appearance-none bg-transparent"
/>
<div className="absolute inset-0 rounded-3xl pointer-events-none border border-black/5" />
<div className="absolute inset-0 pointer-events-none border border-black/5 rounded-3xl" />
</div>
<div className="mt-2 text-center font-mono text-[10px] text-slate-400 uppercase">{color}</div>
{state.colorScheme.length > 1 && (
@@ -192,7 +179,7 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
whileTap={{ scale: 0.9 }}
type="button"
onClick={() => removeColor(i)}
className="absolute -top-3 -right-3 w-8 h-8 bg-white text-red-500 rounded-full flex items-center justify-center shadow-xl border border-slate-100 opacity-0 group-hover:opacity-100 transition-all duration-300 z-10"
className="absolute -top-3 -right-3 w-8 h-8 bg-white text-red-500 rounded-full flex items-center justify-center border border-slate-100 opacity-0 group-hover:opacity-100 transition-all duration-300 z-10"
>
<X size={16} strokeWidth={3} />
</motion.button>
@@ -221,16 +208,14 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
{/* Wishes */}
<Reveal width="100%" delay={0.3}>
<div className="space-y-6">
<h4 className="text-2xl font-bold text-slate-900">Individuelle Wünsche</h4>
<textarea
placeholder="Haben Sie bereits konkrete Vorstellungen oder Referenzen?"
value={state.designWishes}
onChange={(e) => updateState({ designWishes: e.target.value })}
rows={4}
className="w-full p-8 bg-white border border-slate-100 rounded-[3rem] focus:outline-none focus:border-slate-900 transition-all duration-500 resize-none text-xl shadow-sm focus:shadow-2xl"
/>
</div>
<Input
label="Individuelle Wünsche"
isTextArea
rows={4}
placeholder="Haben Sie bereits konkrete Vorstellungen oder Referenzen?"
value={state.designWishes}
onChange={(e) => updateState({ designWishes: e.target.value })}
/>
</Reveal>
</div>
);

View File

@@ -29,7 +29,7 @@ export function FeaturesStep({ state, updateState, toggleItem }: FeaturesStepPro
<div className="space-y-8">
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<LayoutGrid size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">System-Module</h4>
@@ -39,7 +39,7 @@ export function FeaturesStep({ state, updateState, toggleItem }: FeaturesStepPro
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('features')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('features') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
@@ -67,7 +67,7 @@ export function FeaturesStep({ state, updateState, toggleItem }: FeaturesStepPro
<div className="space-y-12">
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<ListPlus size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Weitere inhaltliche Module?</h4>
@@ -84,7 +84,7 @@ export function FeaturesStep({ state, updateState, toggleItem }: FeaturesStepPro
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 shadow-sm hover:shadow-xl transition-all duration-500"
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 hover:shadow-xl transition-all duration-500"
>
<div className="flex flex-col md:flex-row justify-between items-center gap-8">
<div>

View File

@@ -4,8 +4,8 @@ import * as React from 'react';
import { FormState } from '../types';
import { Checkbox } from '../components/Checkbox';
import { RepeatableList } from '../components/RepeatableList';
import { Minus, Plus, Cpu, ListPlus } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Minus, Plus, Cpu, ListPlus } from 'lucide-react';
import { Reveal } from '../../Reveal';
interface FunctionsStepProps {
@@ -32,7 +32,7 @@ export function FunctionsStep({ state, updateState, toggleItem }: FunctionsStepP
<div className="space-y-8">
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Cpu size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">
@@ -44,7 +44,7 @@ export function FunctionsStep({ state, updateState, toggleItem }: FunctionsStepP
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('functions')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('functions') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
@@ -107,7 +107,7 @@ export function FunctionsStep({ state, updateState, toggleItem }: FunctionsStepP
<div className="space-y-12">
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<ListPlus size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Weitere spezifische Wünsche?</h4>
@@ -124,7 +124,7 @@ export function FunctionsStep({ state, updateState, toggleItem }: FunctionsStepP
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 shadow-sm hover:shadow-xl transition-all duration-500"
className="p-10 bg-white border border-slate-100 rounded-[3rem] space-y-6 hover:shadow-xl transition-all duration-500"
>
<div className="flex flex-col md:flex-row justify-between items-center gap-8">
<div>

View File

@@ -2,9 +2,8 @@
import * as React from 'react';
import { FormState } from '../types';
import { Globe, Info, ListPlus } from 'lucide-react';
import { motion } from 'framer-motion';
import { RepeatableList } from '../components/RepeatableList';
import { Globe, Info, Plus, X } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Reveal } from '../../Reveal';
interface LanguageStepProps {
@@ -12,9 +11,26 @@ interface LanguageStepProps {
updateState: (updates: Partial<FormState>) => void;
}
const COMMON_LANGUAGES = [
{ id: 'de', label: 'Deutsch' },
{ id: 'en', label: 'Englisch' },
{ id: 'fr', label: 'Französisch' },
{ id: 'es', label: 'Spanisch' },
{ id: 'it', label: 'Italienisch' },
];
export function LanguageStep({ state, updateState }: LanguageStepProps) {
const basePriceExplanation = "Jede zusätzliche Sprache erhöht den Gesamtaufwand für Design, Entwicklung und Qualitätssicherung um ca. 20%. Dies deckt die technische Implementierung der Übersetzungsschicht sowie die Anpassung von Layouts für unterschiedliche Textlängen ab.";
const toggleLanguage = (lang: string) => {
const current = state.languagesList || [];
if (current.includes(lang)) {
updateState({ languagesList: current.filter(l => l !== lang) });
} else {
updateState({ languagesList: [...current, lang] });
}
};
const toggleDontKnow = (id: string) => {
const current = state.dontKnows || [];
if (current.includes(id)) {
@@ -32,7 +48,7 @@ export function LanguageStep({ state, updateState }: LanguageStepProps) {
<div className="space-y-8">
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Globe size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Sprachen</h4>
@@ -42,7 +58,7 @@ export function LanguageStep({ state, updateState }: LanguageStepProps) {
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleDontKnow('languages')}
className={`px-6 py-3 rounded-full text-sm font-bold transition-all shadow-sm ${
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
state.dontKnows?.includes('languages') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
}`}
>
@@ -50,27 +66,78 @@ export function LanguageStep({ state, updateState }: LanguageStepProps) {
</motion.button>
</div>
<div className="space-y-6">
<div className="flex items-center gap-4">
<div className="w-10 h-10 bg-slate-50 rounded-xl flex items-center justify-center text-slate-900 shadow-sm">
<ListPlus size={20} />
</div>
<p className="text-lg font-bold text-slate-900">Welche Sprachen planen Sie?</p>
<div className="space-y-8">
<p className="text-lg text-slate-500 leading-relaxed ml-2">
Welche Sprachen soll Ihre Website unterstützen?
</p>
<div className="flex flex-wrap gap-4">
{COMMON_LANGUAGES.map((lang) => (
<motion.button
key={lang.id}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => toggleLanguage(lang.label)}
className={`px-8 py-4 rounded-2xl font-bold transition-all border-2 ${
state.languagesList.includes(lang.label)
? 'bg-slate-900 border-slate-900 text-white'
: 'bg-white border-slate-100 text-slate-600 hover:border-slate-300'
}`}
>
{lang.label}
</motion.button>
))}
</div>
<div className="p-2">
<RepeatableList
items={state.languagesList}
onAdd={(v) => updateState({ languagesList: [...state.languagesList, v] })}
onRemove={(i) => updateState({ languagesList: state.languagesList.filter((_, idx) => idx !== i) })}
placeholder="z.B. Englisch, Französisch, Spanisch..."
/>
<div className="pt-4">
<div className="flex gap-3">
<input
type="text"
placeholder="Weitere Sprache hinzufügen..."
className="flex-1 p-6 bg-white border border-slate-100 rounded-2xl focus:outline-none focus:border-slate-900 transition-colors text-lg"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
const val = e.currentTarget.value.trim();
if (val && !state.languagesList.includes(val)) {
updateState({ languagesList: [...state.languagesList, val] });
e.currentTarget.value = '';
}
}
}}
/>
</div>
</div>
<div className="flex flex-wrap gap-3">
<AnimatePresence>
{state.languagesList.filter(l => !COMMON_LANGUAGES.find(cl => cl.label === l)).map((lang, i) => (
<motion.div
key={lang}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
className="flex items-center gap-3 px-6 py-3 bg-slate-100 rounded-full text-base font-bold text-slate-700"
>
<span>{lang}</span>
<button
type="button"
onClick={() => updateState({ languagesList: state.languagesList.filter(l => l !== lang) })}
className="text-slate-400 hover:text-slate-900 transition-colors"
>
<X size={18} />
</button>
</motion.div>
))}
</AnimatePresence>
</div>
</div>
</div>
</Reveal>
<Reveal width="100%" delay={0.3}>
<div className="p-10 bg-slate-900 text-white rounded-[3rem] space-y-8 shadow-2xl relative overflow-hidden">
<div className="p-10 bg-slate-900 text-white rounded-[3rem] space-y-8 relative overflow-hidden">
<div className="absolute top-0 right-0 w-64 h-64 bg-white/5 rounded-full -mr-32 -mt-32 blur-3xl" />
<div className="flex items-center gap-4 text-slate-400 relative z-10">
<Info size={24} />

View File

@@ -3,10 +3,10 @@
import * as React from 'react';
import { FormState } from '../types';
import { Checkbox } from '../components/Checkbox';
import { RepeatableList } from '../components/RepeatableList';
import { Minus, Plus, Link2, Globe, Share2 } from 'lucide-react';
import { Link2, Globe, Share2 } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Reveal } from '../../Reveal';
import { Input } from '../components/Input';
interface PresenceStepProps {
state: FormState;
@@ -38,60 +38,45 @@ export function PresenceStep({ state, updateState, toggleItem }: PresenceStepPro
<Reveal width="100%" delay={0.1}>
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Globe size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Bestehende Website</h4>
</div>
<div className="space-y-4">
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">URL (falls vorhanden)</label>
<div className="relative group">
<div className="absolute left-6 top-1/2 -translate-y-1/2 text-slate-300 group-focus-within:text-slate-900 transition-colors">
<Link2 size={24} />
</div>
<input
type="url"
placeholder="https://www.beispiel.de"
value={state.existingWebsite}
onChange={(e) => updateState({ existingWebsite: e.target.value })}
className="w-full p-8 pl-16 bg-white border border-slate-100 rounded-[3rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-xl shadow-sm focus:shadow-2xl"
/>
</div>
</div>
<Input
label="URL (falls vorhanden)"
type="url"
icon={Link2}
placeholder="https://www.beispiel.de"
value={state.existingWebsite}
onChange={(e) => updateState({ existingWebsite: e.target.value })}
/>
</div>
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<Reveal width="100%" delay={0.2}>
<div className="space-y-4">
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">Bestehende Domain</label>
<input
type="text"
placeholder="z.B. beispiel.de"
value={state.existingDomain}
onChange={(e) => updateState({ existingDomain: e.target.value })}
className="w-full p-6 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-lg shadow-sm focus:shadow-xl"
/>
</div>
<Input
label="Bestehende Domain"
placeholder="z.B. beispiel.de"
value={state.existingDomain}
onChange={(e) => updateState({ existingDomain: e.target.value })}
/>
</Reveal>
<Reveal width="100%" delay={0.2}>
<div className="space-y-4">
<label className="block text-sm font-bold uppercase tracking-widest text-slate-400 ml-2">Wunsch-Domain</label>
<input
type="text"
placeholder="z.B. neue-marke.de"
value={state.wishedDomain}
onChange={(e) => updateState({ wishedDomain: e.target.value })}
className="w-full p-6 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-lg shadow-sm focus:shadow-xl"
/>
</div>
<Input
label="Wunsch-Domain"
placeholder="z.B. neue-marke.de"
value={state.wishedDomain}
onChange={(e) => updateState({ wishedDomain: e.target.value })}
/>
</Reveal>
</div>
<Reveal width="100%" delay={0.3}>
<div className="space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-900 shadow-sm">
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
<Share2 size={24} />
</div>
<h4 className="text-2xl font-bold text-slate-900">Social Media Accounts</h4>
@@ -104,7 +89,7 @@ export function PresenceStep({ state, updateState, toggleItem }: PresenceStepPro
<motion.div
key={option.id}
className={`p-6 rounded-[2.5rem] border-2 transition-all duration-500 ${
isSelected ? 'border-slate-900 bg-slate-50 shadow-md' : 'border-slate-100 bg-white hover:border-slate-300'
isSelected ? 'border-slate-900 bg-slate-50' : 'border-slate-100 bg-white hover:border-slate-300'
}`}
>
<div className="flex flex-col gap-4">
@@ -125,7 +110,7 @@ export function PresenceStep({ state, updateState, toggleItem }: PresenceStepPro
className="overflow-hidden"
>
<div className="relative group/input pt-2">
<div className="absolute left-5 top-[calc(50%+4px)] -translate-y-1/2 text-slate-300 group-focus-within/input:text-slate-900 transition-colors">
<div className="absolute left-5 top-[calc(50%+4px)] -translate-y-1/2 text-black transition-colors">
<Link2 size={18} />
</div>
<input
@@ -133,7 +118,7 @@ export function PresenceStep({ state, updateState, toggleItem }: PresenceStepPro
placeholder={`URL zu Ihrem ${option.label} Profil`}
value={state.socialMediaUrls[option.id] || ''}
onChange={(e) => updateUrl(option.id, e.target.value)}
className="w-full p-4 pl-14 bg-white border border-slate-200 rounded-2xl focus:outline-none focus:border-slate-900 transition-all duration-300 text-base shadow-inner"
className="w-full p-4 pl-14 bg-white border border-slate-200 rounded-2xl focus:outline-none focus:border-slate-900 transition-all duration-300 text-base"
/>
</div>
</motion.div>

View File

@@ -20,7 +20,7 @@ export function TypeStep({ state, updateState }: TypeStepProps) {
].map((type, index) => (
<Reveal key={type.id} width="100%" delay={index * 0.1}>
<motion.button
whileHover={{ y: -8, boxShadow: '0 25px 50px -12px rgb(0 0 0 / 0.15)' }}
whileHover={{ y: -8 }}
whileTap={{ scale: 0.98 }}
type="button"
onClick={() => updateState({ projectType: type.id as ProjectType })}

View File

@@ -23,7 +23,7 @@ export function WebAppStep({ state, updateState }: WebAppStepProps) {
{/* Target Audience */}
<div className="space-y-6">
<h4 className="text-2xl font-bold text-slate-900 flex items-center gap-3">
<Users size={24} /> Zielgruppe
<Users size={24} className="text-black" /> Zielgruppe
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{[
@@ -68,7 +68,7 @@ export function WebAppStep({ state, updateState }: WebAppStepProps) {
{/* Platform Type */}
<div className="space-y-6">
<h4 className="text-2xl font-bold text-slate-900 flex items-center gap-3">
<Monitor size={24} /> Plattform-Fokus
<Monitor size={24} className="text-black" /> Plattform-Fokus
</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[
@@ -84,7 +84,9 @@ export function WebAppStep({ state, updateState }: WebAppStepProps) {
state.platformType === opt.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-100 bg-white hover:border-slate-200'
}`}
>
{opt.icon}
<div className={state.platformType === opt.id ? 'text-white' : 'text-black'}>
{opt.icon}
</div>
<span className="font-bold text-lg">{opt.label}</span>
</button>
))}
@@ -94,7 +96,7 @@ export function WebAppStep({ state, updateState }: WebAppStepProps) {
{/* Data Sensitivity */}
<div className="space-y-6">
<h4 className="text-2xl font-bold text-slate-900 flex items-center gap-3">
<Shield size={24} /> Datensicherheit
<Shield size={24} className="text-black" /> Datensicherheit
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{[
@@ -119,7 +121,7 @@ export function WebAppStep({ state, updateState }: WebAppStepProps) {
{/* Authentication */}
<div className="p-10 bg-slate-50 rounded-[3rem] border border-slate-100 space-y-6">
<h4 className="text-2xl font-bold text-slate-900 flex items-center gap-3">
<Lock size={24} /> Authentifizierung
<Lock size={24} className="text-black" /> Authentifizierung
</h4>
<p className="text-lg text-slate-500">Wie sollen sich Nutzer anmelden?</p>
<div className="flex flex-wrap gap-4">