Add marketing motion reveals to blog and occasions
This commit is contained in:
@@ -12,6 +12,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { ArrowRight, Camera, QrCode, ShieldCheck, Sparkles, Smartphone, Loader2, CheckCircle2 } from 'lucide-react';
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
|
||||
interface Package {
|
||||
id: number;
|
||||
@@ -36,6 +37,7 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
trackClick: trackHeroCtaClick,
|
||||
} = useCtaExperiment('home_hero_cta');
|
||||
const { flash } = usePage<{ flash?: { success?: string } }>().props;
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const { data, setData, post, processing, errors, reset } = useForm({
|
||||
name: '',
|
||||
email: '',
|
||||
@@ -43,6 +45,52 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
nickname: '',
|
||||
});
|
||||
|
||||
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 revealFadeIn = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: { opacity: 1, transition: { duration: 0.5, ease: 'easeOut' } },
|
||||
};
|
||||
const revealFade = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: { opacity: 1, transition: { duration: 0.5, ease: 'easeOut' } },
|
||||
};
|
||||
const stagger = {
|
||||
hidden: {},
|
||||
visible: { transition: { staggerChildren: 0.12 } },
|
||||
};
|
||||
const heroCardVariants = shouldReduceMotion
|
||||
? { rest: {}, hover: {} }
|
||||
: {
|
||||
rest: { y: 0, scale: 1, rotateX: 0, rotateY: 0 },
|
||||
hover: {
|
||||
y: -8,
|
||||
scale: 1.01,
|
||||
rotateX: -2,
|
||||
rotateY: 3,
|
||||
transition: { type: 'spring', stiffness: 180, damping: 20 },
|
||||
},
|
||||
};
|
||||
const heroGlowVariants = shouldReduceMotion
|
||||
? { rest: {}, hover: {} }
|
||||
: {
|
||||
rest: { opacity: 0.4, scale: 1 },
|
||||
hover: { opacity: 0.7, scale: 1.03, transition: { duration: 0.35, ease: 'easeOut' } },
|
||||
};
|
||||
const heroShineVariants = shouldReduceMotion
|
||||
? { rest: {}, hover: {} }
|
||||
: {
|
||||
rest: { x: '-120%', opacity: 0 },
|
||||
hover: { x: '120%', opacity: 0.8, transition: { duration: 0.8, ease: 'easeOut' } },
|
||||
};
|
||||
|
||||
const heroBulletsRaw = t('home.hero_bullets', { returnObjects: true });
|
||||
const heroBullets = Array.isArray(heroBulletsRaw) ? (heroBulletsRaw as string[]) : [];
|
||||
|
||||
@@ -109,34 +157,53 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
|
||||
<section id="hero" className="bg-aurora-enhanced py-20 px-4 text-gray-900 dark:text-gray-100">
|
||||
<div className="container mx-auto flex max-w-6xl flex-col items-center gap-12 md:flex-row">
|
||||
<div className="flex flex-col gap-8 text-center md:w-1/2 md:text-left">
|
||||
<motion.div
|
||||
className="flex flex-col gap-8 text-center md:w-1/2 md:text-left"
|
||||
variants={stagger}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Badge className="mx-auto w-fit bg-white/80 px-3 py-1 text-xs font-semibold uppercase text-rose-500 shadow-sm md:mx-0 md:text-[0.72rem]">
|
||||
{t('home.hero_tagline')}
|
||||
</Badge>
|
||||
<h1 className="font-display text-4xl font-bold leading-tight md:text-5xl lg:text-6xl">
|
||||
{t('home.hero_title')}
|
||||
</h1>
|
||||
<p className="text-lg text-gray-700 dark:text-gray-200 md:text-xl">
|
||||
{t('home.hero_description')}
|
||||
</p>
|
||||
<motion.div variants={revealFadeIn}>
|
||||
<Badge className="mx-auto w-fit bg-white/80 px-3 py-1 text-xs font-semibold uppercase text-rose-500 shadow-sm md:mx-0 md:text-[0.72rem]">
|
||||
{t('home.hero_tagline')}
|
||||
</Badge>
|
||||
</motion.div>
|
||||
<motion.div>
|
||||
<motion.h1 className="font-display text-4xl font-bold leading-tight md:text-5xl lg:text-6xl" variants={revealFadeIn}>
|
||||
{t('home.hero_title')}
|
||||
</motion.h1>
|
||||
</motion.div>
|
||||
<motion.div>
|
||||
<motion.p className="text-lg text-gray-700 dark:text-gray-200 md:text-xl" variants={revealFadeIn}>
|
||||
{t('home.hero_description')}
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
{heroBullets.length > 0 && (
|
||||
<ul className="mx-auto flex flex-col gap-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-200 md:mx-0">
|
||||
{heroBullets.map((item, index) => {
|
||||
const Icon = heroBulletIcons[index % heroBulletIcons.length] ?? Sparkles;
|
||||
return (
|
||||
<li key={`hero-bullet-${index}`} className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-white/80 text-rose-500 shadow-sm dark:bg-gray-900/70">
|
||||
<Icon className="h-4 w-4" aria-hidden />
|
||||
</span>
|
||||
<span className="flex-1 text-base">{item}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<motion.div>
|
||||
<motion.ul
|
||||
className="mx-auto flex flex-col gap-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-200 md:mx-0"
|
||||
variants={stagger}
|
||||
>
|
||||
{heroBullets.map((item, index) => {
|
||||
const Icon = heroBulletIcons[index % heroBulletIcons.length] ?? Sparkles;
|
||||
return (
|
||||
<motion.li key={`hero-bullet-${index}`} className="flex items-start gap-3" variants={revealFadeIn}>
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-white/80 text-rose-500 shadow-sm dark:bg-gray-900/70">
|
||||
<Icon className="h-4 w-4" aria-hidden />
|
||||
</span>
|
||||
<span className="flex-1 text-base">{item}</span>
|
||||
</motion.li>
|
||||
);
|
||||
})}
|
||||
</motion.ul>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-center gap-3 md:justify-start">
|
||||
<motion.div
|
||||
className="flex flex-wrap items-center justify-center gap-3 md:justify-start"
|
||||
variants={revealFadeIn}
|
||||
>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
@@ -193,16 +260,45 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
>
|
||||
{heroTertiaryLabel}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full max-w-xl md:w-1/2">
|
||||
<div className="absolute inset-0 rounded-3xl bg-white/40 blur-xl" aria-hidden />
|
||||
<img
|
||||
src="/joyous_wedding_guests_posing.jpg"
|
||||
alt={t('home.hero_image_alt')}
|
||||
className="relative w-full rounded-[32px] border border-white/60 shadow-2xl"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="relative w-full max-w-xl md:w-1/2"
|
||||
variants={revealFade}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
style={{ perspective: 1200 }}
|
||||
>
|
||||
<motion.div initial="rest" animate="rest" whileHover="hover">
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-3xl bg-white/40 blur-xl"
|
||||
aria-hidden
|
||||
variants={heroGlowVariants}
|
||||
/>
|
||||
<motion.div
|
||||
className="relative overflow-hidden rounded-[32px] border border-white/60 shadow-2xl"
|
||||
variants={heroCardVariants}
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
<motion.span
|
||||
className="pointer-events-none absolute inset-0"
|
||||
variants={heroShineVariants}
|
||||
style={{
|
||||
background: 'linear-gradient(120deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.65) 50%, rgba(255,255,255,0) 100%)',
|
||||
mixBlendMode: 'screen',
|
||||
}}
|
||||
/>
|
||||
<motion.img
|
||||
src="/joyous_wedding_guests_posing.jpg"
|
||||
alt={t('home.hero_image_alt')}
|
||||
className="h-full w-full scale-[1.04] object-cover"
|
||||
initial={shouldReduceMotion ? false : { opacity: 0, scale: 1.02 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -216,20 +312,25 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
</div>
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-3">
|
||||
{howSteps.map(({ icon: Icon, title, description }, index) => (
|
||||
<Card
|
||||
<motion.div
|
||||
key={`how-step-${index}`}
|
||||
className="border-gray-200/70 bg-white/90 shadow-md shadow-gray-200/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/30 dark:border-gray-800/60 dark:bg-gray-900/60"
|
||||
variants={revealUp}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewportOnce}
|
||||
>
|
||||
<CardHeader className="flex flex-col gap-4">
|
||||
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-rose-100/80 text-rose-500 shadow-inner shadow-rose-200/60 dark:bg-rose-500/20 dark:text-rose-100">
|
||||
<Icon className="h-6 w-6" aria-hidden />
|
||||
</span>
|
||||
<CardTitle className="text-xl">{title}</CardTitle>
|
||||
<CardDescription className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
|
||||
{description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="border-gray-200/70 bg-white/90 shadow-md shadow-gray-200/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/30 dark:border-gray-800/60 dark:bg-gray-900/60">
|
||||
<CardHeader className="flex flex-col gap-4">
|
||||
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-rose-100/80 text-rose-500 shadow-inner shadow-rose-200/60 dark:bg-rose-500/20 dark:text-rose-100">
|
||||
<Icon className="h-6 w-6" aria-hidden />
|
||||
</span>
|
||||
<CardTitle className="text-xl">{title}</CardTitle>
|
||||
<CardDescription className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
|
||||
{description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,36 +339,52 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
<section className="bg-slate-950 py-20 px-4 text-white">
|
||||
<div className="container mx-auto max-w-6xl">
|
||||
<div className="grid gap-10 md:grid-cols-[1.1fr_0.9fr] md:items-center">
|
||||
<div className="flex flex-col gap-6">
|
||||
<motion.div
|
||||
className="flex flex-col gap-6"
|
||||
variants={stagger}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewportOnce}
|
||||
>
|
||||
<Badge className="w-fit bg-white/15 px-3 py-1 text-xs uppercase tracking-[0.35em] text-white/80">
|
||||
{t('home.demo_title')}
|
||||
</Badge>
|
||||
<h2 className="font-display text-3xl font-semibold leading-tight md:text-4xl">
|
||||
<motion.h2 className="font-display text-3xl font-semibold leading-tight md:text-4xl" variants={revealUp}>
|
||||
{t('home.demo_description')}
|
||||
</h2>
|
||||
<p className="text-sm text-white/75">{t('home.demo_hint')}</p>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="h-12 w-fit rounded-full bg-white px-7 text-base font-semibold text-slate-900 shadow-lg shadow-white/30 transition hover:bg-white/90"
|
||||
>
|
||||
<Link
|
||||
href={localizedPath('/demo')}
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: 'marketing_home',
|
||||
action: 'demo_section_cta',
|
||||
})
|
||||
}
|
||||
className="flex items-center gap-2"
|
||||
</motion.h2>
|
||||
<motion.p className="text-sm text-white/75" variants={revealUp}>
|
||||
{t('home.demo_hint')}
|
||||
</motion.p>
|
||||
<motion.div variants={revealUp}>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="h-12 w-fit rounded-full bg-white px-7 text-base font-semibold text-slate-900 shadow-lg shadow-white/30 transition hover:bg-white/90"
|
||||
>
|
||||
<span>{t('home.demo_cta')}</span>
|
||||
<ArrowRight className="h-4 w-4" aria-hidden />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative mx-auto w-full max-w-sm">
|
||||
<div className="absolute inset-0 rounded-[42px] bg-gradient-to-br from-rose-400 via-purple-500 to-indigo-500 opacity-80 blur-2xl" aria-hidden />
|
||||
<Link
|
||||
href={localizedPath('/demo')}
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: 'marketing_home',
|
||||
action: 'demo_section_cta',
|
||||
})
|
||||
}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<span>{t('home.demo_cta')}</span>
|
||||
<ArrowRight className="h-4 w-4" aria-hidden />
|
||||
</Link>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="relative mx-auto w-full max-w-sm"
|
||||
variants={revealUp}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewportOnce}
|
||||
>
|
||||
<motion.div className="absolute inset-0 rounded-[42px] bg-gradient-to-br from-rose-400 via-purple-500 to-indigo-500 opacity-80 blur-2xl" aria-hidden />
|
||||
<div className="relative aspect-[9/16] w-full overflow-hidden rounded-[42px] border border-white/20 bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 shadow-[0_40px_90px_-30px_rgba(15,23,42,0.75)]">
|
||||
<div className="flex h-full flex-col items-center justify-center gap-4 p-6 text-center">
|
||||
<Smartphone className="h-12 w-12 text-white/60" aria-hidden />
|
||||
@@ -286,7 +403,7 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -298,17 +415,22 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
</div>
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-3">
|
||||
{features.map((feature, index) => (
|
||||
<Card
|
||||
<motion.div
|
||||
key={`feature-${index}`}
|
||||
className="border-gray-200/80 bg-gray-50/80 shadow-sm shadow-rose-100/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/40 dark:border-gray-800/60 dark:bg-gray-900/70"
|
||||
variants={revealUp}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewportOnce}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">{feature.title}</CardTitle>
|
||||
<CardDescription className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
|
||||
{feature.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="border-gray-200/80 bg-gray-50/80 shadow-sm shadow-rose-100/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/40 dark:border-gray-800/60 dark:bg-gray-900/70">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">{feature.title}</CardTitle>
|
||||
<CardDescription className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
|
||||
{feature.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -317,63 +439,67 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
<section className="bg-gray-50 py-20 px-4 dark:bg-gray-950/80">
|
||||
<div className="container mx-auto max-w-6xl">
|
||||
<div className="grid gap-10 lg:grid-cols-[1.15fr_0.85fr]">
|
||||
<Card className="border-rose-200/50 bg-white/95 shadow-md shadow-rose-200/40 dark:border-rose-500/30 dark:bg-gray-900/80">
|
||||
<CardHeader className="flex flex-col gap-4">
|
||||
<Badge className="w-fit bg-rose-100 px-3 py-1 text-xs font-semibold uppercase text-rose-600 dark:bg-rose-500/20 dark:text-rose-200">
|
||||
{t('home.occasions_title')}
|
||||
</Badge>
|
||||
<CardTitle className="text-2xl">{t('home.occasions_description')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3 sm:grid-cols-2">
|
||||
{occasionLinks.map(({ key, href }) => (
|
||||
<Link
|
||||
key={key}
|
||||
href={href}
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: 'marketing_home',
|
||||
action: 'occasion_tile_click',
|
||||
name: key,
|
||||
})
|
||||
}
|
||||
className="group flex items-center gap-3 rounded-xl border border-rose-100/60 bg-white/80 px-4 py-3 text-sm font-semibold text-rose-600 shadow-sm shadow-rose-100/50 transition hover:bg-rose-50 hover:text-rose-700 dark:border-rose-500/30 dark:bg-gray-900/70 dark:text-rose-200 dark:hover:bg-rose-500/10"
|
||||
>
|
||||
<ArrowRight className="h-4 w-4 -translate-x-1 transition group-hover:translate-x-0" aria-hidden />
|
||||
<span>{t(`home.occasions.${key}`)}</span>
|
||||
</Link>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<motion.div variants={revealUp} initial="hidden" whileInView="visible" viewport={viewportOnce}>
|
||||
<Card className="border-rose-200/50 bg-white/95 shadow-md shadow-rose-200/40 dark:border-rose-500/30 dark:bg-gray-900/80">
|
||||
<CardHeader className="flex flex-col gap-4">
|
||||
<Badge className="w-fit bg-rose-100 px-3 py-1 text-xs font-semibold uppercase text-rose-600 dark:bg-rose-500/20 dark:text-rose-200">
|
||||
{t('home.occasions_title')}
|
||||
</Badge>
|
||||
<CardTitle className="text-2xl">{t('home.occasions_description')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3 sm:grid-cols-2">
|
||||
{occasionLinks.map(({ key, href }) => (
|
||||
<Link
|
||||
key={key}
|
||||
href={href}
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: 'marketing_home',
|
||||
action: 'occasion_tile_click',
|
||||
name: key,
|
||||
})
|
||||
}
|
||||
className="group flex items-center gap-3 rounded-xl border border-rose-100/60 bg-white/80 px-4 py-3 text-sm font-semibold text-rose-600 shadow-sm shadow-rose-100/50 transition hover:bg-rose-50 hover:text-rose-700 dark:border-rose-500/30 dark:bg-gray-900/70 dark:text-rose-200 dark:hover:bg-rose-500/10"
|
||||
>
|
||||
<ArrowRight className="h-4 w-4 -translate-x-1 transition group-hover:translate-x-0" aria-hidden />
|
||||
<span>{t(`home.occasions.${key}`)}</span>
|
||||
</Link>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<Card className="border-gray-200/70 bg-white/95 shadow-md shadow-gray-200/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/40 dark:border-gray-800/60 dark:bg-gray-900/80">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">{t('home.blog_teaser_title')}</CardTitle>
|
||||
<CardDescription className="text-sm leading-relaxed text-muted-foreground">
|
||||
{t('home.blog_teaser_description')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="px-6 pb-6">
|
||||
<Button
|
||||
asChild
|
||||
variant="ghost"
|
||||
className="group inline-flex items-center gap-2 rounded-full px-0 text-base font-semibold text-rose-500 transition hover:text-rose-600 dark:text-rose-200 dark:hover:text-rose-100"
|
||||
>
|
||||
<Link
|
||||
href={localizedPath('/blog')}
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: 'marketing_home',
|
||||
action: 'blog_teaser_cta',
|
||||
})
|
||||
}
|
||||
className="flex items-center gap-2"
|
||||
<motion.div variants={revealUp} initial="hidden" whileInView="visible" viewport={viewportOnce}>
|
||||
<Card className="border-gray-200/70 bg-white/95 shadow-md shadow-gray-200/40 transition hover:-translate-y-1 hover:shadow-lg hover:shadow-rose-200/40 dark:border-gray-800/60 dark:bg-gray-900/80">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">{t('home.blog_teaser_title')}</CardTitle>
|
||||
<CardDescription className="text-sm leading-relaxed text-muted-foreground">
|
||||
{t('home.blog_teaser_description')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="px-6 pb-6">
|
||||
<Button
|
||||
asChild
|
||||
variant="ghost"
|
||||
className="group inline-flex items-center gap-2 rounded-full px-0 text-base font-semibold text-rose-500 transition hover:text-rose-600 dark:text-rose-200 dark:hover:text-rose-100"
|
||||
>
|
||||
<span>{t('home.blog_teaser_cta')}</span>
|
||||
<ArrowRight className="h-4 w-4 transition group-hover:translate-x-1" aria-hidden />
|
||||
</Link>
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Link
|
||||
href={localizedPath('/blog')}
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: 'marketing_home',
|
||||
action: 'blog_teaser_cta',
|
||||
})
|
||||
}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<span>{t('home.blog_teaser_cta')}</span>
|
||||
<ArrowRight className="h-4 w-4 transition group-hover:translate-x-1" aria-hidden />
|
||||
</Link>
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -408,40 +534,45 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
</div>
|
||||
<div className="mt-10 grid gap-8 md:grid-cols-2">
|
||||
{packages.slice(0, 2).map((pkg) => (
|
||||
<Card
|
||||
<motion.div
|
||||
key={pkg.id}
|
||||
className="border-gray-200 bg-white/95 text-center shadow-md shadow-rose-200/30 transition hover:-translate-y-1 hover:shadow-xl hover:shadow-rose-200/40 dark:border-gray-800 dark:bg-gray-900/80"
|
||||
variants={revealUp}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewportOnce}
|
||||
>
|
||||
<CardHeader className="gap-4">
|
||||
<CardTitle className="text-2xl">{pkg.name}</CardTitle>
|
||||
<CardDescription className="text-sm text-muted-foreground">
|
||||
{pkg.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col items-center gap-4">
|
||||
<p className="text-3xl font-bold text-rose-500">
|
||||
{pkg.price} {t('currency.euro')}
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
className="rounded-full bg-rose-500 px-5 py-2 text-sm font-semibold text-white shadow-lg shadow-rose-200/40 transition hover:bg-rose-600"
|
||||
>
|
||||
<Link
|
||||
href={`${localizedPath('/packages')}?package_id=${pkg.id}`}
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: 'marketing_home',
|
||||
action: 'package_teaser_cta',
|
||||
name: pkg.name,
|
||||
value: pkg.price,
|
||||
})
|
||||
}
|
||||
<Card className="border-gray-200 bg-white/95 text-center shadow-md shadow-rose-200/30 transition hover:-translate-y-1 hover:shadow-xl hover:shadow-rose-200/40 dark:border-gray-800 dark:bg-gray-900/80">
|
||||
<CardHeader className="gap-4">
|
||||
<CardTitle className="text-2xl">{pkg.name}</CardTitle>
|
||||
<CardDescription className="text-sm text-muted-foreground">
|
||||
{pkg.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col items-center gap-4">
|
||||
<p className="text-3xl font-bold text-rose-500">
|
||||
{pkg.price} {t('currency.euro')}
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
className="rounded-full bg-rose-500 px-5 py-2 text-sm font-semibold text-white shadow-lg shadow-rose-200/40 transition hover:bg-rose-600"
|
||||
>
|
||||
{t('home.view_details')}
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Link
|
||||
href={`${localizedPath('/packages')}?package_id=${pkg.id}`}
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: 'marketing_home',
|
||||
action: 'package_teaser_cta',
|
||||
name: pkg.name,
|
||||
value: pkg.price,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('home.view_details')}
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user