Add marketing motion reveals to blog and occasions
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user