form
This commit is contained in:
@@ -200,20 +200,28 @@ export function ContactForm() {
|
||||
};
|
||||
|
||||
const steps: Step[] = [
|
||||
{ id: 'type', title: 'Das Ziel', description: 'Was möchten Sie realisieren?', illustration: <ConceptTarget className="w-full h-full" /> },
|
||||
{ id: 'company', title: 'Unternehmen', description: 'Wer sind Sie?', illustration: <ConceptCommunication className="w-full h-full" /> },
|
||||
{ id: 'presence', title: 'Präsenz', description: 'Bestehende Kanäle.', illustration: <ConceptSystem className="w-full h-full" /> },
|
||||
{ id: 'features', title: 'Die Systeme', description: 'Welche inhaltlichen Bereiche planen wir?', illustration: <ConceptPrototyping className="w-full h-full" /> },
|
||||
{ id: 'base', title: 'Die Seiten', description: 'Welche Seiten benötigen wir?', illustration: <ConceptWebsite className="w-full h-full" /> },
|
||||
{ id: 'design', title: 'Design-Wünsche', description: 'Wie soll die Seite wirken?', illustration: <ConceptCommunication className="w-full h-full" /> },
|
||||
{ id: 'assets', title: 'Ihre Assets', description: 'Was bringen Sie bereits mit?', illustration: <ConceptSystem className="w-full h-full" /> },
|
||||
{ id: 'functions', title: 'Die Logik', description: 'Welche Funktionen werden benötigt?', illustration: <ConceptCode className="w-full h-full" /> },
|
||||
{ id: 'api', title: 'Schnittstellen', description: 'Datenaustausch mit Drittsystemen.', illustration: <ConceptAutomation className="w-full h-full" /> },
|
||||
{ id: 'content', title: 'Die Pflege', description: 'Wer kümmert sich um die Daten?', illustration: <ConceptPrice className="w-full h-full" /> },
|
||||
{ id: 'language', title: 'Sprachen', description: 'Globale Reichweite planen.', illustration: <ConceptCommunication className="w-full h-full" /> },
|
||||
{ id: 'timeline', title: 'Zeitplan', description: 'Wann soll das Projekt live gehen?', illustration: <HeroArchitecture className="w-full h-full" /> },
|
||||
{ id: 'contact', title: 'Abschluss', description: 'Erzählen Sie mir mehr über Ihr Vorhaben.', illustration: <ConceptCommunication className="w-full h-full" /> },
|
||||
{ id: 'webapp', title: 'Web App Details', description: 'Spezifische Anforderungen für Ihre Anwendung.', illustration: <ConceptSystem className="w-full h-full" /> },
|
||||
{ id: 'type', title: 'Das Ziel', description: 'Was möchten Sie realisieren?', illustration: <ConceptTarget className="w-full h-full" />, chapter: 'strategy' },
|
||||
{ id: 'company', title: 'Unternehmen', description: 'Wer sind Sie?', illustration: <ConceptCommunication className="w-full h-full" />, chapter: 'strategy' },
|
||||
{ id: 'presence', title: 'Präsenz', description: 'Bestehende Kanäle von {company}.', illustration: <ConceptSystem className="w-full h-full" />, chapter: 'strategy' },
|
||||
{ id: 'features', title: 'Die Systeme', description: 'Welche inhaltlichen Bereiche planen wir für {company}?', illustration: <ConceptPrototyping className="w-full h-full" />, chapter: 'scope' },
|
||||
{ id: 'base', title: 'Die Seiten', description: 'Welche Seiten benötigen wir?', illustration: <ConceptWebsite className="w-full h-full" />, chapter: 'scope' },
|
||||
{ id: 'design', title: 'Design-Wünsche', description: 'Wie soll die neue Präsenz von {company} wirken?', illustration: <ConceptCommunication className="w-full h-full" />, chapter: 'creative' },
|
||||
{ id: 'assets', title: 'Ihre Assets', description: 'Was bringen Sie bereits mit?', illustration: <ConceptSystem className="w-full h-full" />, chapter: 'creative' },
|
||||
{ id: 'functions', title: 'Die Logik', description: 'Welche Funktionen werden benötigt?', illustration: <ConceptCode className="w-full h-full" />, chapter: 'tech' },
|
||||
{ id: 'api', title: 'Schnittstellen', description: 'Datenaustausch mit Drittsystemen.', illustration: <ConceptAutomation className="w-full h-full" />, chapter: 'tech' },
|
||||
{ id: 'content', title: 'Die Pflege', description: 'Wer kümmert sich um die Daten?', illustration: <ConceptPrice className="w-full h-full" />, chapter: 'tech' },
|
||||
{ id: 'language', title: 'Sprachen', description: 'Globale Reichweite planen.', illustration: <ConceptCommunication className="w-full h-full" />, chapter: 'tech' },
|
||||
{ id: 'timeline', title: 'Zeitplan', description: 'Wann soll das Projekt live gehen?', illustration: <HeroArchitecture className="w-full h-full" />, chapter: 'final' },
|
||||
{ id: 'contact', title: 'Abschluss', description: 'Erzählen Sie mir mehr über Ihr Vorhaben.', illustration: <ConceptCommunication className="w-full h-full" />, chapter: 'final' },
|
||||
{ id: 'webapp', title: 'Web App Details', description: 'Spezifische Anforderungen für {company}.', illustration: <ConceptSystem className="w-full h-full" />, chapter: 'scope' },
|
||||
];
|
||||
|
||||
const chapters = [
|
||||
{ id: 'strategy', title: 'Strategie' },
|
||||
{ id: 'scope', title: 'Umfang' },
|
||||
{ id: 'creative', title: 'Design' },
|
||||
{ id: 'tech', title: 'Technik' },
|
||||
{ id: 'final', title: 'Start' },
|
||||
];
|
||||
|
||||
const activeSteps = useMemo(() => {
|
||||
@@ -231,7 +239,7 @@ export function ContactForm() {
|
||||
steps.find(s => s.id === 'timeline')!,
|
||||
steps.find(s => s.id === 'contact')!,
|
||||
];
|
||||
}, [state.projectType]);
|
||||
}, [state.projectType, state.companyName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (stepIndex >= activeSteps.length) setStepIndex(activeSteps.length - 1);
|
||||
@@ -312,17 +320,31 @@ export function ContactForm() {
|
||||
<div className="lg:col-span-8 space-y-12">
|
||||
<div className="flex flex-col gap-10">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-8">
|
||||
<div className="w-32 h-32 shrink-0 bg-slate-50 rounded-[2.5rem] p-4 flex items-center justify-center">{activeSteps[stepIndex].illustration}</div>
|
||||
<div className="w-32 h-32 shrink-0 bg-slate-50 rounded-[2.5rem] p-4 flex items-center justify-center relative">
|
||||
{activeSteps[stepIndex].illustration}
|
||||
<div className="absolute -bottom-2 -right-2 w-10 h-10 bg-slate-900 text-white rounded-full flex items-center justify-center font-bold text-sm border-4 border-white">
|
||||
{stepIndex + 1}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<span className="text-base font-bold uppercase tracking-[0.2em] text-slate-400">Schritt {stepIndex + 1} von {activeSteps.length}</span>
|
||||
<h3 className="text-4xl font-bold tracking-tight text-slate-900">{activeSteps[stepIndex].title}</h3>
|
||||
<p className="text-xl text-slate-500 leading-relaxed">{activeSteps[stepIndex].description}</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-base font-bold uppercase tracking-[0.2em] text-slate-400">
|
||||
Schritt {stepIndex + 1} von {activeSteps.length}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-4xl font-bold tracking-tight text-slate-900">
|
||||
{activeSteps[stepIndex].title.replace('{company}', state.companyName || 'Ihr Unternehmen')}
|
||||
</h3>
|
||||
<p className="text-xl text-slate-500 leading-relaxed">
|
||||
{activeSteps[stepIndex].description.replace('{company}', state.companyName || 'Ihr Unternehmen')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3 h-4">
|
||||
|
||||
<div className="flex gap-2 h-2">
|
||||
{activeSteps.map((step, i) => (
|
||||
<div
|
||||
key={i}
|
||||
<div
|
||||
key={i}
|
||||
className="flex-1 h-full flex items-center relative"
|
||||
onMouseEnter={() => setHoveredStep(i)}
|
||||
onMouseLeave={() => setHoveredStep(null)}
|
||||
@@ -333,21 +355,11 @@ export function ContactForm() {
|
||||
setStepIndex(i);
|
||||
setTimeout(scrollToTop, 50);
|
||||
}}
|
||||
className={`w-full h-full rounded-full transition-all duration-700 ${i <= stepIndex ? 'bg-slate-900' : 'bg-slate-100'} cursor-pointer focus:outline-none p-0 border-none`}
|
||||
className={`w-full h-full rounded-full transition-all duration-700 ${
|
||||
i === stepIndex ? 'bg-slate-900 scale-y-150' :
|
||||
i < stepIndex ? 'bg-slate-400' : 'bg-slate-100'
|
||||
} cursor-pointer focus:outline-none p-0 border-none`}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{hoveredStep === i && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 5, x: '-50%' }}
|
||||
animate={{ opacity: 1, y: 0, x: '-50%' }}
|
||||
exit={{ opacity: 0, y: 5, x: '-50%' }}
|
||||
className="absolute bottom-full left-1/2 mb-3 px-4 py-2 bg-slate-900 text-white text-sm font-bold uppercase tracking-wider rounded-lg whitespace-nowrap pointer-events-none z-50 shadow-xl"
|
||||
>
|
||||
{step.title}
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-slate-900" />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Input({ label, icon: Icon, isTextArea, className = '', ...props
|
||||
)}
|
||||
<div className="relative group">
|
||||
{Icon && (
|
||||
<div className="absolute left-6 top-[1.8rem] -translate-y-1/2 text-black transition-colors">
|
||||
<div className={`absolute left-6 ${isTextArea ? 'top-10' : 'top-1/2'} -translate-y-1/2 text-black transition-colors`}>
|
||||
<Icon size={24} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -80,7 +80,11 @@ export function PriceCalculation({
|
||||
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"
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
if (pdfLoading) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
setPdfLoading(true);
|
||||
setTimeout(() => setPdfLoading(false), 2000);
|
||||
}}
|
||||
|
||||
@@ -69,6 +69,8 @@ export const initialState: FormState = {
|
||||
platformType: 'web-only',
|
||||
// Meta
|
||||
dontKnows: [],
|
||||
visualStaging: 'standard',
|
||||
complexInteractions: 'standard',
|
||||
};
|
||||
|
||||
export const PAGE_SAMPLES = [
|
||||
@@ -107,7 +109,6 @@ export const API_OPTIONS = [
|
||||
{ id: 'social', label: 'Social Media Sync', desc: 'Automatisierte Posts oder Feeds.' },
|
||||
{ id: 'maps', label: 'Google Maps / Places', desc: 'Standortsuche und Kartenintegration.' },
|
||||
{ id: 'analytics', label: 'Custom Analytics', desc: 'Anbindung an spezialisierte Tracking-Tools.' },
|
||||
{ id: 'auth', label: 'Auth-Provider', desc: 'NextAuth, Clerk, Auth0 Integration.' },
|
||||
];
|
||||
|
||||
export const ASSET_OPTIONS = [
|
||||
|
||||
@@ -35,16 +35,19 @@ export function ApiStep({ state, updateState, toggleItem }: ApiStepProps) {
|
||||
<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">
|
||||
{isWebApp ? 'Integrationen & Datenquellen' : 'Schnittstellen (API)'}
|
||||
</h4>
|
||||
<div className="flex items-center gap-3">
|
||||
<h4 className="text-2xl font-bold text-slate-900">
|
||||
{isWebApp ? 'Integrationen & Datenquellen' : 'Schnittstellen (API)'}
|
||||
</h4>
|
||||
<span className="px-2 py-1 bg-slate-50 text-slate-400 text-[10px] font-bold uppercase tracking-wider rounded">Optional</span>
|
||||
</div>
|
||||
</div>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type="button"
|
||||
onClick={() => toggleDontKnow('api')}
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('api') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
@@ -63,7 +66,6 @@ export function ApiStep({ state, updateState, toggleItem }: ApiStepProps) {
|
||||
{ id: 'marketing', label: 'Marketing', desc: 'Newsletter (Mailchimp), Social Media Sync.' },
|
||||
{ id: 'ecommerce', label: 'E-Commerce', desc: 'Shopify, WooCommerce, Lagerbestand-Sync.' },
|
||||
{ id: 'maps', label: 'Google Maps / Places', desc: 'Standortsuche und Kartenintegration.' },
|
||||
{ id: 'auth', label: 'Auth-Provider', desc: 'NextAuth, Clerk, Auth0 Integration.' },
|
||||
].map((opt, index) => (
|
||||
<motion.div
|
||||
key={opt.id}
|
||||
|
||||
@@ -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 ${
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('assets') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
@@ -56,11 +56,18 @@ export function AssetsStep({ state, updateState, toggleItem }: AssetsStepProps)
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
>
|
||||
<Checkbox
|
||||
key={opt.id} label={opt.label} desc={opt.desc}
|
||||
checked={state.assets.includes(opt.id)}
|
||||
onChange={() => updateState({ assets: toggleItem(state.assets, opt.id) })}
|
||||
/>
|
||||
<div className="relative">
|
||||
<Checkbox
|
||||
key={opt.id} label={opt.label} desc={opt.desc}
|
||||
checked={state.assets.includes(opt.id)}
|
||||
onChange={() => updateState({ assets: toggleItem(state.assets, opt.id) })}
|
||||
/>
|
||||
{['logo', 'styleguide', 'content_concept'].includes(opt.id) && (
|
||||
<div className="absolute top-4 right-4 px-2 py-1 bg-slate-900 text-white text-[10px] font-bold uppercase tracking-wider rounded">
|
||||
Wichtig
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
@@ -84,12 +91,13 @@ export function AssetsStep({ state, updateState, toggleItem }: AssetsStepProps)
|
||||
/>
|
||||
</div>
|
||||
|
||||
<motion.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 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 relative overflow-hidden"
|
||||
>
|
||||
<div className="absolute top-0 left-0 w-1 h-full bg-slate-900" />
|
||||
<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 Assets</h4>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FormState } from '../types';
|
||||
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 { Minus, Plus, FileText, ListPlus, HelpCircle } from 'lucide-react';
|
||||
import { Input } from '../components/Input';
|
||||
|
||||
interface BaseStepProps {
|
||||
@@ -31,12 +31,17 @@ export function BaseStep({ state, updateState, toggleItem }: BaseStepProps) {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
<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 })}
|
||||
/>
|
||||
<div className="relative">
|
||||
<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 })}
|
||||
/>
|
||||
<div className="absolute top-0 right-4 px-2 py-1 bg-slate-900 text-white text-[10px] font-bold uppercase tracking-wider rounded">
|
||||
Wichtig
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-8">
|
||||
@@ -45,14 +50,23 @@ export function BaseStep({ state, updateState, toggleItem }: BaseStepProps) {
|
||||
<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>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<h4 className="text-2xl font-bold text-slate-900">Die Seiten</h4>
|
||||
<span className="px-2 py-1 bg-slate-900 text-white text-[10px] font-bold uppercase tracking-wider rounded">Wichtig</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-slate-400 mt-1">
|
||||
<HelpCircle size={14} />
|
||||
<span className="text-sm">Warum ist das wichtig? Die Anzahl der Seiten bestimmt maßgeblich den Design- und Umsetzungsaufwand.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<motion.button
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type="button"
|
||||
onClick={() => toggleDontKnow('pages')}
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('pages') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -23,6 +23,7 @@ export function CompanyStep({ state, updateState }: CompanyStepProps) {
|
||||
<Building2 size={24} />
|
||||
</div>
|
||||
<h4 className="text-2xl font-bold text-slate-900">Unternehmen</h4>
|
||||
<span className="px-2 py-1 bg-slate-100 text-slate-500 text-[10px] font-bold uppercase tracking-wider rounded">Wichtig</span>
|
||||
</div>
|
||||
<Input
|
||||
label="Name des Unternehmens"
|
||||
|
||||
@@ -15,28 +15,48 @@ interface ContactStepProps {
|
||||
export function ContactStep({ state, updateState }: ContactStepProps) {
|
||||
return (
|
||||
<div className="space-y-12">
|
||||
<Reveal width="100%" delay={0.05}>
|
||||
<div className="p-8 bg-slate-50 text-slate-900 rounded-[2.5rem] mb-8 border border-slate-100">
|
||||
<h4 className="text-2xl font-bold mb-2">Fast geschafft! 🚀</h4>
|
||||
<p className="text-slate-500 text-lg">
|
||||
Ich habe alle Details für das Projekt von <span className="text-slate-900 font-bold">{state.companyName || 'Ihrem Unternehmen'}</span> erhalten.
|
||||
Hinterlassen Sie mir noch Ihre Kontaktdaten, damit ich Ihnen ein konkretes Angebot erstellen kann.
|
||||
</p>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<Reveal width="100%" delay={0.1}>
|
||||
<Input
|
||||
label="Ihr Name"
|
||||
icon={User}
|
||||
placeholder="Max Mustermann"
|
||||
required
|
||||
value={state.name}
|
||||
onChange={(e) => updateState({ name: e.target.value })}
|
||||
/>
|
||||
<div className="relative">
|
||||
<Input
|
||||
label="Ihr Name"
|
||||
icon={User}
|
||||
placeholder="Max Mustermann"
|
||||
required
|
||||
value={state.name}
|
||||
onChange={(e) => updateState({ name: e.target.value })}
|
||||
/>
|
||||
<div className="absolute top-0 right-4 px-2 py-1 bg-slate-900 text-white text-[10px] font-bold uppercase tracking-wider rounded">
|
||||
Wichtig
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<Reveal width="100%" delay={0.1}>
|
||||
<Input
|
||||
label="Ihre Email"
|
||||
icon={Mail}
|
||||
type="email"
|
||||
placeholder="max@beispiel.de"
|
||||
required
|
||||
value={state.email}
|
||||
onChange={(e) => updateState({ email: e.target.value })}
|
||||
/>
|
||||
<div className="relative">
|
||||
<Input
|
||||
label="Ihre Email"
|
||||
icon={Mail}
|
||||
type="email"
|
||||
placeholder="max@beispiel.de"
|
||||
required
|
||||
value={state.email}
|
||||
onChange={(e) => updateState({ email: e.target.value })}
|
||||
/>
|
||||
<div className="absolute top-0 right-4 px-2 py-1 bg-slate-900 text-white text-[10px] font-bold uppercase tracking-wider rounded">
|
||||
Wichtig
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -32,7 +32,10 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
|
||||
</div>
|
||||
<h4 className="text-2xl font-bold text-slate-900">Inhalte selbst verwalten (CMS)</h4>
|
||||
</div>
|
||||
<p className="text-lg text-slate-500 leading-relaxed">Möchten Sie Datensätze (z.B. Blogartikel, Produkte) selbst über eine einfache Oberfläche pflegen?</p>
|
||||
<p className="text-lg text-slate-500 leading-relaxed">
|
||||
Ein CMS (Content Management System) ermöglicht es Ihnen, Texte, Bilder und Blogartikel selbst zu ändern, ohne programmieren zu müssen.
|
||||
Ideal, wenn Sie Ihre Website aktuell halten möchten.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center md:items-end gap-6">
|
||||
<motion.button
|
||||
@@ -138,7 +141,10 @@ export function ContentStep({ state, updateState }: ContentStepProps) {
|
||||
<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>
|
||||
<p className="text-lg text-slate-500 leading-relaxed">
|
||||
Wer kümmert sich um die erste Befüllung? Wenn ich das übernehmen soll, geben Sie hier die Anzahl der Datensätze (z.B. fertige Blogartikel oder Produkte) an.
|
||||
Ansonsten übergeben wir Ihnen eine leere, aber einsatzbereite Struktur.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-12 py-2">
|
||||
<motion.button
|
||||
|
||||
@@ -7,6 +7,7 @@ import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Plus, X, Palette, Pipette, RefreshCw } from 'lucide-react';
|
||||
import { Reveal } from '../../Reveal';
|
||||
import { Input } from '../components/Input';
|
||||
import { RepeatableList } from '../components/RepeatableList';
|
||||
|
||||
interface DesignStepProps {
|
||||
state: FormState;
|
||||
@@ -59,12 +60,13 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
|
||||
return `#${f(0)}${f(8)}${f(4)}`;
|
||||
};
|
||||
|
||||
const palette = [
|
||||
hslToHex(hue, saturation, 95), // Light
|
||||
hslToHex(hue, saturation, lightness), // Main
|
||||
hslToHex((hue + 30) % 360, saturation, lightness - 10), // Analogous
|
||||
hslToHex((hue + 180) % 360, saturation - 10, 20), // Complementary Dark
|
||||
];
|
||||
const count = state.colorScheme.length;
|
||||
const palette = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const h = (hue + (i * (360 / count))) % 360;
|
||||
const l = i === 0 ? 95 : i === count - 1 ? 20 : lightness;
|
||||
palette.push(hslToHex(h, saturation, l));
|
||||
}
|
||||
updateState({ colorScheme: palette });
|
||||
};
|
||||
|
||||
@@ -83,7 +85,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 ${
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('design_vibe') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
@@ -137,7 +139,7 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type="button"
|
||||
onClick={() => toggleDontKnow('color_scheme')}
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('color_scheme') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
@@ -206,15 +208,33 @@ export function DesignStep({ state, updateState }: DesignStepProps) {
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
{/* Wishes */}
|
||||
{/* References */}
|
||||
<Reveal width="100%" delay={0.3}>
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-2xl font-bold text-slate-900">Referenz-Websites</h4>
|
||||
<p className="text-slate-500">Gibt es Websites, die Ihnen besonders gut gefallen?</p>
|
||||
</div>
|
||||
<div className="p-10 bg-white border border-slate-100 rounded-[3rem]">
|
||||
<RepeatableList
|
||||
items={state.references || []}
|
||||
onAdd={(v) => updateState({ references: [...(state.references || []), v] })}
|
||||
onRemove={(i) => updateState({ references: (state.references || []).filter((_, idx) => idx !== i) })}
|
||||
placeholder="https://beispiel.de"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
{/* Wishes */}
|
||||
<Reveal width="100%" delay={0.4}>
|
||||
<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 })}
|
||||
placeholder="Haben Sie weitere konkrete Vorstellungen?"
|
||||
value={state.designWishes}
|
||||
onChange={(e) => updateState({ designWishes: e.target.value })}
|
||||
/>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FEATURE_OPTIONS } from '../constants';
|
||||
import { Checkbox } from '../components/Checkbox';
|
||||
import { RepeatableList } from '../components/RepeatableList';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Minus, Plus, LayoutGrid, ListPlus } from 'lucide-react';
|
||||
import { Minus, Plus, LayoutGrid, ListPlus, HelpCircle } from 'lucide-react';
|
||||
|
||||
interface FeaturesStepProps {
|
||||
state: FormState;
|
||||
@@ -32,14 +32,23 @@ export function FeaturesStep({ state, updateState, toggleItem }: FeaturesStepPro
|
||||
<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>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<h4 className="text-2xl font-bold text-slate-900">System-Module</h4>
|
||||
<span className="px-2 py-1 bg-slate-50 text-slate-400 text-[10px] font-bold uppercase tracking-wider rounded">Optional</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-slate-400 mt-1">
|
||||
<HelpCircle size={14} />
|
||||
<span className="text-sm">Module sind funktionale Einheiten, die über einfache Textseiten hinausgehen.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type="button"
|
||||
onClick={() => toggleDontKnow('features')}
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('features') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
@@ -80,50 +89,6 @@ export function FeaturesStep({ state, updateState, toggleItem }: FeaturesStepPro
|
||||
/>
|
||||
</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 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 Module</h4>
|
||||
<p className="text-base text-slate-500 mt-1">Falls Sie weitere Systeme 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({ otherFeaturesCount: Math.max(0, state.otherFeaturesCount - 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.otherFeaturesCount}
|
||||
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.otherFeaturesCount}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
type="button"
|
||||
onClick={() => updateState({ otherFeaturesCount: state.otherFeaturesCount + 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>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -39,12 +39,12 @@ export function FunctionsStep({ state, updateState, toggleItem }: FunctionsStepP
|
||||
{isWebApp ? 'Funktionale Anforderungen' : 'Erweiterte Funktionen'}
|
||||
</h4>
|
||||
</div>
|
||||
<motion.button
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type="button"
|
||||
onClick={() => toggleDontKnow('functions')}
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('functions') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
@@ -77,25 +77,45 @@ export function FunctionsStep({ state, updateState, toggleItem }: FunctionsStepP
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Checkbox
|
||||
label="Suche" desc="Volltextsuche über alle Inhalte."
|
||||
checked={state.functions.includes('search')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'search') })}
|
||||
<Checkbox
|
||||
label="Suche" desc="Volltextsuche über alle Inhalte."
|
||||
checked={state.functions.includes('search')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'search') })}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Filter-Systeme" desc="Kategorisierung und Sortierung."
|
||||
checked={state.functions.includes('filter')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'filter') })}
|
||||
<Checkbox
|
||||
label="Filter-Systeme" desc="Kategorisierung und Sortierung."
|
||||
checked={state.functions.includes('filter')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'filter') })}
|
||||
/>
|
||||
<Checkbox
|
||||
label="PDF-Export" desc="Automatisierte PDF-Erstellung."
|
||||
checked={state.functions.includes('pdf')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'pdf') })}
|
||||
<Checkbox
|
||||
label="PDF-Export" desc="Automatisierte PDF-Erstellung."
|
||||
checked={state.functions.includes('pdf')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'pdf') })}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Erweiterte Formulare" desc="Komplexe Abfragen & Logik."
|
||||
checked={state.functions.includes('forms')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'forms') })}
|
||||
<Checkbox
|
||||
label="Erweiterte Formulare" desc="Komplexe Abfragen & Logik."
|
||||
checked={state.functions.includes('forms')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'forms') })}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Mitgliederbereich" desc="Login-Bereich für exklusive Inhalte."
|
||||
checked={state.functions.includes('members')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'members') })}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Event-Kalender" desc="Verwaltung und Anzeige von Terminen."
|
||||
checked={state.functions.includes('calendar')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'calendar') })}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Mehrsprachigkeit" desc="Inhalte in mehreren Sprachen."
|
||||
checked={state.functions.includes('multilang')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'multilang') })}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Echtzeit-Chat" desc="Direkte Kommunikation mit Besuchern."
|
||||
checked={state.functions.includes('chat')}
|
||||
onChange={() => updateState({ functions: toggleItem(state.functions, 'chat') })}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -120,50 +140,6 @@ export function FunctionsStep({ state, updateState, toggleItem }: FunctionsStepP
|
||||
/>
|
||||
</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 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 Funktionen</h4>
|
||||
<p className="text-base text-slate-500 mt-1">Falls Sie weitere Logik-Bausteine 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({ otherFunctionsCount: Math.max(0, state.otherFunctionsCount - 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.otherFunctionsCount}
|
||||
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.otherFunctionsCount}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
type="button"
|
||||
onClick={() => updateState({ otherFunctionsCount: state.otherFunctionsCount + 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>
|
||||
|
||||
@@ -52,13 +52,14 @@ export function LanguageStep({ state, updateState }: LanguageStepProps) {
|
||||
<Globe size={24} />
|
||||
</div>
|
||||
<h4 className="text-2xl font-bold text-slate-900">Sprachen</h4>
|
||||
<span className="px-2 py-1 bg-slate-50 text-slate-400 text-[10px] font-bold uppercase tracking-wider rounded">Optional</span>
|
||||
</div>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type="button"
|
||||
onClick={() => toggleDontKnow('languages')}
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all ${
|
||||
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('languages') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -42,6 +42,7 @@ export function PresenceStep({ state, updateState, toggleItem }: PresenceStepPro
|
||||
<Globe size={24} />
|
||||
</div>
|
||||
<h4 className="text-2xl font-bold text-slate-900">Bestehende Website</h4>
|
||||
<span className="px-2 py-1 bg-slate-50 text-slate-400 text-[10px] font-bold uppercase tracking-wider rounded">Optional</span>
|
||||
</div>
|
||||
<Input
|
||||
label="URL (falls vorhanden)"
|
||||
@@ -74,61 +75,71 @@ export function PresenceStep({ state, updateState, toggleItem }: PresenceStepPro
|
||||
</div>
|
||||
|
||||
<Reveal width="100%" delay={0.3}>
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-10">
|
||||
<div className="flex items-center gap-4">
|
||||
<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>
|
||||
</div>
|
||||
<p className="text-lg text-slate-500 ml-2">Welche Kanäle nutzen Sie bereits? Bitte geben Sie die URLs an.</p>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{SOCIAL_PLATFORMS.map((option, index) => {
|
||||
const isSelected = state.socialMedia.includes(option.id);
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
{SOCIAL_PLATFORMS.map((platform) => {
|
||||
const isSelected = state.socialMedia.includes(platform.id);
|
||||
return (
|
||||
<motion.div
|
||||
key={option.id}
|
||||
className={`p-6 rounded-[2.5rem] border-2 transition-all duration-500 ${
|
||||
isSelected ? 'border-slate-900 bg-slate-50' : 'border-slate-100 bg-white hover:border-slate-300'
|
||||
<motion.button
|
||||
key={platform.id}
|
||||
whileHover={{ y: -4 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type="button"
|
||||
onClick={() => updateState({ socialMedia: toggleItem(state.socialMedia, platform.id) })}
|
||||
className={`flex flex-col items-center gap-3 p-6 rounded-[2rem] border-2 transition-all duration-300 ${
|
||||
isSelected ? 'border-slate-900 bg-slate-900 text-white shadow-lg' : 'border-slate-100 bg-white text-slate-400 hover:border-slate-200'
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Checkbox
|
||||
label={option.label}
|
||||
desc=""
|
||||
checked={isSelected}
|
||||
onChange={() => updateState({ socialMedia: toggleItem(state.socialMedia, option.id) })}
|
||||
/>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{isSelected && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="relative group/input pt-2">
|
||||
<div className="absolute left-5 top-[calc(50%+4px)] -translate-y-1/2 text-black transition-colors">
|
||||
<Link2 size={18} />
|
||||
</div>
|
||||
<input
|
||||
type="url"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
<span className="font-bold text-sm">{platform.label}</span>
|
||||
</motion.button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<AnimatePresence mode="popLayout">
|
||||
{state.socialMedia.map((id) => {
|
||||
const platform = SOCIAL_PLATFORMS.find(p => p.id === id);
|
||||
if (!platform) return null;
|
||||
return (
|
||||
<motion.div
|
||||
key={id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 20 }}
|
||||
layout
|
||||
className="relative group"
|
||||
>
|
||||
<div className="absolute left-6 top-1/2 -translate-y-1/2 flex items-center gap-3 text-slate-400 group-focus-within:text-slate-900 transition-colors">
|
||||
<span className="font-bold text-xs uppercase tracking-widest w-20">{platform.label}</span>
|
||||
<div className="w-[1px] h-4 bg-slate-200" />
|
||||
<Link2 size={18} />
|
||||
</div>
|
||||
<input
|
||||
type="url"
|
||||
placeholder={`https://${platform.id}.com/ihr-profil`}
|
||||
value={state.socialMediaUrls[id] || ''}
|
||||
onChange={(e) => updateUrl(id, e.target.value)}
|
||||
className="w-full p-6 pl-40 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-all duration-500 text-lg focus:shadow-xl"
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
|
||||
{state.socialMedia.length === 0 && (
|
||||
<div className="p-12 border-2 border-dashed border-slate-100 rounded-[3rem] text-center">
|
||||
<p className="text-slate-400 font-medium">Wählen Sie oben Ihre Kanäle aus, um die Links hinzuzufügen.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,9 @@ interface TimelineStepProps {
|
||||
}
|
||||
|
||||
export function TimelineStep({ state, updateState }: TimelineStepProps) {
|
||||
const isMissingAssets = !state.assets.includes('logo') || !state.assets.includes('content_concept');
|
||||
const isMissingPages = state.selectedPages.length === 0 && state.otherPages.length === 0 && state.otherPagesCount === 0;
|
||||
|
||||
const toggleDontKnow = (id: string) => {
|
||||
const current = state.dontKnows || [];
|
||||
if (current.includes(id)) {
|
||||
@@ -24,10 +27,10 @@ export function TimelineStep({ state, updateState }: TimelineStepProps) {
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h4 className="text-2xl font-bold text-slate-900">Zeitplan</h4>
|
||||
<button
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleDontKnow('timeline')}
|
||||
className={`px-4 py-2 rounded-full text-sm font-bold transition-all ${
|
||||
className={`px-4 py-2 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
||||
state.dontKnows?.includes('timeline') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
@@ -63,6 +66,20 @@ export function TimelineStep({ state, updateState }: TimelineStepProps) {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(isMissingAssets || isMissingPages) && (
|
||||
<div className="p-8 bg-amber-50 rounded-[2rem] 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 shrink-0">
|
||||
<AlertCircle size={24} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="text-amber-900 text-xl font-bold">Mögliche Verzögerungen</p>
|
||||
<p className="text-amber-800 text-base leading-relaxed">
|
||||
Da noch wichtige Informationen (z.B. {isMissingAssets ? 'Logo/Inhaltskonzept' : ''} {isMissingAssets && isMissingPages ? 'und' : ''} {isMissingPages ? 'Seitenstruktur' : ''}) fehlen, kann sich der Projektstart verzögern, bis diese vorliegen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,10 @@ export function TypeStep({ state, updateState }: TypeStepProps) {
|
||||
}`}
|
||||
>
|
||||
<div className={`transition-transform duration-500 group-hover:scale-110 ${state.projectType === type.id ? 'text-white' : 'text-slate-900'}`}>{type.illustration}</div>
|
||||
<h4 className={`text-4xl font-bold mb-4 ${state.projectType === type.id ? 'text-white' : 'text-slate-900'}`}>{type.label}</h4>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<h4 className={`text-4xl font-bold ${state.projectType === type.id ? 'text-white' : 'text-slate-900'}`}>{type.label}</h4>
|
||||
<span className={`px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wider ${state.projectType === type.id ? 'bg-white/20 text-white' : 'bg-slate-100 text-slate-500'}`}>Wichtig</span>
|
||||
</div>
|
||||
<p className={`text-xl leading-relaxed ${state.projectType === type.id ? 'text-slate-200' : 'text-slate-500'}`}>{type.desc}</p>
|
||||
|
||||
{state.projectType === type.id && (
|
||||
|
||||
@@ -22,9 +22,12 @@ export function WebAppStep({ state, updateState }: WebAppStepProps) {
|
||||
<div className="space-y-12">
|
||||
{/* Target Audience */}
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-2xl font-bold text-slate-900 flex items-center gap-3">
|
||||
<Users size={24} className="text-black" /> Zielgruppe
|
||||
</h4>
|
||||
<div className="flex items-center gap-4">
|
||||
<h4 className="text-2xl font-bold text-slate-900 flex items-center gap-3">
|
||||
<Users size={24} className="text-black" /> Zielgruppe
|
||||
</h4>
|
||||
<span className="px-2 py-1 bg-slate-900 text-white text-[10px] font-bold uppercase tracking-wider rounded">Wichtig</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{[
|
||||
{ id: 'internal', label: 'Internes Tool', desc: 'Für Mitarbeiter & Prozesse.' },
|
||||
|
||||
@@ -56,6 +56,8 @@ export interface FormState {
|
||||
platformType: string;
|
||||
// Meta
|
||||
dontKnows: string[];
|
||||
visualStaging: string;
|
||||
complexInteractions: string;
|
||||
}
|
||||
|
||||
export interface Step {
|
||||
@@ -63,4 +65,11 @@ export interface Step {
|
||||
title: string;
|
||||
description: string;
|
||||
illustration: React.ReactNode;
|
||||
chapter?: string;
|
||||
}
|
||||
|
||||
export interface Chapter {
|
||||
id: string;
|
||||
title: string;
|
||||
steps: string[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user