login-seiten neu designt, homepage neu designt. "so funktioniert's" ergänzt und Demo-Seite hinzugefügt. Paketansicht in mobile verbessert.

This commit is contained in:
Codex Agent
2025-11-03 11:47:19 +01:00
parent 073b51e2d5
commit 20eda6b4f8
23 changed files with 2481 additions and 587 deletions

View File

@@ -0,0 +1,125 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
import MarketingLayout from '@/layouts/mainWebsite';
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Sparkles, CheckCircle2 } from 'lucide-react';
type DemoFeature = { title: string; description: string };
const DEMO_TOKEN = 'mdhyA5XSVEVEabF8JhZ85B6fMocyyRMTfmThSeUKPzk7LLTu';
const DemoPage: React.FC = () => {
const { t } = useTranslation('marketing');
const { localizedPath } = useLocalizedRoutes();
const demo = t('demo_page', { returnObjects: true }) as {
title: string;
subtitle: string;
primaryCta: string;
secondaryCta: string;
iframeNote: string;
openFull: string;
features: DemoFeature[];
};
return (
<MarketingLayout title={demo.title}>
<Head title={demo.title} />
<section className="relative overflow-hidden bg-gradient-to-br from-pink-100 via-white to-white px-4 py-16 dark:from-pink-950/40 dark:via-gray-950 dark:to-gray-950">
<div className="absolute -top-32 right-20 hidden h-72 w-72 rounded-full bg-pink-200/50 blur-3xl dark:bg-pink-900/30 lg:block" />
<div className="container mx-auto relative z-10 flex max-w-5xl flex-col gap-10 lg:flex-row lg:items-center">
<div className="flex-1 space-y-6">
<Badge variant="outline" className="border-pink-400 text-pink-600 dark:border-pink-500 dark:text-pink-300">
Demo Live
</Badge>
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-50 md:text-5xl">
{demo.title}
</h1>
<p className="max-w-xl text-lg text-gray-600 dark:text-gray-300">
{demo.subtitle}
</p>
<div className="flex flex-wrap items-center gap-3">
<Button asChild size="lg" className="bg-pink-500 hover:bg-pink-600">
<Link href={localizedPath('/packages')}>
{demo.primaryCta}
</Link>
</Button>
<Button asChild size="lg" variant="ghost" className="text-pink-600 hover:text-pink-700 dark:text-pink-300">
<Link href={localizedPath('/so-funktionierts')}>
{demo.secondaryCta}
</Link>
</Button>
</div>
</div>
<div className="flex-1">
<div className="relative mx-auto w-full max-w-[320px] rounded-[2.5rem] border border-gray-200 bg-gray-900 p-4 shadow-2xl dark:border-gray-700 md:max-w-[360px]">
<div className="absolute left-1/2 top-2 h-1.5 w-16 -translate-x-1/2 rounded-full bg-gray-300 dark:bg-gray-600" aria-hidden />
<iframe
title="Fotospiel Demo"
src={`/e/${DEMO_TOKEN}`}
className="aspect-[9/16] w-full rounded-[1.75rem] border-0 bg-white shadow-inner dark:bg-gray-950"
loading="lazy"
sandbox="allow-scripts allow-same-origin allow-forms"
/>
</div>
<div className="mt-4 flex flex-col items-center gap-1 text-center">
<p className="text-sm text-gray-600 dark:text-gray-300">{demo.iframeNote}</p>
<Button asChild variant="link" className="text-pink-600 hover:text-pink-700 dark:text-pink-300">
<Link href={`/e/${DEMO_TOKEN}`} target="_blank" rel="noopener">
{demo.openFull}
</Link>
</Button>
</div>
</div>
</div>
</section>
<section className="container mx-auto px-4 pb-16">
<div className="mx-auto max-w-5xl">
<div className="grid gap-6 md:grid-cols-3">
{demo.features.map((feature) => (
<Card key={feature.title} className="border-gray-100 shadow-sm dark:border-gray-800">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Sparkles className="h-5 w-5 text-pink-500" aria-hidden />
{feature.title}
</CardTitle>
</CardHeader>
<CardContent>
<CardDescription className="text-sm text-gray-600 dark:text-gray-300">
{feature.description}
</CardDescription>
</CardContent>
</Card>
))}
</div>
<Alert className="mt-10 border-pink-200 bg-white shadow-lg dark:border-pink-900/50 dark:bg-gray-950">
<AlertTitle className="flex items-center gap-2 text-gray-900 dark:text-gray-100">
<CheckCircle2 className="h-5 w-5 text-pink-500" aria-hidden />
{t('marketing.labels.readyToLaunch', 'Bereit für dein Event?')}
</AlertTitle>
<AlertDescription className="mt-3 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<span className="text-sm text-gray-600 dark:text-gray-300">
{t('marketing.labels.readyToLaunchCopy', 'Registriere dich kostenlos und lege noch heute dein erstes Event an.')}
</span>
<Button asChild className="bg-pink-500 hover:bg-pink-600">
<Link href={localizedPath('/register')}>
{demo.primaryCta}
</Link>
</Button>
</AlertDescription>
</Alert>
</div>
</section>
</MarketingLayout>
);
};
export default DemoPage;

View File

