feat: initialize landing page with dynamic competitor comparison routes and structured SEO metadata

This commit is contained in:
Timo Knuth
2026-04-10 16:18:01 +02:00
parent 8a9533c520
commit e8d013d99a
14 changed files with 1277 additions and 58 deletions

View File

@@ -0,0 +1,236 @@
import Link from 'next/link'
import Navbar from '@/components/Navbar'
import CTA from '@/components/CTA'
import Footer from '@/components/Footer'
import type { CompetitorProfile } from '@/lib/competitors'
import { siteConfig } from '@/lib/site'
interface ComparisonPageProps {
competitor: CompetitorProfile
peers: CompetitorProfile[]
}
export default function ComparisonPage({ competitor, peers }: ComparisonPageProps) {
const faqSchema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: competitor.faqs.map((item) => ({
'@type': 'Question',
name: item.question,
acceptedAnswer: {
'@type': 'Answer',
text: item.answer,
},
})),
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
/>
<Navbar />
<main className="comparison-page">
<section className="comparison-hero">
<div className="container comparison-hero-grid">
<div className="comparison-hero-copy">
<p className="tag">Comparison</p>
<h1>{siteConfig.name} vs {competitor.name}</h1>
<p className="comparison-lead">{competitor.heroSummary}</p>
<div className="comparison-actions">
<a href="#cta" className="btn-primary">Try GreenLens</a>
<a href="#comparison-table" className="btn-outline">See full comparison</a>
</div>
<p className="comparison-disclaimer">{competitor.disclaimer}</p>
</div>
<aside className="comparison-hero-card">
<p className="comparison-card-label">Fast verdict</p>
<h2>Pick GreenLens when your plant already looks wrong.</h2>
<ul className="comparison-bullet-list">
{competitor.heroVerdict.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<p className="comparison-verified">Research summary refreshed {competitor.lastVerified}</p>
</aside>
</div>
</section>
<section className="comparison-context">
<div className="container comparison-context-grid">
<article className="comparison-context-card">
<p className="tag">The competitor</p>
<h2>{competitor.name} at a glance</h2>
<p>{competitor.competitorSnapshot}</p>
</article>
<article className="comparison-context-card comparison-context-card--accent">
<p className="tag">The GreenLens angle</p>
<h2>The plant ER, not the encyclopedia.</h2>
<p>{competitor.greenLensPositioning}</p>
</article>
</div>
</section>
<section className="comparison-theses">
<div className="container">
<div className="comparison-section-head">
<p className="tag">Core difference</p>
<h2>Why users compare these two apps.</h2>
</div>
<div className="comparison-pain-grid">
<article className="comparison-pain-card">
<h3>Why searchers keep looking</h3>
<ul className="comparison-bullet-list comparison-bullet-list--dark">
{competitor.whyPeopleCompare.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</article>
{competitor.theses.map((item) => (
<article key={item.title} className="comparison-thesis-card">
<h3>{item.title}</h3>
<div className="comparison-thesis-copy">
<div>
<p className="comparison-mini-label">GreenLens</p>
<p>{item.greenlens}</p>
</div>
<div>
<p className="comparison-mini-label">{competitor.name}</p>
<p>{item.competitor}</p>
</div>
</div>
</article>
))}
</div>
</div>
</section>
<section className="comparison-table-section" id="comparison-table">
<div className="container">
<div className="comparison-section-head">
<p className="tag">At a glance</p>
<h2>Where GreenLens and {competitor.name} differ.</h2>
</div>
<div className="comparison-table">
<div className="comparison-table-header">
<span>Category</span>
<span>GreenLens</span>
<span>{competitor.name}</span>
</div>
{competitor.categories.map((item) => (
<article key={item.title} className="comparison-row">
<div className="comparison-row-title">{item.title}</div>
<div className="comparison-cell comparison-cell--greenlens">{item.greenlens}</div>
<div className="comparison-cell comparison-cell--competitor">{item.competitor}</div>
<p className="comparison-row-verdict">{item.whyItMatters}</p>
</article>
))}
</div>
</div>
</section>
<section className="comparison-fit">
<div className="container comparison-fit-grid">
<article className="comparison-fit-card comparison-fit-card--greenlens">
<p className="tag">Best fit</p>
<h2>Choose GreenLens if you need:</h2>
<ul className="comparison-bullet-list comparison-bullet-list--dark">
{competitor.greenLensBestFor.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</article>
<article className="comparison-fit-card">
<p className="tag">Still a fit</p>
<h2>Choose {competitor.name} if you need:</h2>
<ul className="comparison-bullet-list comparison-bullet-list--dark">
{competitor.competitorBestFor.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</article>
</div>
</section>
<section className="comparison-emergency">
<div className="container">
<div className="comparison-section-head">
<p className="tag">Plant ER scenarios</p>
<h2>What this difference looks like in real use.</h2>
</div>
<div className="comparison-scenario-grid">
{competitor.emergencyScenarios.map((item) => (
<article key={item.symptom} className="comparison-scenario-card">
<h3>{item.symptom}</h3>
<div className="comparison-scenario-copy">
<div>
<p className="comparison-mini-label">GreenLens</p>
<p>{item.greenlens}</p>
</div>
<div>
<p className="comparison-mini-label">{competitor.name}</p>
<p>{item.competitor}</p>
</div>
</div>
</article>
))}
</div>
</div>
</section>
<section className="comparison-faq">
<div className="container">
<div className="comparison-section-head">
<p className="tag">FAQ</p>
<h2>Questions users ask before switching.</h2>
</div>
<div className="comparison-faq-grid">
{competitor.faqs.map((item) => (
<article key={item.question} className="comparison-faq-card">
<h3>{item.question}</h3>
<p>{item.answer}</p>
</article>
))}
</div>
</div>
</section>
<section className="comparison-links">
<div className="container comparison-links-grid">
{peers.map((peer) => (
<Link key={peer.slug} href={`/vs/${peer.slug}`} className="comparison-link-card">
<p className="comparison-mini-label">Compare next</p>
<h3>{siteConfig.name} vs {peer.name}</h3>
<p>
See how GreenLens stacks up against {peer.name} for plant emergencies,
diagnosis clarity, and care workflow design.
</p>
</Link>
))}
<Link href="/support" className="comparison-link-card comparison-link-card--support">
<p className="comparison-mini-label">Need more detail?</p>
<h3>Talk to GreenLens support</h3>
<p>
Questions about billing, scans, care plans, or rollout? Use the support page
and we will help from there.
</p>
</Link>
</div>
</section>
<CTA />
</main>
<Footer />
</>
)
}

View File

@@ -31,26 +31,26 @@ const faqs = [
},
{
question: {
en: 'Can I use it offline?',
de: 'Kann ich die App offline nutzen?',
es: 'Puedo usarla sin conexion?'
en: 'Can I use GreenLens offline?',
de: 'Kann ich GreenLens offline nutzen?',
es: 'Puedo usar GreenLens sin conexion?'
},
answer: {
en: 'Some experiences may require a connection, especially for scan-related features. Saved information inside the app can remain available afterward.',
de: 'Einige Funktionen benoetigen eine Verbindung, besonders scanbezogene Features. Gespeicherte Informationen in der App koennen danach weiter verfuegbar bleiben.',
es: 'Algunas funciones requieren conexion, especialmente las relacionadas con escaneos. La informacion guardada puede seguir disponible despues.'
en: 'Plant identification and health checks require an internet connection. Your saved collection, care notes, and watering reminders are available offline.',
de: 'Pflanzenidentifikation und Gesundheitscheck benoetigen eine Internetverbindung. Deine gespeicherte Sammlung, Pflegenotizen und Giess-Erinnerungen sind offline verfuegbar.',
es: 'La identificacion de plantas y el control de salud requieren conexion a internet. Tu coleccion guardada, notas de cuidado y recordatorios de riego estan disponibles sin conexion.'
}
},
{
question: {
en: 'What kind of plants can I use it for?',
de: 'Fuer welche Pflanzen kann ich die App nutzen?',
es: 'Para que tipo de plantas puedo usar la app?'
en: 'What kind of plants can I use GreenLens for?',
de: 'Fuer welche Pflanzen kann ich GreenLens nutzen?',
es: 'Para que tipo de plantas puedo usar GreenLens?'
},
answer: {
en: 'GreenLens is built for everyday plant owners who want help with houseplants, garden plants, and general care questions.',
de: 'GreenLens richtet sich an Pflanzenbesitzer, die Hilfe bei Zimmerpflanzen, Gartenpflanzen und allgemeinen Pflegefragen wollen.',
es: 'GreenLens esta pensada para personas que quieren ayuda con plantas de interior, jardin y preguntas generales de cuidado.'
en: 'GreenLens covers 450+ plant species including houseplants, garden plants, and succulents. It is built for everyday plant owners who want identification and care guidance in one place.',
de: 'GreenLens umfasst ueber 450 Pflanzenarten, darunter Zimmerpflanzen, Gartenpflanzen und Sukkulenten. Die App richtet sich an Pflanzenbesitzer, die Identifikation und Pflege an einem Ort wollen.',
es: 'GreenLens cubre mas de 450 especies de plantas, incluyendo plantas de interior, de jardin y suculentas. Esta pensada para quienes quieren identificacion y cuidado en un solo lugar.'
}
},
{

View File

@@ -27,14 +27,20 @@ export default function Footer() {
{t.footer.cols.map((col, ci) => (
<div className="footer-col" key={col.title}>
<div className="footer-col-title">{col.title}</div>
{col.links.map((label, li) => (
<Link key={label} href={LINK_HREFS[ci]?.[li] ?? '/support'}>
{label}
</Link>
))}
</div>
))}
</div>
{col.links.map((label, li) => (
<Link key={label} href={LINK_HREFS[ci]?.[li] ?? '/support'}>
{label}
</Link>
))}
{ci === 1 && (
<>
<Link href="/vs/picturethis">GreenLens vs PictureThis</Link>
<Link href="/vs/plantum">GreenLens vs Plantum</Link>
</>
)}
</div>
))}
</div>
<div className="footer-brand-xl" aria-hidden="true">GREENLENS</div>

View File

@@ -109,7 +109,7 @@ export default function Hero() {
<div className="hero-visual reveal-fade delay-2">
<div className="hero-video-card hero-video-16-9">
<video autoPlay loop muted playsInline aria-label="GreenLens App Demo">
<source src="/GreenLensHype.mp4" type="video/mp4" />
<source src="/greenlens.mp4" type="video/mp4" />
</video>
<div className="hero-video-card-overlay" />
<div className="hero-video-badge">