This commit is contained in:
2026-07-10 13:27:29 +02:00
parent 5c48def4e1
commit 53bac4e2b4
35 changed files with 466 additions and 320 deletions

View File

@@ -1,12 +1,13 @@
import Link from 'next/link'
import type { Lang } from '@/lib/i18n'
import { englishSeoPageSlugs, germanSeoPageSlugs, getSeoPageBySlug } from '@/lib/seoPages'
import { getSeoPageBySlug } from '@/lib/seoPages'
import { spanishSeoPageProfiles } from '@/lib/spanishSeoPages'
import { seoTemplateCopy } from '@/lib/seoTemplateCopy'
import { siteConfig, hasIosStoreUrl, hasAndroidStoreUrl } from '@/lib/site'
interface SeoFooterProps {
locale: Lang
showMethodology?: boolean
}
function slugLinks(slugs: readonly string[]) {
@@ -16,21 +17,50 @@ function slugLinks(slugs: readonly string[]) {
.map((profile) => ({ href: profile.canonical, label: profile.h1 }))
}
export default function SeoFooter({ locale }: SeoFooterProps) {
const copy = seoTemplateCopy[locale].footer
const primarySlugs: Record<Exclude<Lang, 'es'>, readonly string[]> = {
de: [
'pflanzen-erkennen-app',
'pflanzen-bestimmen',
'pflanzen-krankheiten-erkennen',
'pflanzen-pflege-app',
'giess-erinnerung-app',
'pflanzen-erkennen-kostenlos',
],
en: [
'plant-identifier-app',
'identify-plant-photo',
'plant-disease-identifier',
'plant-care-app',
'plant-health-app',
'best-plant-identification-app',
],
}
const useCaseLinks = [
...slugLinks(englishSeoPageSlugs),
{ href: '/vs/picturethis', label: 'vs PictureThis' },
{ href: '/vs/plantum', label: 'vs Plantum' },
{ href: '/vs/inaturalist', label: 'vs iNaturalist' },
{ href: '/vs/google-lens', label: 'vs Google Lens' },
]
const germanLinks = slugLinks(germanSeoPageSlugs)
const spanishLinks = Object.values(spanishSeoPageProfiles).map((profile) => ({
href: profile.canonical,
label: profile.h1,
}))
const languageHubs = [
{ href: '/', label: 'Deutsch' },
{ href: '/en', label: 'English' },
{ href: '/es', label: 'Español' },
]
const languageTitles: Record<Lang, string> = {
de: 'Sprachen',
en: 'Languages',
es: 'Idiomas',
}
export default function SeoFooter({ locale, showMethodology = true }: SeoFooterProps) {
const copy = seoTemplateCopy[locale].footer
const primaryLinks = locale === 'es'
? Object.values(spanishSeoPageProfiles).map((profile) => ({
href: profile.canonical,
label: profile.h1,
}))
: slugLinks(primarySlugs[locale])
const primaryTitle = locale === 'de'
? copy.germanTitle
: locale === 'es'
? copy.spanishTitle
: copy.useCasesTitle
return (
<footer className="bg-ink-deep text-white pt-24 pb-8 font-body overflow-hidden" id="download">
@@ -95,7 +125,7 @@ export default function SeoFooter({ locale }: SeoFooterProps) {
<h4 className="text-[10px] font-bold tracking-widest text-primary-fixed mb-6 uppercase">{copy.productTitle}</h4>
<ul className="space-y-4 text-xs text-white/80">
<li><a href="#advantage" className="hover:text-white">{seoTemplateCopy[locale].nav.features}</a></li>
<li><a href="#methodology" className="hover:text-white">{seoTemplateCopy[locale].nav.tech}</a></li>
{showMethodology && <li><a href="#methodology" className="hover:text-white">{seoTemplateCopy[locale].nav.tech}</a></li>}
<li><a href="#download" className="hover:text-white">{seoTemplateCopy[locale].nav.download}</a></li>
<li><Link href="/support" className="hover:text-white">{copy.supportLabel}</Link></li>
</ul>
@@ -103,7 +133,7 @@ export default function SeoFooter({ locale }: SeoFooterProps) {
<div>
<h4 className="text-[10px] font-bold tracking-widest text-primary-fixed mb-6 uppercase">{copy.companyTitle}</h4>
<ul className="space-y-4 text-xs text-white/80">
<li><a href="#methodology" className="hover:text-white">{seoTemplateCopy[locale].nav.how}</a></li>
{showMethodology && <li><a href="#methodology" className="hover:text-white">{seoTemplateCopy[locale].nav.how}</a></li>}
<li><a href="#faq" className="hover:text-white">FAQ</a></li>
<li><Link href="/support" className="hover:text-white">{copy.supportLabel}</Link></li>
</ul>
@@ -119,11 +149,11 @@ export default function SeoFooter({ locale }: SeoFooterProps) {
</div>
{/* SEO-Link-Spalten (interne Verlinkung) */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 mb-24">
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 mb-24">
<div>
<h4 className="text-[10px] font-bold tracking-widest text-primary-fixed mb-6 uppercase">{copy.useCasesTitle}</h4>
<h4 className="text-[10px] font-bold tracking-widest text-primary-fixed mb-6 uppercase">{primaryTitle}</h4>
<ul className="space-y-3 text-xs text-white/70">
{useCaseLinks.map((link) => (
{primaryLinks.map((link) => (
<li key={link.href}>
<Link href={link.href} className="hover:text-white">{link.label}</Link>
</li>
@@ -131,19 +161,9 @@ export default function SeoFooter({ locale }: SeoFooterProps) {
</ul>
</div>
<div>
<h4 className="text-[10px] font-bold tracking-widest text-primary-fixed mb-6 uppercase">{copy.germanTitle}</h4>
<h4 className="text-[10px] font-bold tracking-widest text-primary-fixed mb-6 uppercase">{languageTitles[locale]}</h4>
<ul className="space-y-3 text-xs text-white/70">
{germanLinks.map((link) => (
<li key={link.href}>
<Link href={link.href} className="hover:text-white">{link.label}</Link>
</li>
))}
</ul>
</div>
<div>
<h4 className="text-[10px] font-bold tracking-widest text-primary-fixed mb-6 uppercase">{copy.spanishTitle}</h4>
<ul className="space-y-3 text-xs text-white/70">
{spanishLinks.map((link) => (
{languageHubs.map((link) => (
<li key={link.href}>
<Link href={link.href} className="hover:text-white">{link.label}</Link>
</li>

View File

@@ -10,17 +10,18 @@ import { seoTemplateCopy } from '@/lib/seoTemplateCopy'
interface SeoNavbarProps {
locale: Lang
methodologyHref?: '#methodology' | '#comparison'
}
export default function SeoNavbar({ locale }: SeoNavbarProps) {
export default function SeoNavbar({ locale, methodologyHref = '#methodology' }: SeoNavbarProps) {
const [menuOpen, setMenuOpen] = useState(false)
const copy = seoTemplateCopy[locale]
const links = [
{ href: '#advantage', label: copy.nav.features },
{ href: '#methodology', label: copy.nav.tech },
{ href: methodologyHref, label: copy.nav.tech },
{ href: '#faq', label: copy.nav.faq },
{ href: '#methodology', label: copy.nav.how },
{ href: methodologyHref, label: copy.nav.how },
{ href: '/support', label: copy.nav.support },
]

View File

@@ -8,7 +8,11 @@ interface SectionProps {
copy: SeoTemplateCopy
}
export function SeoHero({ profile, copy }: SectionProps) {
export function SeoHero({
profile,
copy,
secondaryHref = '#methodology',
}: SectionProps & { secondaryHref?: string }) {
return (
<section className="max-w-[1200px] mx-auto px-6 py-16 grid grid-cols-1 lg:grid-cols-12 gap-12 items-start mt-8">
<div className="lg:col-span-6 z-10 flex flex-col justify-center pt-8">
@@ -29,7 +33,7 @@ export function SeoHero({ profile, copy }: SectionProps) {
</a>
<a
className="inline-flex justify-center items-center px-8 py-3 bg-transparent border-2 border-ink text-ink rounded-full font-bold hover:bg-ink hover:text-white transition-colors duration-300"
href="#methodology"
href={secondaryHref}
>
{copy.hero.ctaSecondary}
</a>
@@ -62,20 +66,30 @@ export function SeoHero({ profile, copy }: SectionProps) {
)
}
export function SeoAdvantage({ profile, copy }: SectionProps) {
export function SeoAdvantage({
profile,
copy,
showInsights = false,
}: SectionProps & { showInsights?: boolean }) {
const reviewedAt = {
de: 'Juli 2026',
en: 'July 2026',
es: 'Julio 2026',
}[profile.locale ?? 'en']
return (
<section className="max-w-[1200px] mx-auto px-6 py-16 grid grid-cols-1 lg:grid-cols-3 gap-12 border-t border-surface-container-high" id="advantage">
<div className="lg:col-span-2">
<section className={`max-w-[1200px] mx-auto px-6 py-16 grid grid-cols-1 ${showInsights ? 'lg:grid-cols-3' : ''} gap-12 border-t border-surface-container-high`} id="advantage">
<div className={showInsights ? 'lg:col-span-2' : ''}>
<div className="mb-8">
<span className="text-xs font-semibold tracking-[0.1em] uppercase text-primary">{copy.advantage.eyebrow}</span>
</div>
<h2 className="font-display font-semibold text-[32px] leading-[1.3] text-primary mb-6">{copy.advantage.heading}</h2>
<p className="font-body text-lg text-on-surface-variant mb-6">{profile.definitionBlock}</p>
<div className="text-xs font-semibold tracking-[0.1em] uppercase text-on-surface-variant">
{copy.advantage.updated}: {profile.lastUpdated}
{copy.advantage.updated}: {reviewedAt}
</div>
</div>
<div className="bg-surface-container rounded-xl p-8 seo-card">
{showInsights && <div className="bg-surface-container rounded-xl p-8 seo-card">
<div className="flex items-center gap-3 mb-6">
<SeoIcon name="psychology" className="w-8 h-8 text-primary" />
<h3 className="font-display font-semibold text-2xl text-primary">{copy.didYouKnow.title}</h3>
@@ -88,22 +102,30 @@ export function SeoAdvantage({ profile, copy }: SectionProps) {
</li>
))}
</ul>
</div>
</div>}
</section>
)
}
export function SeoMethodology({ copy }: { copy: SeoTemplateCopy }) {
export function SeoMethodology({
copy,
intent = 'identification',
}: {
copy: SeoTemplateCopy
intent?: 'identification' | 'diagnosis'
}) {
const methodology = intent === 'diagnosis' ? copy.diagnosisMethodology : copy.methodology
return (
<section className="bg-surface-container-low py-16 md:py-[120px]" id="methodology">
<div className="max-w-[1200px] mx-auto px-6">
<div className="text-center mb-16">
<span className="text-xs font-semibold tracking-[0.1em] uppercase text-primary block mb-4">{copy.methodology.eyebrow}</span>
<h2 className="font-display font-semibold text-[32px] md:text-[40px] leading-[1.2] text-primary">{copy.methodology.title}</h2>
<p className="font-body text-on-surface-variant max-w-2xl mx-auto mt-4">{copy.methodology.lead}</p>
<span className="text-xs font-semibold tracking-[0.1em] uppercase text-primary block mb-4">{methodology.eyebrow}</span>
<h2 className="font-display font-semibold text-[32px] md:text-[40px] leading-[1.2] text-primary">{methodology.title}</h2>
<p className="font-body text-on-surface-variant max-w-2xl mx-auto mt-4">{methodology.lead}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{copy.methodology.steps.map((step, index) => (
{methodology.steps.map((step, index) => (
<div key={step.title} className="bg-surface p-8 rounded-xl shadow-sm border border-surface-container-high seo-card">
<div className="w-12 h-12 bg-primary-container text-on-primary-container rounded-full flex items-center justify-center font-display font-semibold text-xl mb-6">
{index + 1}
@@ -120,7 +142,11 @@ export function SeoMethodology({ copy }: { copy: SeoTemplateCopy }) {
const comparisonIcons: SeoIconName[] = ['check_circle', 'calendar', 'healing', 'sun']
export function SeoComparison({ profile, copy }: SectionProps) {
export function SeoComparison({
profile,
copy,
intent = 'identification',
}: SectionProps & { intent?: 'identification' | 'diagnosis' | 'care' | 'watering' }) {
const tipLink = profile.relatedLinks[0]
return (
@@ -130,7 +156,7 @@ export function SeoComparison({ profile, copy }: SectionProps) {
<div className="lg:sticky lg:top-32">
<span className="text-xs font-semibold tracking-[0.1em] uppercase text-primary block mb-4">{copy.comparison.eyebrow}</span>
<h2 className="font-display font-semibold text-[32px] leading-[1.3] text-primary mb-6">{profile.featureTable.title}</h2>
<p className="font-body text-on-surface-variant mb-8">{copy.comparison.lead}</p>
<p className="font-body text-on-surface-variant mb-8">{copy.comparison.intentLead[intent]}</p>
{tipLink && (
<div className="bg-primary-container/20 p-6 rounded-lg border border-primary-container/30">
<h4 className="font-display font-semibold text-base text-primary mb-2">{copy.comparison.tipTitle}</h4>