feat: initialize landing page with dynamic competitor comparison routes and structured SEO metadata
This commit is contained in:
236
greenlns-landing/components/ComparisonPage.tsx
Normal file
236
greenlns-landing/components/ComparisonPage.tsx
Normal 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 />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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.'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user