@@ -1,10 +1,16 @@
import React from 'react';
import React from 'react';
import { Head, Link, useForm } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
import MarketingLayout from '@/layouts/mainWebsite';
import { useAnalytics } from '@/hooks/useAnalytics';
import { useCtaExperiment } from '@/hooks/useCtaExperiment';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { ArrowRight, Camera, QrCode, ShieldCheck, Sparkles, Smartphone } from 'lucide-react';
interface Package {
id: number;
@@ -17,6 +23,9 @@ interface Props {
packages: Package[];
}
const heroBulletIcons = [Sparkles, ShieldCheck, Camera];
const howStepIcons = [QrCode, Smartphone, ShieldCheck];
const Home: React.FC<Props> = ({ packages }) => {
const { t } = useTranslation('marketing');
const { localizedPath } = useLocalizedRoutes();
@@ -31,8 +40,49 @@ const Home: React.FC<Props> = ({ packages }) => {
message: '',
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const heroBulletsRaw = t('home.hero_bullets', { returnObjects: true });
const heroBullets = Array.isArray(heroBulletsRaw) ? (heroBulletsRaw as string[]) : [];
const featuresRaw = t('home.features_highlight', { returnObjects: true });
const features = Array.isArray(featuresRaw)
? (featuresRaw as Array<{ title: string; description: string }>)
: [];
const howSteps = [
{
icon: howStepIcons[0] ?? QrCode,
title: t('home.step1_title'),
description: t('home.step1_desc'),
},
{
icon: howStepIcons[1] ?? Smartphone,
title: t('home.step2_title'),
description: t('home.step2_desc'),
},
{
icon: howStepIcons[2] ?? ShieldCheck,
title: t('home.step3_title'),
description: t('home.step3_desc'),
},
];
const occasionLinks = [
{ key: 'wedding', href: localizedPath('/anlaesse/hochzeit') },
{ key: 'birthday', href: localizedPath('/anlaesse/geburtstag') },
{ key: 'corporate', href: localizedPath('/anlaesse/firmenevent') },
{ key: 'confirmation', href: localizedPath('/anlaesse/konfirmation') },
];
const heroPrimaryHref = localizedPath('/demo');
const heroPrimaryLabel =
heroCtaVariant === 'gradient' ? t('home.cta_demo_highlight') : t('home.cta_demo');
const heroSecondaryHref = localizedPath('/so-funktionierts');
const heroSecondaryLabel = t('home.cta_how');
const heroTertiaryHref = localizedPath('/packages');
const heroTertiaryLabel = t('home.cta_packages');
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
post(localizedPath('/kontakt'), {
onSuccess: () => {
trackEvent({
@@ -54,226 +104,471 @@ const Home: React.FC<Props> = ({ packages }) => {
<MarketingLayout title={t('home.title')}>
<Head title={t('home.hero_title')} />
{/* Hero Section */}
<section id="hero" className="bg-aurora-enhanced text-gray-900 dark:text-gray-100 py-20 px-4">
<div className="container mx-auto flex flex-col md:flex-row items-center gap-8 max-w-6xl">
<div className="md:w-1/2 text-center md:text-left">
<h1 className="text-4xl md:text-6xl font-bold mb-4 font-display">{t('home.hero_title')}</h1>
<p className="text-xl md:text-2xl mb-8 font-sans-marketing">{t('home.hero_description')}</p>
<Link
href={localizedPath('/packages')}
onClick={() => {
trackHeroCtaClick();
trackEvent({
category: 'marketing_home',
action: 'hero_cta',
name: `packages:${heroCtaVariant}`,
});
}}
className={[
'inline-block rounded-full px-8 py-4 font-bold transition duration-300',
heroCtaVariant === 'gradient'
? 'bg-gradient-to-r from-rose-500 via-pink-500 to-amber-400 text-white shadow-lg shadow-rose-500/40 hover:from-rose-500/95 hover:via-pink-500/95 hover:to-amber-400/95'
: 'bg-white text-[#FFB6C1] hover:bg-gray-100 dark:bg-gray-800 dark:text-rose-200 dark:hover:bg-gray-700',
].join(' ')}
>
{heroCtaVariant === 'gradient' ? t('home.cta_explore_highlight') : t('home.cta_explore')}
</Link>
<section id="hero" className="bg-aurora-enhanced py-20 px-4 text-gray-900 dark:text-gray-100">
<div className="container mx-auto flex max-w-6xl flex-col items-center gap-12 md:flex-row">
<div className="flex flex-col gap-8 text-center md:w-1/2 md:text-left">
<div className="flex flex-col gap-4">
<Badge className="mx-auto w-fit bg-white/80 px-3 py-1 text-xs font-semibold uppercase text-rose-500 shadow-sm md:mx-0 md:text-[0.72rem]">
{t('home.hero_tagline')}
</Badge>
<h1 className="font-display text-4xl font-bold leading-tight md:text-5xl lg:text-6xl">
{t('home.hero_title')}
</h1>
<p className="text-lg text-gray-700 dark:text-gray-200 md:text-xl">
{t('home.hero_description')}
</p>
{heroBullets.length > 0 && (
<ul className="mx-auto flex flex-col gap-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-200 md:mx-0">
{heroBullets.map((item, index) => {
const Icon = heroBulletIcons[index % heroBulletIcons.length] ?? Sparkles;
return (
<li key={`hero-bullet-${index}`} className="flex items-start gap-3">
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-white/80 text-rose-500 shadow-sm dark:bg-gray-900/70">
<Icon className="h-4 w-4" aria-hidden />
</span>
<span className="flex-1 text-base">{item}</span>
</li>
);
})}
</ul>
)}
</div>
<div className="flex flex-wrap items-center justify-center gap-3 md:justify-start">
<Button
asChild
size="lg"
className={
heroCtaVariant === 'gradient'
? 'group h-12 rounded-full bg-gradient-to-r from-[#ff5f87] via-[#ec4899] to-[#6366f1] px-7 text-base font-semibold text-white shadow-[0_18px_35px_-18px_rgba(236,72,153,0.7)] transition hover:from-[#ff4470] hover:via-[#ec4899] hover:to-[#4f46e5]'
: 'group h-12 rounded-full bg-white/90 px-7 text-base font-semibold text-rose-500 shadow-md shadow-rose-200/40 transition hover:bg-white'
}
>
<Link
href={heroPrimaryHref}
onClick={() => {
trackHeroCtaClick();
trackEvent({
category: 'marketing_home',
action: 'hero_cta',
name: `demo:${heroCtaVariant}`,
});
}}
className="flex items-center gap-2"
>
<span>{heroPrimaryLabel}</span>
<ArrowRight className="h-4 w-4 transition group-hover:translate-x-1" aria-hidden />
</Link>
</Button>
<Button
asChild
variant="outline"
size="lg"
className="h-12 rounded-full border-rose-200 bg-white/80 px-6 text-base font-semibold text-rose-500 shadow-sm transition hover:bg-white dark:border-rose-500/30 dark:bg-transparent dark:text-rose-200 dark:hover:bg-white/10"
>
<Link
href={heroSecondaryHref}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'hero_secondary_cta',
})
}
className="flex items-center gap-2"
>
<span>{heroSecondaryLabel}</span>
</Link>
</Button>
<Link
href={heroTertiaryHref}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'hero_packages_cta',
})
}
className="font-semibold text-rose-500 underline-offset-4 hover:underline dark:text-rose-200"
>
{heroTertiaryLabel}
</Link>
</div>
</div>
<div className="md:w-1/2">
<div className="relative w-full max-w-xl md:w-1/2">
<div className="absolute inset-0 rounded-3xl bg-white/40 blur-xl" aria-hidden />
<img
src="/joyous_wedding_guests_posing.jpg"
alt={t('home.hero_image_alt')}
className="w-full h-auto rounded-lg shadow-lg"
className="relative w-full rounded-[32px] border border-white/60 shadow-2xl"
/>
</div>
</div>
</section>
{/* How it Works Section */}
<section className="py-20 px-4 bg-gray-50 dark:bg-gray-900">
<section id="how-it-works" className="bg-gray-50 py-20 px-4 dark:bg-gray-950">
<div className="container mx-auto max-w-6xl">
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 font-display">{t('home.how_title')}</h2>
<div className="grid md:grid-cols-3 gap-8">
<div className="text-center">
<div className="w-16 h-16 bg-[#FFB6C1] rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl">1</span>
</div>
<h3 className="text-xl font-semibold mb-2">{t('home.step1_title')}</h3>
<p>{t('home.step1_desc')}</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-[#FFB6C1] rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl">2</span>
</div>
<h3 className="text-xl font-semibold mb-2">{t('home.step2_title')}</h3>
<p>{t('home.step2_desc')}</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-[#FFB6C1] rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl">3</span>
</div>
<h3 className="text-xl font-semibold mb-2">{t('home.step3_title')}</h3>
<p>{t('home.step3_desc')}</p>
</div>
<div className="mx-auto max-w-3xl text-center">
<h2 className="font-display text-3xl font-bold md:text-4xl">{t('home.how_title')}</h2>
<p className="mt-4 text-base text-muted-foreground md:text-lg">
{t('home.how_subtitle')}
</p>
</div>
</div>
</section>
{/* Features Section */}
<section className="py-20 px-4 dark:bg-gray-700">
<div className="container mx-auto max-w-6xl">
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 font-display">{t('home.features_title')}</h2>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
<h3 className="text-xl font-semibold mb-2">{t('home.feature1_title')}</h3>
<p>{t('home.feature1_desc')}</p>
</div>
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
<h3 className="text-xl font-semibold mb-2">{t('home.feature2_title')}</h3>
<p>{t('home.feature2_desc')}</p>
</div>
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
<h3 className="text-xl font-semibold mb-2">{t('home.feature3_title')}</h3>
<p>{t('home.feature3_desc')}</p>
</div>
</div>
</div>
</section>
{/* Packages Teaser */}
<section className="py-20 px-4 bg-gray-50 dark:bg-gray-900">
<div className="container mx-auto max-w-6xl">
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 font-display">{t('home.packages_title')}</h2>
<div className="grid md:grid-cols-2 gap-8 mb-8">
{packages.slice(0, 2).map((pkg) => (
<div key={pkg.id} className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md text-center">
<h3 className="text-2xl font-bold mb-2">{pkg.name}</h3>
<p className="text-gray-600 dark:text-gray-300 mb-4">{pkg.description}</p>
<p className="text-3xl font-bold text-[#FFB6C1]">{pkg.price} {t('currency.euro')}</p>
<Link
href={`${localizedPath('/packages')}?package_id=${pkg.id}`}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'package_teaser_cta',
name: pkg.name,
value: pkg.price,
})
}
className="mt-4 inline-block bg-[#FFB6C1] text-white px-6 py-2 rounded-full hover:bg-pink-600"
>
{t('home.view_details')}
</Link>
</div>
<div className="mt-12 grid gap-6 md:grid-cols-3">
{howSteps.map(({ icon: Icon, title, description }, index) => (
<Card
key={`how-step-${index}`}
className="border-gray-200/70 bg-white/90 shadow-md shadow-gray-200/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/30 dark:border-gray-800/60 dark:bg-gray-900/60"
>
<CardHeader className="flex flex-col gap-4">
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-rose-100/80 text-rose-500 shadow-inner shadow-rose-200/60 dark:bg-rose-500/20 dark:text-rose-100">
<Icon className="h-6 w-6" aria-hidden />
</span>
<CardTitle className="text-xl">{title}</CardTitle>
<CardDescription className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
{description}
</CardDescription>
</CardHeader>
</Card>
))}
</div>
<div className="text-center">
<Link
href={localizedPath('/packages')}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'all_packages_cta',
})
}
className="bg-[#FFB6C1] text-white px-8 py-4 rounded-full font-bold hover:bg-pink-600 transition"
>
{t('home.all_packages')}
</Link>
</div>
</div>
</section>
{/* Contact Section */}
<section id="contact" className="py-20 px-4 dark:bg-gray-700">
<div className="container mx-auto max-w-4xl">
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 font-display">{t('home.contact_title')}</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium mb-2">
{t('home.name_label')} {t('common.required')}
</label>
<input
type="text"
id="name"
value={data.name}
onChange={(e) => setData('name', e.target.value)}
className="w-full p-3 border rounded-lg"
/>
{errors.name && <p className="text-red-500 text-sm">{errors.name}</p>}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-2">
{t('home.email_label')} {t('common.required')}
</label>
<input
type="email"
id="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
className="w-full p-3 border rounded-lg"
/>
{errors.email && <p className="text-red-500 text-sm">{errors.email}</p>}
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium mb-2">
{t('home.message_label')} {t('common.required')}
</label>
<textarea
id="message"
rows={4}
value={data.message}
onChange={(e) => setData('message', e.target.value)}
className="w-full p-3 border rounded-lg"
/>
{errors.message && <p className="text-red-500 text-sm">{errors.message}</p>}
</div>
<button
type="submit"
disabled={processing}
className="bg-[#FFB6C1] text-white px-8 py-3 rounded-full font-bold hover:bg-pink-600 transition disabled:opacity-50"
>
{processing ? t('home.sending') : t('home.send')}
</button>
</form>
</div>
</section>
{/* Testimonials Section */}
<section className="py-20 px-4 bg-gray-50 dark:bg-gray-900">
<section className="bg-slate-950 py-20 px-4 text-white">
<div className="container mx-auto max-w-6xl">
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 font-display">{t('home.testimonials_title')}</h2>
<div className="grid md:grid-cols-3 gap-8">
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
<p className="italic mb-4">"{t('home.testimonial1')}"</p>
<p className="font-semibold">{t('common.testimonials.anna.name')}</p>
<div className="grid gap-10 md:grid-cols-[1.1fr_0.9fr] md:items-center">
<div className="flex flex-col gap-6">
<Badge className="w-fit bg-white/15 px-3 py-1 text-xs uppercase tracking-[0.35em] text-white/80">
{t('home.demo_title')}
</Badge>
<h2 className="font-display text-3xl font-semibold leading-tight md:text-4xl">
{t('home.demo_description')}
</h2>
<p className="text-sm text-white/75">{t('home.demo_hint')}</p>
<Button
asChild
size="lg"
className="h-12 w-fit rounded-full bg-white px-7 text-base font-semibold text-slate-900 shadow-lg shadow-white/30 transition hover:bg-white/90"
>
<Link
href={localizedPath('/demo')}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'demo_section_cta',
})
}
className="flex items-center gap-2"
>
<span>{t('home.demo_cta')}</span>
<ArrowRight className="h-4 w-4" aria-hidden />
</Link>
</Button>
</div>
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
<p className="italic mb-4">"{t('home.testimonial2')}"</p>
<p className="font-semibold">{t('common.testimonials.max.name')}</p>
</div>
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
<p className="italic mb-4">"{t('home.testimonial3')}"</p>
<p className="font-semibold">{t('common.testimonials.lisa.name')}</p>
<div className="relative mx-auto w-full max-w-sm">
<div className="absolute inset-0 rounded-[42px] bg-gradient-to-br from-rose-400 via-purple-500 to-indigo-500 opacity-80 blur-2xl" aria-hidden />
<div className="relative aspect-[9/16] w-full overflow-hidden rounded-[42px] border border-white/20 bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 shadow-[0_40px_90px_-30px_rgba(15,23,42,0.75)]">
<div className="flex h-full flex-col items-center justify-center gap-4 p-6 text-center">
<Smartphone className="h-12 w-12 text-white/60" aria-hidden />
<p className="text-sm text-white/75">{t('home.demo_media_alt')}</p>
<Link
href={localizedPath('/demo')}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'demo_frame_cta',
})
}
className="text-sm font-semibold text-white underline-offset-4 hover:underline"
>
{t('home.demo_cta')}
</Link>
</div>
</div>
</div>
</div>
</div>
</section>
{/* FAQ Section */}
<section className="py-20 px-4 dark:bg-gray-700">
<section className="bg-white py-20 px-4 dark:bg-gray-950">
<div className="container mx-auto max-w-6xl">
<div className="mx-auto max-w-3xl text-center">
<h2 className="font-display text-3xl font-bold md:text-4xl">{t('home.features_title')}</h2>
</div>
<div className="mt-12 grid gap-6 md:grid-cols-3">
{features.map((feature, index) => (
<Card
key={`feature-${index}`}
className="border-gray-200/80 bg-gray-50/80 shadow-sm shadow-rose-100/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/40 dark:border-gray-800/60 dark:bg-gray-900/70"
>
<CardHeader>
<CardTitle className="text-xl">{feature.title}</CardTitle>
<CardDescription className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
{feature.description}
</CardDescription>
</CardHeader>
</Card>
))}
</div>
</div>
</section>
<section className="bg-gray-50 py-20 px-4 dark:bg-gray-950/80">
<div className="container mx-auto max-w-6xl">
<div className="grid gap-10 lg:grid-cols-[1.15fr_0.85fr]">
<Card className="border-rose-200/50 bg-white/95 shadow-md shadow-rose-200/40 dark:border-rose-500/30 dark:bg-gray-900/80">
<CardHeader className="flex flex-col gap-4">
<Badge className="w-fit bg-rose-100 px-3 py-1 text-xs font-semibold uppercase text-rose-600 dark:bg-rose-500/20 dark:text-rose-200">
{t('home.occasions_title')}
</Badge>
<CardTitle className="text-2xl">{t('home.occasions_description')}</CardTitle>
</CardHeader>
<CardContent className="grid gap-3 sm:grid-cols-2">
{occasionLinks.map(({ key, href }) => (
<Link
key={key}
href={href}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'occasion_tile_click',
name: key,
})
}
className="group flex items-center gap-3 rounded-xl border border-rose-100/60 bg-white/80 px-4 py-3 text-sm font-semibold text-rose-600 shadow-sm shadow-rose-100/50 transition hover:bg-rose-50 hover:text-rose-700 dark:border-rose-500/30 dark:bg-gray-900/70 dark:text-rose-200 dark:hover:bg-rose-500/10"
>
<ArrowRight className="h-4 w-4 -translate-x-1 transition group-hover:translate-x-0" aria-hidden />
<span>{t(`home.occasions.${key}`)}</span>
</Link>
))}
</CardContent>
</Card>
<Card className="border-gray-200/70 bg-white/95 shadow-md shadow-gray-200/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/40 dark:border-gray-800/60 dark:bg-gray-900/80">
<CardHeader>
<CardTitle className="text-2xl">{t('home.blog_teaser_title')}</CardTitle>
<CardDescription className="text-sm leading-relaxed text-muted-foreground">
{t('home.blog_teaser_description')}
</CardDescription>
</CardHeader>
<CardFooter className="px-6 pb-6">
<Button
asChild
variant="ghost"
className="group inline-flex items-center gap-2 rounded-full px-0 text-base font-semibold text-rose-500 transition hover:text-rose-600 dark:text-rose-200 dark:hover:text-rose-100"
>
<Link
href={localizedPath('/blog')}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'blog_teaser_cta',
})
}
className="flex items-center gap-2"
>
<span>{t('home.blog_teaser_cta')}</span>
<ArrowRight className="h-4 w-4 transition group-hover:translate-x-1" aria-hidden />
</Link>
</Button>
</CardFooter>
</Card>
</div>
</div>
</section>
<section className="bg-white py-20 px-4 dark:bg-gray-950">
<div className="container mx-auto max-w-6xl">
<div className="flex flex-col items-center justify-between gap-4 text-center md:flex-row md:text-left">
<div>
<h2 className="font-display text-3xl font-bold md:text-4xl">
{t('home.packages_title')}
</h2>
<p className="mt-3 max-w-2xl text-base text-muted-foreground md:text-lg">
{t('home.packages_subtitle')}
</p>
</div>
<Button
asChild
className="h-11 rounded-full bg-gradient-to-r from-[#ff5f87] via-[#ec4899] to-[#6366f1] px-6 text-sm font-semibold text-white shadow-lg shadow-rose-300/30 transition hover:from-[#ff4470] hover:via-[#ec4899] hover:to-[#4f46e5]"
>
<Link
href={localizedPath('/packages')}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'all_packages_cta',
})
}
>
{t('home.all_packages')}
</Link>
</Button>
</div>
<div className="mt-10 grid gap-8 md:grid-cols-2">
{packages.slice(0, 2).map((pkg) => (
<Card
key={pkg.id}
className="border-gray-200 bg-white/95 text-center shadow-md shadow-rose-200/30 transition hover:-translate-y-1 hover:shadow-xl hover:shadow-rose-200/40 dark:border-gray-800 dark:bg-gray-900/80"
>
<CardHeader className="gap-4">
<CardTitle className="text-2xl">{pkg.name}</CardTitle>
<CardDescription className="text-sm text-muted-foreground">
{pkg.description}
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center gap-4">
<p className="text-3xl font-bold text-rose-500">
{pkg.price} {t('currency.euro')}
</p>
<Button
asChild
className="rounded-full bg-rose-500 px-5 py-2 text-sm font-semibold text-white shadow-lg shadow-rose-200/40 transition hover:bg-rose-600"
>
<Link
href={`${localizedPath('/packages')}?package_id=${pkg.id}`}
onClick={() =>
trackEvent({
category: 'marketing_home',
action: 'package_teaser_cta',
name: pkg.name,
value: pkg.price,
})
}
>
{t('home.view_details')}
</Link>
</Button>
</CardContent>
</Card>
))}
</div>
</div>
</section>
<section id="contact" className="bg-gray-50 py-20 px-4 dark:bg-gray-950/80">
<div className="container mx-auto max-w-4xl">
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 font-display">{t('home.faq_title')}</h2>
<div className="space-y-4">
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg">
<h3 className="font-semibold">{t('home.faq1_q')}</h3>
<p>{t('home.faq1_a')}</p>
</div>
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg">
<h3 className="font-semibold">{t('home.faq2_q')}</h3>
<p>{t('home.faq2_a')}</p>
</div>
<Card className="border-gray-200/70 bg-white/95 shadow-lg shadow-rose-200/40 dark:border-gray-800/60 dark:bg-gray-900/80">
<CardHeader className="text-center">
<Badge className="mx-auto mb-3 w-fit bg-rose-100 px-3 py-1 text-xs font-semibold uppercase text-rose-600 dark:bg-rose-500/20 dark:text-rose-200">
{t('home.contact_title')}
</Badge>
<CardTitle className="text-2xl md:text-3xl">{t('home.contact_lead')}</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid gap-4 md:grid-cols-2">
<div className="flex flex-col gap-2">
<label htmlFor="name" className="text-sm font-semibold text-gray-600 dark:text-gray-200">
{t('home.name_label')} *
</label>
<Input
id="name"
value={data.name}
onChange={(event) => setData('name', event.target.value)}
className="h-12 rounded-xl border-gray-200/70 bg-white/90 shadow-inner shadow-gray-200/40 focus-visible:ring-rose-300/60 dark:border-gray-700 dark:bg-gray-900/70"
autoComplete="name"
required
/>
{errors.name && (
<p className="text-sm font-medium text-rose-600 dark:text-rose-300">{errors.name}</p>
)}
</div>
<div className="flex flex-col gap-2">
<label htmlFor="email" className="text-sm font-semibold text-gray-600 dark:text-gray-200">
{t('home.email_label')} *
</label>
<Input
id="email"
type="email"
value={data.email}
onChange={(event) => setData('email', event.target.value)}
className="h-12 rounded-xl border-gray-200/70 bg-white/90 shadow-inner shadow-gray-200/40 focus-visible:ring-rose-300/60 dark:border-gray-700 dark:bg-gray-900/70"
autoComplete="email"
required
/>
{errors.email && (
<p className="text-sm font-medium text-rose-600 dark:text-rose-300">{errors.email}</p>
)}
</div>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="message" className="text-sm font-semibold text-gray-600 dark:text-gray-200">
{t('home.message_label')} *
</label>
<Textarea
id="message"
rows={5}
value={data.message}
onChange={(event) => setData('message', event.target.value)}
className="rounded-xl border-gray-200/70 bg-white/90 shadow-inner shadow-gray-200/40 focus-visible:ring-rose-300/60 dark:border-gray-700 dark:bg-gray-900/70"
required
/>
{errors.message && (
<p className="text-sm font-medium text-rose-600 dark:text-rose-300">{errors.message}</p>
)}
</div>
<div className="space-y-3 text-sm text-muted-foreground">
<p>{t('home.contact_privacy')}</p>
<Button
type="submit"
disabled={processing}
className="h-12 w-full rounded-full bg-gradient-to-r from-[#ff5f87] via-[#ec4899] to-[#6366f1] text-base font-semibold text-white shadow-[0_18px_35px_-18px_rgba(236,72,153,0.7)] transition hover:from-[#ff4470] hover:via-[#ec4899] hover:to-[#4f46e5] disabled:cursor-not-allowed disabled:opacity-60"
>
{processing ? t('home.sending') : t('home.send')}
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
</section>
<section className="bg-white py-20 px-4 dark:bg-gray-950">
<div className="container mx-auto max-w-6xl">
<div className="mx-auto max-w-3xl text-center">
<h2 className="font-display text-3xl font-bold md:text-4xl">
{t('home.testimonials_title')}
</h2>
<p className="mt-4 text-base text-muted-foreground">{t('home.testimonials_subtitle')}</p>
</div>
<div className="mt-12 grid gap-6 md:grid-cols-3">
{[t('home.testimonial1'), t('home.testimonial2'), t('home.testimonial3')].map(
(quote, index) => (
<Card
key={`testimonial-${index}`}
className="border-gray-200/70 bg-white/95 shadow-md shadow-rose-200/40 dark:border-gray-800/60 dark:bg-gray-900/80"
>
<CardContent className="flex h-full flex-col justify-between gap-4 p-6">
<p className="text-base italic text-slate-700 dark:text-slate-200">{quote}</p>
</CardContent>
</Card>
),
)}
</div>
</div>
</section>
<section className="bg-gray-50 py-20 px-4 dark:bg-gray-950/80">
<div className="container mx-auto max-w-4xl">
<div className="mx-auto text-center">
<h2 className="font-display text-3xl font-bold md:text-4xl">{t('home.faq_title')}</h2>
<p className="mt-3 text-base text-muted-foreground">{t('home.faq_subtitle')}</p>
</div>
<div className="mt-10 space-y-4">
{[{ q: t('home.faq1_q'), a: t('home.faq1_a') }, { q: t('home.faq2_q'), a: t('home.faq2_a') }].map(
({ q, a }, index) => (
<Card
key={`faq-${index}`}
className="border-gray-200/70 bg-white/95 shadow-sm shadow-gray-200/40 dark:border-gray-800/60 dark:bg-gray-900/80"
>
<CardHeader>
<CardTitle className="text-lg">{q}</CardTitle>
<CardDescription className="text-sm leading-relaxed text-muted-foreground">
{a}
</CardDescription>
</CardHeader>
</Card>
),
)}
</div>
</div>
</section>

