Update marketing packages testimonials and demo

This commit is contained in:
Codex Agent
2026-01-21 12:48:34 +01:00
parent b9708d5174
commit 9b245e9c51
8 changed files with 533 additions and 160 deletions

View File

@@ -23,6 +23,7 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
const locale = useLocale();
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');
const demo = t('demo_page', { returnObjects: true }) as {
title: string;
@@ -35,17 +36,6 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
};
const demoFeatures = Array.isArray(demo.features) ? demo.features : [];
const handleOpenDemo = (): void => {
if (typeof window === 'undefined') {
return;
}
const isMobile = window.matchMedia('(max-width: 768px)').matches;
if (isMobile) {
window.open(embedUrl, '_blank', 'noopener,noreferrer');
return;
}
setIsDemoOpen(true);
};
@@ -84,18 +74,28 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
<div className="flex-1">
{embedUrl ? (
<>
<div className="relative mx-auto w-full max-w-[300px] rounded-[2.5rem] border border-gray-200 bg-gray-900 px-3 pb-3 pt-4 shadow-2xl sm:max-w-[340px] md:max-w-[380px] dark:border-gray-700">
<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
/>
<iframe
title="Demo der Fotospiel App"
src={embedUrl}
className="aspect-[9/19] w-full rounded-[1.75rem] border-0 bg-white shadow-inner dark:bg-gray-950"
loading="lazy"
sandbox="allow-scripts allow-same-origin allow-forms"
/>
<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>
<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>
@@ -105,7 +105,7 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
className="text-pink-600 hover:text-pink-700 dark:text-pink-300"
onClick={handleOpenDemo}
>
{demo.openFull}
{demoOpenLabel}
</Button>
</div>
</>
@@ -174,8 +174,8 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
</div>
</section>
<Dialog open={isDemoOpen} onOpenChange={setIsDemoOpen}>
<DialogContent className="w-[388px] max-w-[90vw] border-0 bg-transparent p-0 shadow-none">
<div className="relative w-full rounded-[2.5rem] border border-gray-200 bg-gray-900 p-3 shadow-2xl dark:border-gray-700">
<DialogContent className="flex items-center justify-center border-0 bg-transparent p-0 shadow-none">
<div className="relative h-[75dvh] w-auto max-w-[92vw] aspect-[10/14] rounded-[2.5rem] border border-gray-200 bg-gray-900 p-3 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
@@ -183,7 +183,7 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
<iframe
title="Demo der Fotospiel App"
src={embedUrl}
className="aspect-[9/16] w-full rounded-[1.75rem] border-0 bg-white shadow-inner dark:bg-gray-950"
className="h-full w-full rounded-[1.75rem] border-0 bg-white shadow-inner dark:bg-gray-950"
loading="lazy"
sandbox="allow-scripts allow-same-origin allow-forms"
/>

View File

@@ -431,11 +431,33 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
}, [orderedResellerPackages, highlightResellerId]);
const testimonials = [
{ name: tCommon('testimonials.anna.name'), text: t('packages.testimonials.anna'), rating: 5 },
{ name: tCommon('testimonials.max.name'), text: t('packages.testimonials.max'), rating: 5 },
{ name: tCommon('testimonials.lisa.name'), text: t('packages.testimonials.lisa'), rating: 5 },
];
const testimonialsByPackage = useMemo(() => {
const raw = t('packages.testimonials', { returnObjects: true });
if (!raw || typeof raw !== 'object') {
return {};
}
return raw as Record<string, { name?: string; text?: string; rating?: number }[]>;
}, [locale, t]);
const testimonials = useMemo(() => {
if (!selectedPackage) {
return [];
}
const entries = testimonialsByPackage[selectedPackage.slug] ?? testimonialsByPackage.default ?? [];
if (!Array.isArray(entries)) {
return [];
}
return entries.map((entry) => ({
name: entry.name ?? '',
text: entry.text ?? '',
rating: entry.rating ?? 4,
}));
}, [selectedPackage, testimonialsByPackage]);
const renderDetailBody = (wrapperClass: string) => {
if (!selectedPackage) {
@@ -1104,7 +1126,7 @@ const PackageDetailGrid: React.FC<PackageDetailGridProps> = ({
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
{[
{ title: t('packages.faq_free'), body: t('packages.faq_free_desc') },
{ title: t('packages.faq_branding'), body: t('packages.faq_branding_desc') },
{ title: t('packages.faq_upgrade'), body: t('packages.faq_upgrade_desc') },
{ title: t('packages.faq_reseller'), body: t('packages.faq_reseller_desc') },
{ title: t('packages.faq_payment'), body: t('packages.faq_payment_desc') },

View File

@@ -118,10 +118,129 @@
"custom_branding": "Benutzerdefiniertes Branding",
"max_tenants": "Max. Tenants",
"max_events": "Events im Kontingent",
"faq_free": "Was ist das Free Package?",
"faq_branding": "Was bedeutet „Eigenes Branding“?",
"faq_branding_desc": "Du steuerst das Design der Gäste-App: Light/Dark/Auto-Modus, Farben, Schriften, Logo/Emoji und Button-Stil. Wenn dein Paket Branding erlaubt, kannst du es event-spezifisch überschreiben, sonst bleibt das Standard-Branding aktiv.",
"faq_upgrade": "Kann ich upgraden?",
"faq_upgrade_desc": "Ja, du kannst dein Paket jederzeit upgraden. Änderungen wirken sofort für das laufende Event.",
"faq_reseller": "Was für Partner / Agenturen?",
"faq_payment": "Zahlung sicher?"
"faq_reseller_desc": "Partner-Pakete bündeln mehrere Events und enthalten Team-Features wie Reseller-Dashboard und Branding-Steuerung.",
"faq_payment": "Zahlung sicher?",
"faq_payment_desc": "Ja. Zahlungen werden über sichere Zahlungsanbieter abgewickelt und mit TLS verschlüsselt übertragen.",
"testimonials_title": "Erfahrungen zum Paket",
"testimonials": {
"starter": [
{
"name": "Sarah K.",
"text": "Für unsere Gartenparty mit rund 80 Gästen war Starter ideal. Foto-Limits und Aufgaben haben gut gepasst."
},
{
"name": "Tom H.",
"text": "Wir wollten eine einfache Lösung für QR-Uploads und Galerie. Starter war genau richtig."
},
{
"name": "Maja L.",
"text": "Die 6 Monate Galerie reichen uns, und die 600 Fotos waren ein guter Rahmen."
}
],
"standard": [
{
"name": "Lena & Jonas",
"text": "Standard fühlt sich wie das Allround-Paket an: genug Gäste und Fotos plus ein Jahr Galerie."
},
{
"name": "Marco P.",
"text": "Mit Branding und Live-Slideshow wirkte das Event richtig stimmig."
},
{
"name": "Nadine R.",
"text": "Kein Wasserzeichen und eigene Farben für Hochzeiten einfach passend."
}
],
"pro": [
{
"name": "Aylin B.",
"text": "Bei großen Events ist Premium entspannt: keine Gästegrenze und viel Foto-Puffer."
},
{
"name": "Robert M.",
"text": "Die Analytics sind hilfreich für den Nachbericht, und der Support reagierte schnell."
},
{
"name": "Clara F.",
"text": "Zwei Jahre Galerie geben uns Ruhe, wenn Fotos später noch gebraucht werden."
}
],
"s-small-reseller": [
{
"name": "Agentur Huber",
"text": "Ein guter Einstieg für kleine Studios. Fünf Events lassen sich sauber planen."
},
{
"name": "Studio Meyer",
"text": "Das Partner-Dashboard spart Zeit, wenn mehrere Kunden parallel laufen."
},
{
"name": "Kathrin T.",
"text": "Für kleinere Feiern reicht Starter-Level völlig aus der Ablauf ist zuverlässig."
}
],
"m-medium-reseller": [
{
"name": "Eventbüro Lenz",
"text": "15 Events decken unsere Saison meistens ab. Standard-Level ist für viele Kunden passend."
},
{
"name": "Jasmin & Co.",
"text": "Das Reporting unterstützt die Nachbereitung und Kunden-Recaps."
},
{
"name": "Agentur Nord",
"text": "Branding und Slideshow machen die Umsetzung professionell, ohne kompliziert zu werden."
}
],
"l-large-reseller": [
{
"name": "Studio Westend",
"text": "Bei hoher Auslastung ist Premium angenehm keine Diskussionen über Limits."
},
{
"name": "Eventagentur Krämer",
"text": "Die Live-Slideshow kommt bei großen Events immer gut an."
},
{
"name": "Fritz P.",
"text": "Viele Events im Jahr lassen sich hier sauber bündeln."
}
],
"partner-premium-5": [
{
"name": "Hochzeitsstudio Weiß",
"text": "Perfekt für wenige, aber große Produktionen im Jahr."
},
{
"name": "Agentur Rosen",
"text": "Premium-Features wirken bei Kunden sehr hochwertig, ohne großes Kontingent."
},
{
"name": "Laura S.",
"text": "Wir nutzen es für unsere Top-Events zuverlässig und planbar."
}
],
"studio-annual": [
{
"name": "Studio Alster",
"text": "Das Jahreskontingent passt gut zu wiederkehrenden Kunden."
},
{
"name": "Eventservice Hahn",
"text": "24 Events geben ausreichend Spielraum über die Saison."
},
{
"name": "Agentur Süd",
"text": "Standard-Level ist ein guter Mittelweg für verschiedene Event-Typen."
}
]
}
},
"blog": {
"title": "Fotospiel - Blog",

View File

@@ -118,10 +118,129 @@
"custom_branding": "Custom Branding",
"max_tenants": "Max. Tenants",
"max_events": "Events in bundle",
"faq_free": "What is the Free Package?",
"faq_branding": "What does “Custom Branding” mean?",
"faq_branding_desc": "You control the guest apps look: Light/Dark/Auto mode, colors, fonts, logo/emoji, and button style. If your package allows branding, you can override per event; otherwise the default branding stays active.",
"faq_upgrade": "Can I upgrade?",
"faq_upgrade_desc": "Yes, you can upgrade at any time. Changes apply immediately to the current event.",
"faq_reseller": "What for Partner / Agencies?",
"faq_payment": "Payment secure?"
"faq_reseller_desc": "Partner packages bundle multiple events and include team features like reseller dashboards and branding controls.",
"faq_payment": "Payment secure?",
"faq_payment_desc": "Yes. Payments are processed by secure providers and transmitted with TLS encryption.",
"testimonials_title": "Package experiences",
"testimonials": {
"starter": [
{
"name": "Sarah K.",
"text": "Starter was ideal for our garden party of about 80 guests. The photo and task limits fit well."
},
{
"name": "Tom H.",
"text": "We wanted a simple QR upload + gallery flow. Starter did exactly that."
},
{
"name": "Maja L.",
"text": "Six months of gallery access was enough for us, and 600 photos was a good frame."
}
],
"standard": [
{
"name": "Lena & Jonas",
"text": "Standard feels like the all-round package: plenty of guests and photos plus a full year of gallery."
},
{
"name": "Marco P.",
"text": "Branding and the live slideshow made the event feel cohesive."
},
{
"name": "Nadine R.",
"text": "No watermark and custom colors fit our wedding perfectly."
}
],
"pro": [
{
"name": "Aylin B.",
"text": "For large events, Premium is stress-free: no guest limit and lots of photo headroom."
},
{
"name": "Robert M.",
"text": "Analytics help with post-event reporting, and support was quick."
},
{
"name": "Clara F.",
"text": "Two years of gallery access is a relief when photos are needed later."
}
],
"s-small-reseller": [
{
"name": "Agency Huber",
"text": "A solid entry for small studios. Five events per year are easy to plan."
},
{
"name": "Studio Meyer",
"text": "The partner dashboard saves time when running several clients."
},
{
"name": "Kathrin T.",
"text": "Starter-level coverage is enough for smaller events, and the flow is reliable."
}
],
"m-medium-reseller": [
{
"name": "Event Bureau Lenz",
"text": "Fifteen events usually cover our season. Standard level fits most clients."
},
{
"name": "Jasmin & Co.",
"text": "Reporting helps with recaps and client summaries."
},
{
"name": "Agency North",
"text": "Branding and slideshow make delivery feel professional without complexity."
}
],
"l-large-reseller": [
{
"name": "Studio Westend",
"text": "At high volume, Premium is comfortable—no back-and-forth about limits."
},
{
"name": "Agency Krämer",
"text": "The live slideshow is a consistent hit at larger events."
},
{
"name": "Fritz P.",
"text": "Plenty of events per year bundled in one plan."
}
],
"partner-premium-5": [
{
"name": "Wedding Studio Weiß",
"text": "Perfect for a few large productions each year."
},
{
"name": "Agency Rosen",
"text": "Premium features feel high-end without committing to a huge bundle."
},
{
"name": "Laura S.",
"text": "We use it for our top events—reliable and predictable."
}
],
"studio-annual": [
{
"name": "Studio Alster",
"text": "The annual bundle fits recurring clients well."
},
{
"name": "Event Service Hahn",
"text": "24 events give enough room across the season."
},
{
"name": "Agency South",
"text": "Standard level is a solid middle ground for varied event types."
}
]
}
},
"blog": {
"title": "Fotospiel - Blog",