Add marketing motion reveals to blog and occasions
This commit is contained in:
@@ -10,6 +10,7 @@ import { Head, Link } from '@inertiajs/react';
|
||||
import { CheckCircle2, Sparkles } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
|
||||
type DemoFeature = { title: string; description: string };
|
||||
|
||||
@@ -21,6 +22,7 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
const { t } = useTranslation('marketing');
|
||||
const { localizedPath } = useLocalizedRoutes();
|
||||
const locale = useLocale();
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const embedUrl = demoToken ? `/e/${demoToken}` : '/e/demo?demo=1';
|
||||
const [isDemoOpen, setIsDemoOpen] = React.useState(false);
|
||||
const demoOpenLabel = t('labels.demoOpenOverlay', locale === 'en' ? 'Open demo overlay' : 'Demo im Overlay öffnen');
|
||||
@@ -38,6 +40,25 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
const handleOpenDemo = (): void => {
|
||||
setIsDemoOpen(true);
|
||||
};
|
||||
const frameVariants = shouldReduceMotion
|
||||
? { rest: {}, hover: {} }
|
||||
: {
|
||||
rest: { y: 0, scale: 1, rotateX: 0, rotateY: 0 },
|
||||
hover: {
|
||||
y: -6,
|
||||
scale: 1.01,
|
||||
rotateX: -2,
|
||||
rotateY: 3,
|
||||
transition: { type: 'spring', stiffness: 180, damping: 20 },
|
||||
},
|
||||
};
|
||||
const shineVariants = shouldReduceMotion
|
||||
? { rest: {}, hover: {} }
|
||||
: {
|
||||
rest: { x: '-120%', opacity: 0 },
|
||||
hover: { x: '120%', opacity: 0.8, transition: { duration: 0.8, ease: 'easeOut' } },
|
||||
};
|
||||
const featureHover = shouldReduceMotion ? {} : { y: -4, scale: 1.01 };
|
||||
|
||||
return (
|
||||
<MarketingLayout title={demo.title}>
|
||||
@@ -74,28 +95,46 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
<div className="flex-1">
|
||||
{embedUrl ? (
|
||||
<>
|
||||
<div className="relative mx-auto w-[min(92vw,calc(75dvh*10/14))] aspect-[10/14] rounded-[2.5rem] border border-gray-200 bg-gray-900 px-3 pb-3 pt-4 shadow-2xl dark:border-gray-700">
|
||||
<div
|
||||
className="absolute top-2 left-1/2 h-1.5 w-16 -translate-x-1/2 rounded-full bg-gray-300 dark:bg-gray-600"
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="h-full w-full overflow-hidden rounded-[1.75rem] bg-white shadow-inner dark:bg-gray-950">
|
||||
{isDemoOpen ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-2 bg-gradient-to-br from-pink-200 via-white to-white px-4 text-center dark:from-pink-900/40 dark:via-gray-950 dark:to-gray-950">
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-pink-600/70 dark:text-pink-300/70">Live Demo</p>
|
||||
<p className="text-sm font-semibold text-gray-900 dark:text-gray-100">Demo läuft im Overlay</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300">Schließe das Overlay, um hier weiterzumachen.</p>
|
||||
</div>
|
||||
) : (
|
||||
<iframe
|
||||
title="Demo der Fotospiel App"
|
||||
src={embedUrl}
|
||||
className="h-full w-full border-0 bg-white dark:bg-gray-950"
|
||||
loading="lazy"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="mx-auto w-[min(92vw,calc(75dvh*10/14))] aspect-[10/14]" style={{ perspective: 1200 }}>
|
||||
<motion.div
|
||||
className="relative h-full w-full rounded-[2.5rem] border border-gray-200 bg-gray-900 px-3 pb-3 pt-4 shadow-2xl dark:border-gray-700"
|
||||
initial="rest"
|
||||
animate="rest"
|
||||
whileHover="hover"
|
||||
variants={frameVariants}
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
<motion.span
|
||||
className="pointer-events-none absolute inset-0 rounded-[2.5rem]"
|
||||
variants={shineVariants}
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(120deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.6) 50%, rgba(255,255,255,0) 100%)',
|
||||
mixBlendMode: 'screen',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="absolute top-2 left-1/2 h-1.5 w-16 -translate-x-1/2 rounded-full bg-gray-300 dark:bg-gray-600"
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="h-full w-full overflow-hidden rounded-[1.75rem] bg-white shadow-inner dark:bg-gray-950">
|
||||
{isDemoOpen ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-2 bg-gradient-to-br from-pink-200 via-white to-white px-4 text-center dark:from-pink-900/40 dark:via-gray-950 dark:to-gray-950">
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-pink-600/70 dark:text-pink-300/70">Live Demo</p>
|
||||
<p className="text-sm font-semibold text-gray-900 dark:text-gray-100">Demo läuft im Overlay</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300">Schließe das Overlay, um hier weiterzumachen.</p>
|
||||
</div>
|
||||
) : (
|
||||
<iframe
|
||||
title="Demo der Fotospiel App"
|
||||
src={embedUrl}
|
||||
className="h-full w-full border-0 bg-white dark:bg-gray-950"
|
||||
loading="lazy"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col items-center gap-1 text-center">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">{demo.iframeNote}</p>
|
||||
@@ -143,17 +182,19 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
{demoFeatures.map((feature) => (
|
||||
<Card key={feature.title} className="border-gray-100 shadow-sm dark:border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Sparkles className="h-5 w-5 text-pink-500" aria-hidden />
|
||||
{feature.title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-sm text-gray-600 dark:text-gray-300">{feature.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<motion.div key={feature.title} whileHover={featureHover} transition={{ duration: 0.2, ease: 'easeOut' }}>
|
||||
<Card className="border-gray-100 shadow-sm dark:border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Sparkles className="h-5 w-5 text-pink-500" aria-hidden />
|
||||
{feature.title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-sm text-gray-600 dark:text-gray-300">{feature.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user