183 lines
6.3 KiB
TypeScript
183 lines
6.3 KiB
TypeScript
import SeoNavbar from '@/components/seo/SeoNavbar'
|
|
import SeoFooter from '@/components/seo/SeoFooter'
|
|
import {
|
|
SeoHero,
|
|
SeoAdvantage,
|
|
SeoMethodology,
|
|
SeoComparison,
|
|
SeoContentSections,
|
|
SeoBestFit,
|
|
SeoOfferBlock,
|
|
SeoFaq,
|
|
SeoRelated,
|
|
} from '@/components/seo/SeoSections'
|
|
import type { SeoPageProfile } from '@/lib/seoPages'
|
|
import { seoTemplateCopy } from '@/lib/seoTemplateCopy'
|
|
import { siteConfig, hasIosStoreUrl } from '@/lib/site'
|
|
|
|
interface SeoCategoryPageProps {
|
|
profile: SeoPageProfile
|
|
}
|
|
|
|
export default function SeoCategoryPage({ profile }: SeoCategoryPageProps) {
|
|
const locale = profile.locale ?? 'en'
|
|
const copy = seoTemplateCopy[locale]
|
|
const templateIntent = profile.templateIntent ?? 'identification'
|
|
const usesPhotoAnalysis = templateIntent === 'identification' || templateIntent === 'diagnosis'
|
|
const showsCareInsights = templateIntent === 'care' || templateIntent === 'watering'
|
|
|
|
const faqSchema = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'FAQPage',
|
|
mainEntity: profile.faqs.map((item) => ({
|
|
'@type': 'Question',
|
|
name: item.question,
|
|
acceptedAnswer: {
|
|
'@type': 'Answer',
|
|
text: item.answer,
|
|
},
|
|
})),
|
|
}
|
|
|
|
const appSchema = profile.includeAppSchema
|
|
? {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'SoftwareApplication',
|
|
name: siteConfig.name,
|
|
operatingSystem: 'iOS',
|
|
applicationCategory: 'LifestyleApplication',
|
|
description: profile.directAnswer,
|
|
...(hasIosStoreUrl && { downloadUrl: siteConfig.iosAppStoreUrl }),
|
|
// Kein pauschaler Nullpreis: Der Download ist kostenlos, die Nutzung nach
|
|
// vier Scans nicht. Apple liefert die regionalen Preise selbst aus.
|
|
offers: {
|
|
'@type': 'Offer',
|
|
category: 'Free trial, then subscription',
|
|
priceSpecification: {
|
|
'@type': 'PriceSpecification',
|
|
description:
|
|
'One scan free without an account, three more after signing up, then a monthly or yearly GreenLens Pro subscription. Current regional pricing is shown in the App Store.',
|
|
},
|
|
},
|
|
}
|
|
: null
|
|
|
|
// HowTo: bevorzugtes Format vieler KI-Antwortsysteme fuer "Wie erkenne ich ..."-Fragen
|
|
const howToSchema =
|
|
profile.howToSteps && profile.howToSteps.length > 0
|
|
? {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'HowTo',
|
|
name: profile.howToName ?? profile.h1,
|
|
description: profile.directAnswer,
|
|
inLanguage: locale,
|
|
totalTime: 'PT5M',
|
|
step: profile.howToSteps.map((item, index) => ({
|
|
'@type': 'HowToStep',
|
|
position: index + 1,
|
|
name: item.name,
|
|
text: item.text,
|
|
})),
|
|
}
|
|
: null
|
|
|
|
// WebPage mit Aenderungsdatum, Autor und Sprache — Frische- und E-E-A-T-Signale
|
|
const webPageSchema = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'WebPage',
|
|
name: profile.h1,
|
|
description: profile.metaDescription,
|
|
url: `${siteConfig.domain}${profile.canonical}`,
|
|
inLanguage: locale,
|
|
dateModified: profile.lastUpdatedIso ?? undefined,
|
|
primaryImageOfPage: profile.heroImage
|
|
? { '@type': 'ImageObject', url: `${siteConfig.domain}${profile.heroImage}`, caption: profile.heroImageAlt }
|
|
: undefined,
|
|
isPartOf: {
|
|
'@type': 'WebSite',
|
|
name: siteConfig.name,
|
|
url: siteConfig.domain,
|
|
},
|
|
publisher: {
|
|
'@type': 'Organization',
|
|
name: siteConfig.name,
|
|
url: siteConfig.domain,
|
|
},
|
|
}
|
|
|
|
const breadcrumbSchema = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement: [
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 1,
|
|
name: 'Home',
|
|
item: siteConfig.domain,
|
|
},
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 2,
|
|
name: profile.h1,
|
|
item: `${siteConfig.domain}${profile.canonical}`,
|
|
},
|
|
],
|
|
}
|
|
|
|
return (
|
|
<div className="bg-background text-on-background font-body antialiased">
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
|
|
/>
|
|
{appSchema && (
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(appSchema) }}
|
|
/>
|
|
)}
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
|
|
/>
|
|
{howToSchema && (
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(howToSchema) }}
|
|
/>
|
|
)}
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(webPageSchema) }}
|
|
/>
|
|
|
|
<SeoNavbar locale={locale} methodologyHref={usesPhotoAnalysis ? '#methodology' : '#comparison'} />
|
|
|
|
<main className="pt-[72px] relative overflow-hidden bg-[#fdfbf6]">
|
|
{/* Dekorative Farb-Blobs */}
|
|
<div className="absolute inset-0 pointer-events-none -z-10" aria-hidden="true">
|
|
<div className="absolute top-[-5%] left-[-10%] w-[60vw] h-[60vw] max-w-[800px] max-h-[800px] bg-[#ffd8b1] blur-[120px] rounded-full mix-blend-multiply opacity-60"></div>
|
|
<div className="absolute top-[15%] right-[-5%] w-[50vw] h-[50vw] max-w-[600px] max-h-[600px] bg-[#c2e5cc] blur-[100px] rounded-full mix-blend-multiply opacity-80"></div>
|
|
<div className="absolute top-[40%] left-[15%] w-[40vw] h-[40vw] max-w-[500px] max-h-[500px] bg-[#e5f0cb] blur-[100px] rounded-full mix-blend-multiply opacity-70"></div>
|
|
</div>
|
|
|
|
<SeoHero
|
|
profile={profile}
|
|
copy={copy}
|
|
secondaryHref={usesPhotoAnalysis ? '#methodology' : '#comparison'}
|
|
/>
|
|
<SeoAdvantage profile={profile} copy={copy} showInsights={showsCareInsights} />
|
|
<SeoContentSections profile={profile} />
|
|
{usesPhotoAnalysis && <SeoMethodology copy={copy} intent={templateIntent} />}
|
|
<SeoComparison profile={profile} copy={copy} intent={templateIntent} />
|
|
<SeoBestFit profile={profile} copy={copy} />
|
|
<SeoOfferBlock profile={profile} />
|
|
<SeoFaq profile={profile} copy={copy} />
|
|
<SeoRelated profile={profile} copy={copy} />
|
|
</main>
|
|
|
|
<SeoFooter locale={locale} showMethodology={usesPhotoAnalysis} />
|
|
</div>
|
|
)
|
|
}
|