Add marketing motion reveals to blog and occasions

This commit is contained in:
Codex Agent
2026-01-21 15:22:39 +01:00
parent 5eb0941512
commit a01a7ec399
16 changed files with 1869 additions and 781 deletions

View File

@@ -11,6 +11,7 @@ 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';
import { motion, useReducedMotion } from 'framer-motion';
type HeroStat = { value: string; label: string };
type ExperienceStep = { title: string; description: string };
@@ -42,6 +43,21 @@ const HowItWorks: React.FC = () => {
const { t, ready } = useTranslation('marketing');
const { localizedPath } = useLocalizedRoutes();
const locale = useLocale();
const shouldReduceMotion = useReducedMotion();
const viewportOnce = { once: true, amount: 0.25 };
const revealUp = {
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 18 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
},
};
const stagger = {
hidden: {},
visible: { transition: { staggerChildren: 0.12 } },
};
if (!ready) {
return (
@@ -139,17 +155,19 @@ const HowItWorks: React.FC = () => {
<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">
<motion.div className="flex-1 space-y-6" variants={stagger} initial="hidden" animate="visible">
<motion.div variants={revealUp}>
<Badge variant="outline" className="border-pink-400 text-pink-600 dark:border-pink-500 dark:text-pink-300">
Fotospiel Flow
</Badge>
</motion.div>
<motion.h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-50 md:text-5xl" variants={revealUp}>
{hero.title}
</h1>
<p className="max-w-2xl text-lg text-gray-600 dark:text-gray-300">
</motion.h1>
<motion.p className="max-w-2xl text-lg text-gray-600 dark:text-gray-300" variants={revealUp}>
{hero.subtitle}
</p>
<div className="flex flex-wrap items-center gap-3">
</motion.p>
<motion.div className="flex flex-wrap items-center gap-3" variants={revealUp}>
<Button asChild size="lg" className="bg-pink-500 hover:bg-pink-600">
<Link href={localizedPath('/packages')}>
{hero.primaryCta}
@@ -170,23 +188,31 @@ const HowItWorks: React.FC = () => {
{t('packages.gift_cta', 'Paket verschenken')}
</Link>
</Button>
</div>
</div>
</motion.div>
</motion.div>
<div className="flex-1">
<div className="grid gap-4 sm:grid-cols-3">
{heroStats.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>
<motion.div
key={stat.label}
variants={revealUp}
initial="hidden"
whileInView="visible"
viewport={viewportOnce}
>
<Card 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>
</motion.div>
))}
</div>
</div>
@@ -205,10 +231,14 @@ const HowItWorks: React.FC = () => {
</TabsTrigger>
</TabsList>
<TabsContent value="host" className="mt-6">
<ExperiencePanel data={experience.host} />
<motion.div variants={revealUp} initial="hidden" whileInView="visible" viewport={viewportOnce}>
<ExperiencePanel data={experience.host} />
</motion.div>
</TabsContent>
<TabsContent value="guest" className="mt-6">
<ExperiencePanel data={experience.guest} />
<motion.div variants={revealUp} initial="hidden" whileInView="visible" viewport={viewportOnce}>
<ExperiencePanel data={experience.guest} />
</motion.div>
</TabsContent>
</Tabs>
</div>
@@ -226,19 +256,27 @@ const HowItWorks: React.FC = () => {
</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>
<motion.div
key={pillar.title}
variants={revealUp}
initial="hidden"
whileInView="visible"
viewport={viewportOnce}
>
<Card 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>
</motion.div>
))}
</div>
</section>
@@ -312,48 +350,50 @@ const HowItWorks: React.FC = () => {
</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('how_it_works_page.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('how_it_works_page.labels.challenge_ideas', '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>
))}
<motion.div variants={revealUp} initial="hidden" whileInView="visible" viewport={viewportOnce}>
<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>
</CardContent>
</Card>
<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('how_it_works_page.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('how_it_works_page.labels.challenge_ideas', '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>
</motion.div>
</TabsContent>
))}
</Tabs>
@@ -362,29 +402,31 @@ const HowItWorks: React.FC = () => {
<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('how_it_works_page.labels.prep_hint', '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('/packages')}>
{checklist.cta}
</Link>
</Button>
</CardContent>
</Card>
<motion.div variants={revealUp} initial="hidden" whileInView="visible" viewport={viewportOnce}>
<Card className="border-gray-100 shadow-sm dark:border-gray-800">
<CardHeader>
<CardTitle>{checklist.title}</CardTitle>
<CardDescription>
{t('how_it_works_page.labels.prep_hint', '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('/packages')}>
{checklist.cta}
</Link>
</Button>
</CardContent>
</Card>
</motion.div>
</div>
</section>