View File

@@ -0,0 +1,423 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
import MarketingLayout from '@/layouts/mainWebsite';
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { CheckCircle2, Images, Sparkles, Users } from 'lucide-react';
type HeroStat = { value: string; label: string };
type ExperienceStep = { title: string; description: string };
type ExperienceGroup = {
label: string;
intro: string;
steps: ExperienceStep[];
callouts: string[];
};
type TimelineItem = { title: string; body: string; tips: string[] };
type UseCase = {
value: string;
label: string;
goal: string;
recommendations: string[];
ideas: string[];
};
type FaqItem = { question: string; answer: string };
const iconByUseCase: Record<string, React.ReactNode> = {
wedding: <Sparkles className="h-6 w-6 text-pink-500" aria-hidden />,
birthday: <Sparkles className="h-6 w-6 text-pink-500" aria-hidden />,
corporate: <Sparkles className="h-6 w-6 text-pink-500" aria-hidden />,
confirmation: <Sparkles className="h-6 w-6 text-pink-500" aria-hidden />,
public: <Users className="h-6 w-6 text-pink-500" aria-hidden />,
};
const HowItWorks: React.FC = () => {
const { t } = useTranslation('marketing');
const { localizedPath } = useLocalizedRoutes();
const hero = t('how_it_works_page.hero', { returnObjects: true }) as {
title: string;
subtitle: string;
primaryCta: string;
secondaryCta: string;
stats: HeroStat[];
};
const experience = t('how_it_works_page.experience', { returnObjects: true }) as {
host: ExperienceGroup;
guest: ExperienceGroup;
};
const pillars = t('how_it_works_page.pillars', { returnObjects: true }) as Array<{
title: string;
description: string;
}>;
const timeline = t('how_it_works_page.timeline', { returnObjects: true }) as TimelineItem[];
const useCases = t('how_it_works_page.use_cases', { returnObjects: true }) as {
title: string;
description: string;
tabs: UseCase[];
};
const checklist = t('how_it_works_page.checklist', { returnObjects: true }) as {
title: string;
items: string[];
cta: string;
};
const faq = t('how_it_works_page.faq', { returnObjects: true }) as {
title: string;
items: FaqItem[];
};
const support = t('how_it_works_page.support', { returnObjects: true }) as {
title: string;
description: string;
cta: string;
};
return (
<MarketingLayout title={hero.title}>
<Head title={hero.title} />
<section className="relative overflow-hidden bg-gradient-to-br from-pink-100 via-white to-white px-4 py-16 dark:from-pink-950/40 dark:via-gray-950 dark:to-gray-950">
<div className="absolute -top-24 right-24 hidden h-64 w-64 rounded-full bg-pink-200/60 blur-3xl dark:bg-pink-900/50 lg:block" />
<div className="container mx-auto relative z-10 flex max-w-6xl flex-col gap-12 lg:flex-row lg:items-center">
<div className="flex-1 space-y-6">
<Badge variant="outline" className="border-pink-400 text-pink-600 dark:border-pink-500 dark:text-pink-300">
Fotospiel Flow
</Badge>
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-50 md:text-5xl">
{hero.title}
</h1>
<p className="max-w-2xl text-lg text-gray-600 dark:text-gray-300">
{hero.subtitle}
</p>
<div className="flex flex-wrap items-center gap-3">
<Button asChild size="lg" className="bg-pink-500 hover:bg-pink-600">
<Link href={localizedPath('/register')}>
{hero.primaryCta}
</Link>
</Button>
<Button asChild size="lg" variant="outline" className="border-pink-200 text-pink-600 hover:bg-pink-100 dark:border-pink-900 dark:text-pink-300 dark:hover:bg-pink-900/40">
<Link href={localizedPath('/kontakt')}>
{hero.secondaryCta}
</Link>
</Button>
</div>
</div>
<div className="flex-1">
<div className="grid gap-4 sm:grid-cols-3">
{hero.stats.map((stat) => (
<Card key={stat.label} className="border-pink-100/70 shadow-none dark:border-pink-900/40">
<CardHeader className="pb-2">
<CardTitle className="text-2xl font-semibold text-pink-600 dark:text-pink-300">
{stat.value}
</CardTitle>
</CardHeader>
<CardContent>
<CardDescription className="text-sm text-gray-600 dark:text-gray-300">
{stat.label}
</CardDescription>
</CardContent>
</Card>
))}
</div>
</div>
</div>
</section>
<section className="container mx-auto px-4 py-16">
<div className="mx-auto max-w-5xl">
<Tabs defaultValue="host" className="w-full">
<TabsList className="flex w-full justify-start gap-2 bg-pink-50/60 p-2 dark:bg-gray-900/60">
<TabsTrigger value="host" className="flex-1 text-base">
{experience.host.label}
</TabsTrigger>
<TabsTrigger value="guest" className="flex-1 text-base">
{experience.guest.label}
</TabsTrigger>
</TabsList>
<TabsContent value="host" className="mt-6">
<ExperiencePanel data={experience.host} />
</TabsContent>
<TabsContent value="guest" className="mt-6">
<ExperiencePanel data={experience.guest} />
</TabsContent>
</Tabs>
</div>
</section>
<section className="container mx-auto px-4 py-16">
<div className="mx-auto max-w-6xl text-center">
<Badge variant="secondary" className="mb-4">Core Features</Badge>
<h2 className="text-3xl font-bold text-gray-900 dark:text-gray-50 md:text-4xl">
{t('home.features_title', 'Warum Fotospiel?')}
</h2>
<p className="mt-3 text-lg text-gray-600 dark:text-gray-300">
{t('home.hero_description')}
</p>
</div>
<div className="mx-auto mt-10 grid max-w-6xl gap-6 md:grid-cols-2">
{pillars.map((pillar) => (
<Card key={pillar.title} className="border-gray-100 shadow-sm dark:border-gray-800">
<CardHeader>
<CardTitle className="flex items-center gap-3 text-left text-xl">
<Sparkles className="h-5 w-5 text-pink-500" aria-hidden />
{pillar.title}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-left text-gray-600 dark:text-gray-300">
{pillar.description}
</p>
</CardContent>
</Card>
))}
</div>
</section>
<section className="bg-gradient-to-br from-white via-pink-50/40 to-white px-4 py-16 dark:from-gray-950 dark:via-gray-900 dark:to-gray-950">
<div className="container mx-auto max-w-5xl">
<div className="mb-8 text-center">
<Badge variant="outline" className="border-pink-400 text-pink-600 dark:border-pink-500 dark:text-pink-300">
{t('how_it_works_page.timeline_title', 'Der Ablauf im Detail')}
</Badge>
<h2 className="mt-3 text-3xl font-bold text-gray-900 dark:text-gray-50">
Ein klarer Fahrplan für dein Event
</h2>
</div>
<Accordion type="single" collapsible className="w-full">
{timeline.map((item, index) => (
<AccordionItem key={item.title} value={`step-${index}`}>
<AccordionTrigger className="text-left text-lg">
<div className="flex items-center gap-3">
<Badge variant="secondary" className="rounded-full">{index + 1}</Badge>
<span>{item.title}</span>
</div>
</AccordionTrigger>
<AccordionContent>
<p className="text-gray-600 dark:text-gray-300">
{item.body}
</p>
{item.tips?.length ? (
<div className="mt-4 rounded-lg border border-pink-100 bg-white p-4 text-sm text-gray-600 shadow-sm dark:border-pink-900/40 dark:bg-gray-900 dark:text-gray-300">
<p className="mb-2 font-semibold text-pink-600 dark:text-pink-300">
{t('marketing.actions.tips', 'Tipps')}
</p>
<ul className="space-y-1">
{item.tips.map((tip) => (
<li key={tip} className="flex items-start gap-2">
<span className="mt-1 inline-flex h-1.5 w-1.5 rounded-full bg-pink-400" aria-hidden />
<span>{tip}</span>
</li>
))}
</ul>
</div>
) : null}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</section>
<section className="container mx-auto px-4 py-16">
<div className="mx-auto max-w-5xl text-center">
<Badge variant="secondary" className="mb-4">
{useCases.title}
</Badge>
<h2 className="text-3xl font-bold text-gray-900 dark:text-gray-50 md:text-4xl">
{useCases.description}
</h2>
</div>
<div className="mx-auto mt-10 max-w-5xl">
<Tabs defaultValue={useCases.tabs[0]?.value ?? ''}>
<TabsList className="flex flex-wrap gap-2 bg-transparent p-0">
{useCases.tabs.map((tab) => (
<TabsTrigger
key={tab.value}
value={tab.value}
className="rounded-full border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 shadow-sm data-[state=active]:border-pink-500 data-[state=active]:bg-pink-50 data-[state=active]:text-pink-600 dark:border-gray-800 dark:text-gray-200 dark:data-[state=active]:border-pink-500 dark:data-[state=active]:bg-pink-900/40"
>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{useCases.tabs.map((tab) => (
<TabsContent key={tab.value} value={tab.value} className="mt-6">
<Card className="border-gray-100 shadow-md dark:border-gray-800">
<CardHeader className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<CardTitle className="text-2xl">
{tab.label}
</CardTitle>
<CardDescription className="text-base text-gray-600 dark:text-gray-300">
{tab.goal}
</CardDescription>
</div>
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-pink-100 dark:bg-pink-900/40">
{iconByUseCase[tab.value] ?? <Images className="h-6 w-6 text-pink-500" aria-hidden />}
</div>
</CardHeader>
<CardContent className="grid gap-6 md:grid-cols-2">
<div>
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-pink-600 dark:text-pink-300">
{t('marketing.labels.recommendations', 'Empfehlungen')}
</p>
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-300">
{tab.recommendations.map((item) => (
<li key={item} className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 text-pink-500" aria-hidden />
<span>{item}</span>
</li>
))}
</ul>
</div>
<div>
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-pink-600 dark:text-pink-300">
{t('marketing.labels.challengeIdeas', 'Ideen für Challenges')}
</p>
<div className="flex flex-wrap gap-2">
{tab.ideas.map((idea) => (
<Badge key={idea} variant="outline" className="border-pink-200 text-pink-600 dark:border-pink-900/40 dark:text-pink-300">
{idea}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
</TabsContent>
))}
</Tabs>
</div>
</section>
<section className="container mx-auto px-4 py-16">
<div className="mx-auto max-w-4xl">
<Card className="border-gray-100 shadow-sm dark:border-gray-800">
<CardHeader>
<CardTitle>{checklist.title}</CardTitle>
<CardDescription>
{t('marketing.labels.prepHint', 'Alles, was du vor dem Event abhaken solltest.')}
</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-3 text-gray-600 dark:text-gray-300">
{checklist.items.map((item) => (
<li key={item} className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-5 w-5 text-pink-500" aria-hidden />
<span>{item}</span>
</li>
))}
</ul>
<Button asChild className="mt-6 bg-pink-500 hover:bg-pink-600">
<Link href={localizedPath('/register')}>
{checklist.cta}
</Link>
</Button>
</CardContent>
</Card>
</div>
</section>
<section className="container mx-auto px-4 pb-16">
<div className="mx-auto max-w-4xl">
<h2 className="text-3xl font-bold text-gray-900 dark:text-gray-50">
{faq.title}
</h2>
<Accordion type="single" collapsible className="mt-6">
{faq.items.map((item, index) => (
<AccordionItem key={item.question} value={`faq-${index}`}>
<AccordionTrigger className="text-left text-lg">
{item.question}
</AccordionTrigger>
<AccordionContent className="text-gray-600 dark:text-gray-300">
{item.answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</section>
<section className="bg-pink-50/80 px-4 py-14 dark:bg-pink-950/30">
<div className="container mx-auto max-w-4xl">
<Alert className="border-pink-200 bg-white shadow-lg dark:border-pink-800 dark:bg-gray-950">
<AlertTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
{support.title}
</AlertTitle>
<AlertDescription className="mt-2 flex flex-col gap-4 text-gray-600 dark:text-gray-300 md:flex-row md:items-center md:justify-between">
<span className="max-w-xl text-base">{support.description}</span>
<Button asChild className="bg-pink-500 hover:bg-pink-600">
<Link href={localizedPath('/kontakt')}>
{support.cta}
</Link>
</Button>
</AlertDescription>
</Alert>
</div>
</section>
</MarketingLayout>
);
};
const ExperiencePanel: React.FC<{ data: ExperienceGroup }> = ({ data }) => {
const { t } = useTranslation('marketing');
return (
<Card className="border-gray-100 shadow-md dark:border-gray-800">
<CardHeader>
<CardTitle className="text-2xl text-gray-900 dark:text-gray-50">
{data.label}
</CardTitle>
<CardDescription className="text-base text-gray-600 dark:text-gray-300">
{data.intro}
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-4 md:grid-cols-3">
{data.steps.map((step, index) => (
<div key={step.title} className="rounded-lg border border-gray-100 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<div className="mb-3 inline-flex h-9 w-9 items-center justify-center rounded-full bg-pink-100 text-base font-semibold text-pink-600 dark:bg-pink-900/40 dark:text-pink-300">
{index + 1}
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
{step.title}
</h3>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
{step.description}
</p>
</div>
))}
</div>
{data.callouts?.length ? (
<div className="rounded-xl border border-pink-100 bg-pink-50/60 p-6 dark:border-pink-900/40 dark:bg-pink-950/30">
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-pink-600 dark:text-pink-300">
{t('how_it_works_page.labels.good_to_know', 'Gut zu wissen')}
</p>
<ul className="grid gap-3 md:grid-cols-3">
{data.callouts.map((item) => (
<li key={item} className="flex items-start gap-2 text-sm text-pink-700 dark:text-pink-200">
<Sparkles className="mt-0.5 h-4 w-4" aria-hidden />
<span>{item}</span>
</li>
))}
</ul>
</div>
) : null}
</CardContent>
</Card>
);
};
export default HowItWorks;

View File

@@ -46,6 +46,17 @@ const Occasions: React.FC<OccasionsProps> = ({ type }) => {
],
cta: t('occasions.cta'),
},
konfirmation: {
title: t('occasions.confirmation.title'),
description: t('occasions.confirmation.description'),
features: [
t('occasions.confirmation.benefit1'),
t('occasions.confirmation.benefit2'),
t('occasions.confirmation.benefit3'),
t('occasions.confirmation.benefit4'),
],
cta: t('occasions.cta'),
},
};
const content = occasionsContent[type as keyof typeof occasionsContent] || occasionsContent.hochzeit;
@@ -78,4 +89,4 @@ const Occasions: React.FC<OccasionsProps> = ({ type }) => {
);
};
export default Occasions;
export default Occasions;

View File

@@ -2,7 +2,6 @@ import React, { useState, useEffect, useMemo } from 'react';
import { Head, Link, usePage } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next';
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
@@ -484,27 +483,7 @@ function PackageCard({
<div className="container mx-auto">
<h2 className="text-3xl font-bold text-center mb-12 font-display">{t('packages.section_endcustomer')}</h2>
<div className="block md:hidden">
<Carousel className="mx-auto w-full max-w-md" opts={{ loop: true }}>
<CarouselContent className="-ml-2">
{endcustomerPackages.map((pkg) => (
<CarouselItem key={pkg.id} className="basis-full pl-2">
<PackageCard
pkg={pkg}
variant="endcustomer"
highlight={pkg.id === highlightEndcustomerId}
onSelect={(pkg) => handleCardClick(pkg, 'endcustomer')}
className="h-full"
/>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
<div className="hidden gap-6 md:grid md:grid-cols-2 lg:gap-8 lg:grid-cols-3">
<div className="grid gap-6 sm:grid-cols-2 lg:gap-8 lg:grid-cols-3">
{endcustomerPackages.map((pkg) => (
<PackageCard
key={pkg.id}
@@ -652,27 +631,7 @@ function PackageCard({
<div className="container mx-auto">
<h2 className="text-3xl font-bold text-center mb-12 font-display">{t('packages.section_reseller')}</h2>
<div className="block md:hidden">
<Carousel className="mx-auto w-full max-w-md" opts={{ loop: true }}>
<CarouselContent className="-ml-2">
{resellerPackages.map((pkg) => (
<CarouselItem key={pkg.id} className="basis-full pl-2">
<PackageCard
pkg={pkg}
variant="reseller"
highlight={pkg.id === highlightResellerId}
onSelect={(pkg) => handleCardClick(pkg, 'reseller')}
className="h-full"
/>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
<div className="hidden gap-6 md:grid md:grid-cols-2 lg:gap-8 xl:grid-cols-3">
<div className="grid gap-6 sm:grid-cols-2 lg:gap-8 xl:grid-cols-3">
{resellerPackages.map((pkg) => (
<PackageCard
key={pkg.id}

View File

@@ -144,7 +144,7 @@ export const PaymentStep: React.FC = () => {
const inlineSupported = initialised && !!paddleConfig?.client_token;
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-console
console.info('[Checkout] Paddle inline status', {
inlineSupported,
initialised,
@@ -188,7 +188,7 @@ export const PaymentStep: React.FC = () => {
}
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-console
console.info('[Checkout] Opening inline Paddle checkout', inlinePayload);
}
@@ -214,7 +214,7 @@ export const PaymentStep: React.FC = () => {
const rawBody = await response.text();
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-console
console.info('[Checkout] Hosted checkout response', { status: response.status, rawBody });
}
@@ -270,7 +270,7 @@ export const PaymentStep: React.FC = () => {
}
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-console
console.debug('[Checkout] Paddle event', event);
}
@@ -307,7 +307,7 @@ export const PaymentStep: React.FC = () => {
let inlineReady = false;
if (typeof paddle.Initialize === 'function' && clientToken) {
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-console
console.info('[Checkout] Initializing Paddle.js', { environment, hasToken: Boolean(clientToken) });
}