überarbeitung des event-admins fortgesetzt

This commit is contained in:
Codex Agent
2025-11-25 13:03:42 +01:00
parent fd788ef770
commit 596dcbf18a
20 changed files with 998 additions and 2210 deletions

View File

@@ -1,94 +1,61 @@
import React from 'react';
import { Navigate } from 'react-router-dom';
import {
ArrowRight,
Camera,
Clock3,
Heart,
MessageCircle,
CheckCircle2,
Layers,
ListChecks,
Menu,
Moon,
Palette,
QrCode,
ShieldCheck,
Smartphone,
Sparkles,
SunMedium,
Sun,
Wand2,
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
import { cn } from '@/lib/utils';
import { LanguageSwitcher } from '../components/LanguageSwitcher';
import { FrostedSurface } from '../components/tenant';
import { useAppearance } from '@/hooks/use-appearance';
import { ADMIN_DEFAULT_AFTER_LOGIN_PATH, ADMIN_LOGIN_PATH } from '../constants';
import { encodeReturnTo, resolveReturnTarget } from '../lib/returnTo';
import { useAuth } from '../auth/context';
import { LanguageSwitcher } from '../components/LanguageSwitcher';
import { navigateToHref } from '../lib/navigation';
import { getCurrentLocale } from '../lib/locale';
const heroStats = [
{ label: 'Events begleitet', value: '2.100+' },
{ label: 'Fotos kuratiert', value: '680k' },
{ label: 'Mission Cards live', value: '120+' },
];
type Feature = {
key: string;
title: string;
description: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
};
const featureCards = [
{
icon: Sparkles,
badge: 'Welcome Flow',
title: 'Geführte Einrichtung',
description:
'Ein roter Faden von eurer ersten Mission bis zum Event-Branding. Alles läuft dort, wo ihr später auch Events steuert.',
},
{
icon: QrCode,
badge: 'Gäste einladen',
title: 'Links & QR-Cards teilen',
description:
'Mit einem Fingertipp entstehen Einladungslinks, QR-Codes und TWA-Links für Android & iOS ganz ohne App Store Hürden.',
},
{
icon: Camera,
badge: 'Live Galerie',
title: 'Moderieren wie im Dashboard',
description:
'Markiert Highlights, ordnet Emotions oder sperrt Fotos exakt die Tools aus dem Event Admin, nur mit Willkommens-Glow.',
},
];
type Step = {
key: string;
title: string;
description: string;
accent: string;
};
const timelineSteps = [
{
title: 'Mission Cards wählen',
description: 'Kuratiert Aufgaben, die eure Gäste ins Erzählen bringen. Jeder Schritt speichert automatisch.',
},
{
title: 'Event Branding festlegen',
description: 'Farben, Story, Cover alles in einer Ansicht. Vorschau zeigt sofort, wie der Gastzugang wirkt.',
},
{
title: 'Link teilen & feiern',
description: 'QR-Code oder Link verschicken, fertig. Gäste landen direkt in eurer Galerie und können ohne Login starten.',
},
];
const supportHighlights = [
{
icon: ShieldCheck,
title: 'Datenschutz-ready',
description: 'Keine versteckten Tracker, DSGVO-konformes Hosting und Kontrolle, wer Fotos sieht.',
},
{
icon: Clock3,
title: 'Offline nutzbar',
description: 'Uploads puffern automatisch, falls das WLAN in der Location aussetzt.',
},
{
icon: MessageCircle,
title: 'Crew an eurer Seite',
description: 'Direkter Chat zum Fotospiel-Team aus der App heraus oder via hallo@fotospiel.de.',
},
];
type Plan = {
key: string;
title: string;
badge?: string;
highlight?: string;
points: string[];
};
export default function WelcomeTeaserPage() {
const [isRedirecting, setIsRedirecting] = React.useState(false);
const [mode, setMode] = React.useState<'dark' | 'light'>('dark');
const isLightMode = mode === 'light';
const flowSectionRef = React.useRef<HTMLDivElement | null>(null);
const { t } = useTranslation('common');
const { status } = useAuth();
const { appearance, updateAppearance } = useAppearance();
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
React.useEffect(() => {
document.body.classList.add('tenant-admin-theme', 'tenant-admin-welcome-theme');
@@ -98,395 +65,428 @@ export default function WelcomeTeaserPage() {
};
}, []);
React.useEffect(() => {
document.body.classList.toggle('tenant-admin-welcome-light', isLightMode);
document.body.classList.toggle('tenant-admin-welcome-dark', !isLightMode);
}, [isLightMode]);
if (status === 'authenticated') {
return <Navigate to={ADMIN_DEFAULT_AFTER_LOGIN_PATH} replace />;
}
const theme = React.useMemo(
() => ({
rootBackground: isLightMode
? 'bg-gradient-to-br from-rose-50 via-white to-sky-50 text-slate-900'
: 'bg-slate-950 text-white',
aurora: isLightMode
? 'bg-[radial-gradient(ellipse_at_top,_rgba(255,179,205,0.55),_transparent_55%),radial-gradient(ellipse_at_bottom,_rgba(148,187,233,0.45),_transparent_60%)]'
: 'bg-[radial-gradient(ellipse_at_top,_rgba(255,137,170,0.25),_transparent_60%),radial-gradient(ellipse_at_bottom,_rgba(99,102,241,0.25),_transparent_62%)]',
overlay: isLightMode
? 'bg-gradient-to-br from-white/85 via-rose-50/70 to-sky-50/70'
: 'bg-gradient-to-br from-slate-950/95 via-slate-950/80 to-[#150b1f]/90',
headerBadge: isLightMode ? 'border-rose-100 bg-white text-rose-500' : 'border-white/30 bg-white/5 text-white',
headerSubtitle: isLightMode ? 'text-slate-600' : 'text-white/70',
heroEyebrow: isLightMode ? 'text-rose-500' : 'text-rose-200',
heroTitle: isLightMode ? 'text-slate-900' : 'text-white',
heroBody: isLightMode ? 'text-slate-600' : 'text-white/75',
ghostButton: isLightMode ? 'text-slate-700 hover:bg-slate-100' : 'text-white/80 hover:bg-white/10',
statCard: isLightMode
? 'border-rose-100/70 bg-white/90 text-slate-900 shadow-rose-100/50'
: 'border-white/15 bg-white/10 text-white shadow-rose-500/30',
featureCard: isLightMode
? 'border-rose-100 bg-white text-slate-900 shadow-rose-100/40'
: 'border-white/15 bg-white/10 text-white shadow-slate-900/40',
timelineCard: isLightMode ? 'border-slate-200 bg-white text-slate-900' : 'border-white/15 bg-white/5 text-white',
timelineDescription: isLightMode ? 'text-slate-600' : 'text-white/70',
supportCard: isLightMode ? 'border-slate-200 bg-white text-slate-900' : 'border-white/15 bg-white/10 text-white',
supportDescription: isLightMode ? 'text-slate-600' : 'text-white/70',
ctaSurface: isLightMode ? 'border-slate-200 bg-white text-slate-900' : 'border-white/15 bg-white/10 text-white',
ctaDetail: isLightMode ? 'text-slate-600' : 'text-white/70',
footer: isLightMode
? 'border-t border-slate-200/80 bg-white/70 text-slate-500'
: 'border-t border-white/10 bg-black/20 text-white/60',
}),
[isLightMode]
const locale = getCurrentLocale();
const packagesHref = `/${locale}/packages`;
const howItWorksHref = locale === 'de' ? `/${locale}/so-funktionierts` : `/${locale}/how-it-works`;
const features: Feature[] = [
{
key: 'branding',
title: t('welcome.features.branding.title', 'Branding & Layout'),
description: t('welcome.features.branding.description', 'Farben, Schriften, QR-Layouts und Einladungen in einem Fluss.'),
icon: Palette,
},
{
key: 'tasks',
title: t('welcome.features.tasks.title', 'Aufgaben & Emotion-Sets'),
description: t('welcome.features.tasks.description', 'Sammlungen importieren oder eigene Aufgaben erstellen mobil abhakbar.'),
icon: ListChecks,
},
{
key: 'moderation',
title: t('welcome.features.moderation.title', 'Foto-Moderation'),
description: t('welcome.features.moderation.description', 'Uploads sofort prüfen, Highlights markieren und Galerie-Link teilen.'),
icon: ShieldCheck,
},
{
key: 'invites',
title: t('welcome.features.invites.title', 'Einladungen & QR'),
description: t('welcome.features.invites.description', 'Links und Druckvorlagen generieren mit Paketlimits im Blick.'),
icon: QrCode,
},
];
const steps: Step[] = [
{
key: 'prepare',
title: t('welcome.steps.prepare.title', 'Vorbereiten'),
description: t('welcome.steps.prepare.description', 'Event anlegen, Branding setzen, Aufgaben aktivieren.'),
accent: t('welcome.steps.prepare.accent', 'Setup'),
},
{
key: 'share',
title: t('welcome.steps.share.title', 'Teilen & Einladen'),
description: t('welcome.steps.share.description', 'QRs/Links verteilen, Missionen auswählen, Team onboarden.'),
accent: t('welcome.steps.share.accent', 'Share'),
},
{
key: 'run',
title: t('welcome.steps.run.title', 'Live moderieren'),
description: t('welcome.steps.run.description', 'Uploads prüfen, Highlights pushen und nach dem Event die Galerie teilen.'),
accent: t('welcome.steps.run.accent', 'Live'),
},
];
const plans: Plan[] = [
{
key: 'starter',
title: t('welcome.plans.starter.title', 'Starter'),
badge: t('welcome.plans.starter.badge', 'Für ein Event'),
points: [
t('welcome.plans.starter.p1', '1 Event, Basis-Branding'),
t('welcome.plans.starter.p2', 'Aufgaben & Einladungen inklusive'),
t('welcome.plans.starter.p3', 'Moderation & Galerie-Link'),
],
},
{
key: 'standard',
title: t('welcome.plans.standard.title', 'Standard'),
badge: t('welcome.plans.standard.badge', 'Beliebt'),
highlight: t('welcome.plans.standard.highlight', 'Mehr Kontingent & Branding'),
points: [
t('welcome.plans.standard.p1', 'Mehr Events pro Jahr'),
t('welcome.plans.standard.p2', 'Erweitertes Branding & Layouts'),
t('welcome.plans.standard.p3', 'Support bei Live-Events'),
],
},
{
key: 'reseller',
title: t('welcome.plans.reseller.title', 'Reseller S'),
badge: t('welcome.plans.reseller.badge', 'Für Dienstleister'),
highlight: t('welcome.plans.reseller.highlight', 'Mehrere Events parallel verwalten'),
points: [
t('welcome.plans.reseller.p1', 'Bis zu 5 Events pro Paket'),
t('welcome.plans.reseller.p2', 'Aufgaben-Sammlungen und Vorlagen'),
t('welcome.plans.reseller.p3', 'Teamrollen & Rechteverwaltung'),
],
},
];
const audienceCards = [
{
key: 'endcustomers',
title: t('welcome.audience.endcustomers.title', 'Endkund:innen'),
description: t('welcome.audience.endcustomers.description', 'Schnell einrichten, mobil moderieren und nach dem Event die Galerie teilen.'),
icon: Smartphone,
},
{
key: 'resellers',
title: t('welcome.audience.resellers.title', 'Reseller & Agenturen'),
description: t('welcome.audience.resellers.description', 'Mehrere Events im Blick behalten, Kontingente überwachen und Vorlagen nutzen.'),
icon: Layers,
},
];
const previewRaw = t('welcome.preview.items', {
defaultValue: [
'Moderation, Aufgaben und Einladungen als Schnellzugriff',
'Sticky Actions auf Mobile für den Eventtag',
'Paket-Status & Limits jederzeit sichtbar',
],
returnObjects: true,
});
const previewBullets = Array.isArray(previewRaw) ? previewRaw : [String(previewRaw)];
const themeLabel = appearance === 'dark' ? t('welcome.theme.dark', 'Dunkel') : t('welcome.theme.light', 'Hell');
const handleLogin = React.useCallback(() => {
navigateToHref(ADMIN_LOGIN_PATH);
}, []);
const handlePackages = React.useCallback(() => navigateToHref(packagesHref), [packagesHref]);
const handleHow = React.useCallback(() => navigateToHref(howItWorksHref), [howItWorksHref]);
const handleThemeToggle = React.useCallback(() => {
updateAppearance(appearance === 'dark' ? 'light' : 'dark');
}, [appearance, updateAppearance]);
const renderMenuActions = () => (
<div className="flex flex-col gap-3">
<Button size="sm" onClick={handleLogin} className="justify-between">
<span>{t('welcome.cta.login', 'Login')}</span>
<ArrowRight className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={handlePackages} className="justify-between">
<span>{t('welcome.cta.packages', 'Pakete ansehen')}</span>
<ArrowRight className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" onClick={handleHow} className="justify-between">
<span>{t('welcome.cta.how', "So funktioniert's")}</span>
<ArrowRight className="h-4 w-4" />
</Button>
<div className="flex items-center justify-between rounded-xl border border-slate-200/70 px-4 py-3 text-sm dark:border-white/10">
<div>
<p className="text-xs text-slate-500 dark:text-slate-400">{t('welcome.theme.label', 'Darstellung')}</p>
<p className="text-sm font-semibold text-slate-900 dark:text-white">{themeLabel}</p>
</div>
<Button variant="outline" size="icon" aria-label={t('welcome.theme.aria', 'Darstellung umschalten')} onClick={handleThemeToggle}>
{appearance === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</Button>
</div>
<div className="flex items-center justify-between rounded-xl border border-slate-200/70 px-4 py-3 text-sm dark:border-white/10">
<span className="text-slate-700 dark:text-slate-200">{t('app.languageSwitch')}</span>
<LanguageSwitcher />
</div>
</div>
);
const toggleMode = React.useCallback(() => {
setMode((prev) => (prev === 'dark' ? 'light' : 'dark'));
}, []);
const handleLoginRedirect = React.useCallback(() => {
if (isRedirecting) {
return;
}
setIsRedirecting(true);
const params = new URLSearchParams(window.location.search);
const rawReturnTo = params.get('return_to');
const { finalTarget, encodedFinal } = resolveReturnTarget(rawReturnTo, ADMIN_DEFAULT_AFTER_LOGIN_PATH);
const target = new URL(ADMIN_LOGIN_PATH, window.location.origin);
target.searchParams.set('return_to', encodedFinal ?? encodeReturnTo(finalTarget));
const nextHref = `${target.pathname}${target.search}`;
navigateToHref(nextHref);
}, [isRedirecting]);
const handleScrollToFlow = React.useCallback(() => {
flowSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, []);
return (
<div className={cn('relative min-h-svh overflow-hidden transition-colors duration-500', theme.rootBackground)}>
<div className="relative min-h-svh overflow-hidden bg-gradient-to-b from-rose-50 via-white to-slate-50 text-slate-900 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950 dark:text-white">
<div
aria-hidden
className={cn(
'pointer-events-none absolute inset-0 motion-safe:animate-[aurora_24s_ease-in-out_infinite]',
theme.aurora
)}
className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_10%_20%,rgba(255,137,170,0.35),transparent_45%),radial-gradient(circle_at_90%_0%,rgba(96,165,250,0.25),transparent_45%),radial-gradient(circle_at_50%_90%,rgba(16,185,129,0.15),transparent_45%)] opacity-70 dark:opacity-30"
/>
<div aria-hidden className={cn('absolute inset-0 transition-colors duration-500', theme.overlay)} />
<div aria-hidden className="absolute inset-0 bg-gradient-to-b from-white/70 via-white/40 to-transparent dark:from-slate-950/90 dark:via-slate-950/70 dark:to-slate-950/80" />
<div className="relative z-10 flex min-h-svh flex-col">
<header className="mx-auto flex w-full max-w-6xl flex-wrap items-center justify-between gap-4 px-6 pb-4 pt-8">
<div className="space-y-1">
<Badge
variant="outline"
className={cn(
'border text-[11px] font-semibold uppercase tracking-[0.4em] transition-colors duration-300',
theme.headerBadge
)}
>
Event Admin · Fotospiel
</Badge>
<p className={cn('text-sm transition-colors duration-300', theme.headerSubtitle)}>
Euer mobiles Kontrollzentrum für Events, Workshops und Feiern
</p>
<div className="relative z-10 mx-auto flex w-full max-w-6xl flex-col gap-10 px-4 pb-16 pt-8 sm:px-6 lg:px-10 lg:pt-12">
<header className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-rose-500 text-lg font-semibold text-white shadow-lg shadow-rose-200/60 dark:bg-rose-400 dark:text-slate-900">
FS
</div>
<div className="space-y-1">
<p className="text-[11px] font-semibold uppercase tracking-[0.35em] text-rose-600 dark:text-rose-200">
{t('welcome.eyebrow', 'Event Admin')}
</p>
<p className="text-sm font-semibold text-slate-900 dark:text-white">Fotospiel.app</p>
</div>
</div>
<div className="flex flex-wrap items-center gap-3">
<div className="hidden items-center gap-2 sm:flex">
<Button
variant="ghost"
size="sm"
onClick={handleThemeToggle}
aria-label={t('welcome.theme.aria', 'Darstellung umschalten')}
className="rounded-full border border-slate-200/80 bg-white/80 text-slate-700 hover:border-rose-200 hover:text-rose-700 dark:border-white/10 dark:bg-white/10 dark:text-white"
>
{appearance === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
<span className="hidden sm:inline">{themeLabel}</span>
</Button>
<LanguageSwitcher />
<Button
type="button"
variant="outline"
className={cn(
'items-center gap-2 border text-xs font-semibold uppercase tracking-wide',
isLightMode
? 'border-slate-200 text-slate-700 hover:bg-slate-100'
: 'border-white/30 text-white hover:bg-white/10'
)}
onClick={toggleMode}
>
{isLightMode ? (
<>
<Moon className="h-4 w-4" />
Dark Mode
</>
) : (
<>
<SunMedium className="h-4 w-4" />
Light Mode
</>
)}
<Button variant="outline" size="sm" onClick={handlePackages} className="border-rose-200 text-rose-700 hover:bg-rose-50">
{t('welcome.cta.packages', 'Pakete ansehen')}
</Button>
<Button
type="button"
variant="outline"
className={cn(
'border text-sm font-semibold transition-colors duration-300',
isLightMode ? 'border-slate-200 text-slate-700 hover:bg-slate-100' : 'border-white/30 text-white hover:bg-white/10'
)}
onClick={handleLoginRedirect}
disabled={isRedirecting}
>
{isRedirecting ? 'Weiterleitung …' : 'Login öffnen'}
<Button size="sm" onClick={handleLogin} className="bg-rose-500 text-white hover:bg-rose-600">
{t('welcome.cta.login', 'Login')}
</Button>
</div>
<div className="sm:hidden">
<Sheet open={isMenuOpen} onOpenChange={setIsMenuOpen}>
<SheetTrigger asChild>
<Button
variant="outline"
size="icon"
aria-label={isMenuOpen ? t('welcome.menu.close', 'Menü schließen') : t('welcome.menu.open', 'Menü öffnen')}
className="rounded-full border-rose-200 bg-white/80 text-slate-700 shadow-sm dark:border-white/10 dark:bg-white/10 dark:text-white"
>
{isMenuOpen ? <Sparkles className="h-4 w-4" /> : <Menu className="h-4 w-4" />}
</Button>
</SheetTrigger>
<SheetContent side="right" className="bg-white/95 text-slate-900 dark:bg-slate-950 dark:text-white">
<SheetHeader>
<SheetTitle className="text-lg font-semibold">{t('welcome.menu.title', 'Navigation')}</SheetTitle>
</SheetHeader>
{renderMenuActions()}
</SheetContent>
</Sheet>
</div>
</header>
<main className="mx-auto flex w-full max-w-6xl flex-1 flex-col gap-12 px-6 pb-16 pt-4">
<section className="grid gap-10 lg:grid-cols-[1.1fr_0.9fr]">
<div className="space-y-8">
<main className="flex flex-col gap-12">
<section className="grid gap-8 lg:grid-cols-[1.1fr,0.9fr]">
<div className="space-y-6">
<div className="flex flex-wrap items-center gap-3 text-sm text-slate-600 dark:text-slate-300">
<Badge variant="secondary" className="bg-rose-100/80 text-rose-700 hover:bg-rose-100 dark:bg-rose-400/20 dark:text-rose-100">
<Sparkles className="h-3.5 w-3.5" />
{t('welcome.badge', 'Fotos, Aufgaben & Einladungen an einem Ort')}
</Badge>
<div className="flex items-center gap-2 rounded-full bg-white/70 px-3 py-1 text-xs font-medium text-slate-700 shadow-sm shadow-rose-200/50 ring-1 ring-slate-200/70 dark:bg-white/5 dark:text-white dark:ring-white/10">
<ShieldCheck className="h-3.5 w-3.5 text-emerald-500" />
{t('welcome.loginPrompt', 'Bereits Kunde? Login oben rechts.')}
</div>
</div>
<div className="space-y-4">
<p
className={cn(
'text-sm font-semibold uppercase tracking-[0.4em] transition-colors duration-300',
theme.heroEyebrow
)}
>
Willkommen im Event-Kontrollzentrum
</p>
<h1
className={cn(
'font-display text-4xl font-semibold leading-tight transition-colors duration-300 md:text-5xl',
theme.heroTitle
)}
>
Alles für eure Gästegeschichte in einer App
<h1 className="font-display text-3xl font-semibold leading-tight tracking-tight text-slate-900 dark:text-white sm:text-4xl">
{t('welcome.title', 'Event-Branding, Aufgaben & Foto-Moderation in einer App.')}
</h1>
<p
className={cn(
'text-base leading-relaxed transition-colors duration-300 md:text-lg',
theme.heroBody
)}
>
Der neue Startscreen begrüßt euch wie eine mobile App, führt euch in den Welcome Flow und lässt euch
jederzeit zurück ins Dashboard springen, sobald ihr bereit seid.
<p className="text-lg text-slate-700 dark:text-slate-200">
{t('welcome.subtitle', 'Bereite dein Event vor, teile Einladungen, moderiere Uploads live und gib die Galerie danach frei.')}
</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row">
<Button type="button" size="lg" onClick={handleLoginRedirect} disabled={isRedirecting}>
{isRedirecting ? 'Weiterleitung …' : 'Event Admin öffnen'}
<div className="flex flex-wrap items-center gap-3">
<Button size="lg" onClick={handleLogin} className="bg-rose-500 text-white hover:bg-rose-600">
{t('welcome.cta.open', 'Event Admin öffnen')}
<ArrowRight className="h-4 w-4" />
</Button>
<Button
type="button"
size="lg"
variant="ghost"
className={cn('transition-colors duration-300', theme.ghostButton)}
onClick={handleScrollToFlow}
>
Geführten Ablauf ansehen
<Heart className="h-4 w-4" />
<Button variant="outline" size="lg" onClick={handleHow} className="border-rose-200 text-rose-700 hover:bg-rose-50">
{t('welcome.cta.how', "So funktioniert's")}
</Button>
<Button variant="ghost" size="lg" onClick={handlePackages} className="text-slate-700 hover:text-rose-700 dark:text-white">
{t('welcome.cta.packages', 'Pakete ansehen')}
</Button>
</div>
<div className="grid gap-4 sm:grid-cols-3">
{heroStats.map((stat) => (
<FrostedSurface
key={stat.label}
className={cn(
'rounded-3xl border px-5 py-4 shadow-2xl backdrop-blur transition-colors duration-300',
theme.statCard
)}
>
<p className={cn('text-2xl font-semibold', isLightMode ? 'text-slate-900' : 'text-white')}>{stat.value}</p>
<p
className={cn(
'text-xs font-semibold uppercase tracking-wide',
isLightMode ? 'text-slate-500' : 'text-white/70'
)}
>
{stat.label}
</p>
</FrostedSurface>
))}
</div>
</div>
<div className="relative">
<div className="pointer-events-none absolute inset-0 -z-10 translate-y-6 scale-105 rounded-[40px] bg-gradient-to-br from-rose-500/40 via-white/5 to-sky-500/30 blur-3xl" />
<div className="relative rounded-[40px] border border-white/15 bg-white/95 p-6 text-slate-900 shadow-[0_40px_90px_rgba(15,23,42,0.55)]">
<div className="flex items-center justify-between text-xs font-semibold text-slate-500">
<span>Fotospiel</span>
<span>Event Admin</span>
<div className="absolute inset-0 rounded-3xl bg-gradient-to-br from-rose-100/80 via-white/70 to-sky-100/60 blur-3xl dark:from-rose-400/10 dark:via-slate-900 dark:to-sky-400/10" aria-hidden />
<div className="relative flex h-full flex-col gap-4 rounded-3xl border border-slate-200/60 bg-white/80 p-6 shadow-lg shadow-rose-100/40 backdrop-blur dark:border-white/10 dark:bg-white/5 dark:shadow-none">
<div className="flex items-center gap-2 text-sm font-semibold text-slate-800 dark:text-white">
<Wand2 className="h-4 w-4 text-rose-500" />
{t('welcome.preview.title', 'Was dich erwartet')}
</div>
<div className="mt-4 rounded-3xl bg-slate-900/5 p-4">
<div className="flex items-center justify-between text-xs font-semibold text-slate-500">
<span>Heute</span>
<span>Live</span>
</div>
<div className="mt-4 space-y-3">
{['Mission Pack auswählen', 'Event Branding finalisieren', 'Einladungslink teilen'].map((item) => (
<div key={item} className="flex items-center justify-between rounded-2xl bg-white p-3 text-sm shadow">
<div>
<p className="font-semibold text-slate-900">{item}</p>
<p className="text-xs text-slate-500">Welcome Flow</p>
</div>
<Wand2 className="h-5 w-5 text-rose-500" />
</div>
))}
</div>
<div className="space-y-3 text-sm text-slate-700 dark:text-slate-200">
{previewBullets.map((item) => (
<div key={item} className="flex items-start gap-2 rounded-2xl border border-slate-200/70 bg-white/70 px-3 py-2 text-left dark:border-white/10 dark:bg-white/5">
<CheckCircle2 className="mt-0.5 h-4 w-4 text-emerald-500" />
<p>{item}</p>
</div>
))}
</div>
<div className="mt-6 rounded-3xl bg-slate-900 text-white">
<div className="flex items-center justify-between border-b border-white/10 px-5 py-3 text-xs uppercase tracking-[0.3em] text-white/70">
<span>Live Moments</span>
<span>+28</span>
<div className="mt-auto grid grid-cols-2 gap-3 text-xs text-slate-600 dark:text-slate-300">
<div className="rounded-2xl border border-dashed border-rose-200/70 bg-rose-50/70 p-4 dark:border-rose-200/30 dark:bg-rose-200/10">
<p className="text-[11px] uppercase tracking-[0.3em] text-rose-500">{t('welcome.highlight.moderation', 'Live-Moderation')}</p>
<p className="mt-2 font-semibold text-slate-900 dark:text-white">{t('welcome.highlight.moderationHint', 'Approve/Hide, Highlights, Galerie-Link')}</p>
</div>
<div className="space-y-4 px-5 py-6">
{[
{ title: 'Anna lädt 6 Fotos hoch', subtitle: 'Event · Gartenpalast' },
{ title: 'Max reagiert auf Mission „Emotionen“', subtitle: 'Tasks · Emotion Board' },
{ title: 'QR-Link 124x gescannt', subtitle: 'Einladungen · Photobooth' },
].map((entry) => (
<div key={entry.title} className="space-y-1">
<p className="text-sm font-semibold">{entry.title}</p>
<p className="text-xs text-white/70">{entry.subtitle}</p>
</div>
))}
<div className="rounded-2xl border border-dashed border-sky-200/80 bg-sky-50/80 p-4 dark:border-sky-200/40 dark:bg-sky-200/10">
<p className="text-[11px] uppercase tracking-[0.3em] text-sky-600">{t('welcome.highlight.tasks', 'Aufgaben & Emotion-Sets')}</p>
<p className="mt-2 font-semibold text-slate-900 dark:text-white">{t('welcome.highlight.tasksHint', 'Sammlungen importieren oder eigene erstellen')}</p>
</div>
</div>
</div>
</div>
</section>
<section className="grid gap-6 lg:grid-cols-3">
{featureCards.map((feature) => (
<FrostedSurface
key={feature.title}
className={cn(
'flex flex-col gap-4 rounded-3xl border p-6 shadow-lg backdrop-blur transition-colors duration-300',
theme.featureCard
)}
>
<section className="space-y-4">
<div className="flex flex-col gap-2">
<p className="text-xs uppercase tracking-[0.35em] text-rose-500 dark:text-rose-200">{t('welcome.features.title', 'Was du steuern kannst')}</p>
<h2 className="text-2xl font-semibold text-slate-900 dark:text-white sm:text-3xl">{t('welcome.features.subtitle', 'Alles an einem Ort')}</h2>
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{features.map((feature) => (
<div
className={cn('flex items-center gap-3 text-sm', isLightMode ? 'text-rose-600' : 'text-white/80')}
key={feature.key}
className="group flex h-full flex-col gap-3 rounded-2xl border border-slate-200 bg-white/90 p-4 text-left shadow-sm transition hover:-translate-y-0.5 hover:border-rose-200 hover:shadow-md dark:border-white/10 dark:bg-white/5"
>
<feature.icon className={cn('h-5 w-5', isLightMode ? 'text-rose-400' : 'text-rose-200')} />
<span>{feature.badge}</span>
</div>
<h2 className={cn('text-2xl font-semibold', isLightMode ? 'text-slate-900' : 'text-white')}>
{feature.title}
</h2>
<p
className={cn('text-sm leading-relaxed', isLightMode ? 'text-slate-600' : 'text-white/75')}
>
{feature.description}
</p>
</FrostedSurface>
))}
</section>
<section ref={flowSectionRef} className="grid gap-8 lg:grid-cols-[0.95fr_1.05fr]">
<FrostedSurface
className={cn('rounded-3xl border p-6 transition-colors duration-300', theme.timelineCard)}
>
<p
className={cn(
'text-xs font-semibold uppercase tracking-[0.4em]',
isLightMode ? 'text-rose-500' : 'text-rose-200'
)}
>
Guided Journey
</p>
<h3 className={cn('mt-3 text-3xl font-semibold', isLightMode ? 'text-slate-900' : 'text-white')}>
So leitet euch der Welcome Flow
</h3>
<div className="mt-6 space-y-4">
{timelineSteps.map((step, index) => (
<div
key={step.title}
className={cn(
'flex gap-4 rounded-2xl border p-4 transition-colors duration-300',
isLightMode ? 'border-slate-200 bg-white' : 'border-white/15 bg-white/5'
)}
>
<div
className={cn(
'flex h-10 w-10 items-center justify-center rounded-2xl text-lg font-semibold',
isLightMode ? 'bg-rose-100 text-rose-600' : 'bg-rose-500/15 text-white'
)}
>
{index + 1}
</div>
<div>
<p className={cn('font-semibold', isLightMode ? 'text-slate-900' : 'text-white')}>
{step.title}
</p>
<p className={cn('text-sm', theme.timelineDescription)}>{step.description}</p>
</div>
<div className="flex items-center gap-2 text-sm font-semibold text-slate-900 dark:text-white">
<feature.icon className="h-4 w-4 text-rose-500" />
{feature.title}
</div>
))}
</div>
</FrostedSurface>
<div className="space-y-6">
<FrostedSurface className="flex flex-col gap-4 rounded-3xl border-white/15 bg-white/95 p-6 text-slate-900 shadow-2xl">
<div className="flex items-center justify-between text-xs font-semibold uppercase tracking-[0.3em] text-slate-500">
<span>Mobile Greeting</span>
<span>Neu</span>
<p className="text-sm text-slate-600 dark:text-slate-300">{feature.description}</p>
</div>
<p className="text-2xl font-semibold text-slate-900">Fühlt sich an wie eine native App</p>
<p className="text-sm text-slate-600">
Mit großen Touch-Flächen, Animationen und frosted Cards wirkt der Startscreen wie die mobile Version
des Event Admins perfekt für TWA und Capacitor Builds.
</p>
<div className="mt-2 grid gap-3 sm:grid-cols-2">
<div className="rounded-2xl bg-slate-900/5 p-4">
<p className="text-xs text-slate-500">CTA</p>
<p className="text-lg font-semibold text-slate-900">Event Admin öffnen</p>
<p className="text-sm text-slate-600">Leitet direkt zum OAuth Login</p>
</div>
<div className="rounded-2xl bg-slate-900/5 p-4">
<p className="text-xs text-slate-500">Scroll Action</p>
<p className="text-lg font-semibold text-slate-900">Journey ansehen</p>
<p className="text-sm text-slate-600">Smooth Scroll bis zur Timeline</p>
</div>
</div>
</FrostedSurface>
<div className="grid gap-4 sm:grid-cols-3">
{supportHighlights.map((support) => (
<FrostedSurface
key={support.title}
className={cn(
'flex flex-col gap-2 rounded-2xl border p-4 transition-colors duration-300',
theme.supportCard
)}
>
<support.icon className={cn('h-5 w-5', isLightMode ? 'text-rose-500' : 'text-rose-200')} />
<p className={cn('font-semibold', isLightMode ? 'text-slate-900' : 'text-white')}>
{support.title}
</p>
<p className={cn('text-sm', theme.supportDescription)}>{support.description}</p>
</FrostedSurface>
))}
</div>
))}
</div>
</section>
<FrostedSurface
className={cn(
'flex flex-col gap-4 rounded-3xl border px-6 py-5 transition-colors duration-300 md:flex-row md:items-center md:justify-between',
theme.ctaSurface
)}
>
<div>
<p className={cn('text-sm uppercase tracking-[0.4em]', theme.ctaDetail)}>Bereit?</p>
<h3 className={cn('text-2xl font-semibold', isLightMode ? 'text-slate-900' : 'text-white')}>
Wechselt ins Dashboard, sobald euer Flow steht.
</h3>
<p className={cn('text-sm', theme.ctaDetail)}>
Alle Schritte lassen sich später direkt aus dem Event Admin wieder öffnen.
</p>
<section className="space-y-4">
<div className="flex flex-col gap-2">
<p className="text-xs uppercase tracking-[0.35em] text-rose-500 dark:text-rose-200">{t('welcome.steps.title', "So funktioniert's")}</p>
<h2 className="text-2xl font-semibold text-slate-900 dark:text-white sm:text-3xl">{t('welcome.steps.subtitle', 'In drei Schritten bereit')}</h2>
</div>
<Button type="button" size="lg" className="self-start md:self-auto" onClick={handleLoginRedirect} disabled={isRedirecting}>
{isRedirecting ? 'Weiterleitung …' : 'Zum Login'}
<ArrowRight className="h-4 w-4" />
</Button>
</FrostedSurface>
<div className="grid gap-4 md:grid-cols-3">
{steps.map((step) => (
<div
key={step.key}
className="flex h-full flex-col gap-3 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm dark:border-white/10 dark:bg-white/5"
>
<div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.35em] text-rose-500 dark:text-rose-200">
{step.accent}
</div>
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">{step.title}</h3>
<p className="text-sm text-slate-600 dark:text-slate-300">{step.description}</p>
</div>
))}
</div>
</section>
<section className="space-y-4">
<div className="flex flex-col gap-2">
<p className="text-xs uppercase tracking-[0.35em] text-rose-500 dark:text-rose-200">{t('welcome.plans.title', 'Pakete im Überblick')}</p>
<h2 className="text-2xl font-semibold text-slate-900 dark:text-white sm:text-3xl">{t('welcome.plans.subtitle', 'Wähle das passende Kontingent')}</h2>
<p className="text-sm text-slate-600 dark:text-slate-300">{t('welcome.plans.hint', 'Starter, Standard oder Reseller alles mit Moderation & Einladungen.')}</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
{plans.map((plan) => (
<div
key={plan.key}
className={cn(
'flex h-full flex-col gap-3 rounded-2xl border border-slate-200 bg-white/90 p-5 text-left shadow-sm dark:border-white/10 dark:bg-white/5',
plan.highlight ? 'ring-1 ring-rose-200 dark:ring-rose-300/30' : ''
)}
>
<div className="flex items-center gap-2">
<Badge variant="secondary" className="bg-rose-100/80 text-rose-700 dark:bg-rose-300/20 dark:text-rose-100">
{plan.badge ?? t('welcome.plans.badge', 'Paket')}
</Badge>
{plan.highlight ? (
<Badge className="bg-emerald-500/15 text-emerald-700 dark:bg-emerald-400/20 dark:text-emerald-200">
{plan.highlight}
</Badge>
) : null}
</div>
<div className="flex items-center gap-2 text-lg font-semibold text-slate-900 dark:text-white">
<Sparkles className="h-4 w-4 text-rose-500" />
{plan.title}
</div>
<ul className="space-y-2 text-sm text-slate-600 dark:text-slate-300">
{plan.points.map((point) => (
<li key={point} className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 text-emerald-500" />
<span>{point}</span>
</li>
))}
</ul>
<div className="mt-auto">
<Button variant="outline" size="sm" className="border-rose-200 text-rose-700 hover:bg-rose-50" onClick={handlePackages}>
{t('welcome.cta.packages', 'Pakete ansehen')} <ArrowRight className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
</section>
<section className="space-y-4">
<div className="flex flex-col gap-2">
<p className="text-xs uppercase tracking-[0.35em] text-rose-500 dark:text-rose-200">{t('welcome.audience.title', 'Für wen?')}</p>
<h2 className="text-2xl font-semibold text-slate-900 dark:text-white sm:text-3xl">{t('welcome.audience.subtitle', 'Endkunden & Reseller im Blick')}</h2>
</div>
<div className="grid gap-4 md:grid-cols-2">
{audienceCards.map((audience) => (
<div
key={audience.key}
className="flex h-full flex-col gap-3 rounded-2xl border border-slate-200 bg-white/90 p-4 shadow-sm dark:border-white/10 dark:bg-white/5"
>
<div className="flex items-center gap-2 text-lg font-semibold text-slate-900 dark:text-white">
<audience.icon className="h-5 w-5 text-rose-500" />
{audience.title}
</div>
<p className="text-sm text-slate-600 dark:text-slate-300">{audience.description}</p>
<div className="flex items-center gap-2 text-xs uppercase tracking-[0.3em] text-slate-500 dark:text-slate-400">
<ArrowRight className="h-3.5 w-3.5" />
{t('welcome.audience.cta', 'Wenige Klicks bis zum Start')}
</div>
</div>
))}
</div>
</section>
<section className="rounded-3xl border border-slate-200 bg-white/85 p-6 shadow-lg shadow-rose-100/40 backdrop-blur dark:border-white/10 dark:bg-white/5">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="space-y-1">
<p className="text-xs uppercase tracking-[0.35em] text-rose-500 dark:text-rose-200">{t('welcome.footer.eyebrow', 'Bereit?')}</p>
<h3 className="text-xl font-semibold text-slate-900 dark:text-white">{t('welcome.footer.title', 'Melde dich an oder prüfe die Pakete')}</h3>
<p className="text-sm text-slate-600 dark:text-slate-300">{t('welcome.footer.subtitle', 'Login für bestehende Kunden, Pakete für neue Teams.')}</p>
</div>
<div className="flex flex-wrap items-center gap-3">
<Button size="lg" onClick={handleLogin} className="bg-rose-500 text-white hover:bg-rose-600">
{t('welcome.cta.login', 'Login')} <ArrowRight className="h-4 w-4" />
</Button>
<Button variant="outline" size="lg" onClick={handlePackages} className="border-rose-200 text-rose-700 hover:bg-rose-50">
{t('welcome.cta.packages', 'Pakete ansehen')}
</Button>
</div>
</div>
</section>
</main>
<footer className={cn('py-6 text-center text-xs transition-colors duration-300', theme.footer)}>
Fotospiel · Eure Gäste gestalten eure Lieblingsmomente
</footer>
</div>
</div>
);