überarbeitung des event-admins fortgesetzt
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user