From e8d013d99abb660c88ea8ee8ac283cbf8f23b987 Mon Sep 17 00:00:00 2001 From: Timo Knuth Date: Fri, 10 Apr 2026 16:18:01 +0200 Subject: [PATCH] feat: initialize landing page with dynamic competitor comparison routes and structured SEO metadata --- greenlns-landing/app/globals.css | 359 +++++++++++++++- greenlns-landing/app/layout.tsx | 9 - greenlns-landing/app/opengraph-image.tsx | 83 ++++ greenlns-landing/app/page.tsx | 40 +- greenlns-landing/app/sitemap.ts | 12 + greenlns-landing/app/vs/[competitor]/page.tsx | 70 ++++ .../components/ComparisonPage.tsx | 236 +++++++++++ greenlns-landing/components/FAQ.tsx | 24 +- greenlns-landing/components/Footer.tsx | 22 +- greenlns-landing/components/Hero.tsx | 2 +- greenlns-landing/lib/competitors.ts | 384 ++++++++++++++++++ greenlns-landing/lib/i18n.ts | 6 +- greenlns-landing/next.config.ts | 3 - keyword-research.csv | 85 ++++ 14 files changed, 1277 insertions(+), 58 deletions(-) create mode 100644 greenlns-landing/app/opengraph-image.tsx create mode 100644 greenlns-landing/app/vs/[competitor]/page.tsx create mode 100644 greenlns-landing/components/ComparisonPage.tsx create mode 100644 greenlns-landing/lib/competitors.ts create mode 100644 keyword-research.csv diff --git a/greenlns-landing/app/globals.css b/greenlns-landing/app/globals.css index d47256c..601f42a 100644 --- a/greenlns-landing/app/globals.css +++ b/greenlns-landing/app/globals.css @@ -1208,14 +1208,291 @@ h3 { margin-bottom: var(--s2); } -.support-faq-item p { - color: var(--muted); -} - -/* ============================================= - RESPONSIVE - ============================================= */ -@media (max-width: 1024px) { +.support-faq-item p { + color: var(--muted); +} + +/* ============================================= + COMPARISON PAGES + ============================================= */ +.comparison-page { + background: + radial-gradient(circle at top left, rgba(86, 160, 116, 0.16), transparent 26%), + linear-gradient(180deg, var(--cream) 0%, var(--white) 100%); +} + +.comparison-hero { + background: + linear-gradient(135deg, rgba(13, 22, 15, 0.96) 0%, rgba(28, 46, 33, 0.92) 45%, rgba(42, 92, 63, 0.86) 100%); + color: var(--cream); + padding: 11rem 0 5rem; +} + +.comparison-hero-grid, +.comparison-context-grid, +.comparison-fit-grid, +.comparison-links-grid { + display: grid; + grid-template-columns: 1.2fr 0.8fr; + gap: var(--s4); +} + +.comparison-hero-copy h1 { + max-width: 12ch; + margin-bottom: var(--s3); +} + +.comparison-lead, +.comparison-disclaimer, +.comparison-context-card p, +.comparison-thesis-copy p, +.comparison-row-verdict, +.comparison-faq-card p, +.comparison-link-card p, +.comparison-scenario-copy p { + line-height: 1.75; +} + +.comparison-lead { + max-width: 700px; + color: rgba(244, 241, 232, 0.86); + font-size: 1.08rem; +} + +.comparison-actions { + display: flex; + flex-wrap: wrap; + gap: var(--s2); + margin: var(--s4) 0 var(--s3); +} + +.comparison-disclaimer, +.comparison-verified { + font-size: 0.82rem; + color: rgba(244, 241, 232, 0.72); +} + +.comparison-hero-card, +.comparison-context-card, +.comparison-pain-card, +.comparison-thesis-card, +.comparison-fit-card, +.comparison-scenario-card, +.comparison-faq-card, +.comparison-link-card, +.comparison-row { + border-radius: var(--r-lg); + box-shadow: 0 24px 60px rgba(19, 31, 22, 0.08); +} + +.comparison-hero-card { + background: rgba(244, 241, 232, 0.08); + border: 1px solid rgba(244, 241, 232, 0.12); + padding: var(--s4); + align-self: start; +} + +.comparison-hero-card h2 { + font-size: clamp(1.55rem, 2.2vw, 2.2rem); + margin-bottom: var(--s3); +} + +.comparison-card-label, +.comparison-mini-label { + display: inline-block; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.comparison-card-label { + color: var(--green-light); + margin-bottom: var(--s2); +} + +.comparison-mini-label { + color: var(--accent); + margin-bottom: 0.55rem; +} + +.comparison-bullet-list { + display: flex; + flex-direction: column; + gap: 0.9rem; +} + +.comparison-bullet-list li { + position: relative; + padding-left: 1.25rem; +} + +.comparison-bullet-list li::before { + content: ''; + position: absolute; + top: 0.7rem; + left: 0; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--accent); +} + +.comparison-bullet-list--dark li::before { + background: var(--green-mid); +} + +.comparison-context, +.comparison-theses, +.comparison-table-section, +.comparison-fit, +.comparison-emergency, +.comparison-faq, +.comparison-links { + padding: var(--s12) 0; +} + +.comparison-context-card, +.comparison-pain-card, +.comparison-thesis-card, +.comparison-fit-card, +.comparison-scenario-card, +.comparison-faq-card, +.comparison-link-card { + background: rgba(255, 255, 255, 0.86); + border: 1px solid rgba(19, 31, 22, 0.08); + padding: var(--s4); +} + +.comparison-context-card h2, +.comparison-fit-card h2, +.comparison-link-card h3 { + margin-bottom: var(--s2); +} + +.comparison-context-card--accent, +.comparison-fit-card--greenlens { + background: + linear-gradient(180deg, rgba(86, 160, 116, 0.12) 0%, rgba(255, 255, 255, 0.96) 100%); +} + +.comparison-section-head { + max-width: 720px; + margin-bottom: var(--s4); +} + +.comparison-section-head h2 { + color: var(--dark); +} + +.comparison-pain-grid, +.comparison-scenario-grid, +.comparison-faq-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--s3); +} + +.comparison-pain-card { + background: var(--dark); + color: var(--cream); +} + +.comparison-pain-card h3, +.comparison-thesis-card h3, +.comparison-scenario-card h3, +.comparison-faq-card h3 { + margin-bottom: var(--s2); +} + +.comparison-thesis-copy, +.comparison-scenario-copy { + display: grid; + gap: var(--s3); +} + +.comparison-table { + display: grid; + gap: var(--s3); +} + +.comparison-table-header { + display: grid; + grid-template-columns: 0.75fr 1fr 1fr; + gap: var(--s3); + padding: 0 var(--s2); + color: var(--muted); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.comparison-row { + background: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(19, 31, 22, 0.08); + display: grid; + grid-template-columns: 0.75fr 1fr 1fr; + gap: var(--s3); + padding: var(--s3); +} + +.comparison-row-title { + font-family: var(--display); + font-size: 1.4rem; + color: var(--dark); +} + +.comparison-cell { + padding: var(--s3); + border-radius: var(--r-md); + line-height: 1.75; +} + +.comparison-cell--greenlens { + background: rgba(86, 160, 116, 0.12); + border: 1px solid rgba(86, 160, 116, 0.18); +} + +.comparison-cell--competitor { + background: rgba(19, 31, 22, 0.05); + border: 1px solid rgba(19, 31, 22, 0.08); +} + +.comparison-row-verdict { + grid-column: 1 / -1; + margin-top: 0.2rem; + color: var(--muted); +} + +.comparison-links-grid { + grid-template-columns: repeat(2, 1fr); +} + +.comparison-link-card { + display: block; + transition: transform var(--t), box-shadow var(--t); +} + +.comparison-link-card:hover { + transform: translateY(-4px); + box-shadow: 0 20px 50px rgba(19, 31, 22, 0.12); +} + +.comparison-link-card--support { + background: var(--dark); + color: var(--cream); +} + +.comparison-link-card--support .comparison-mini-label, +.comparison-link-card--support p { + color: rgba(244, 241, 232, 0.76); +} + +/* ============================================= + RESPONSIVE + ============================================= */ +@media (max-width: 1024px) { .hero .container { grid-template-columns: 1fr; } @@ -1266,13 +1543,29 @@ h3 { display: none; } - .footer-inner { - grid-template-columns: 1fr 1fr; - gap: var(--s6); - } -} - -@media (max-width: 768px) { + .footer-inner { + grid-template-columns: 1fr 1fr; + gap: var(--s6); + } + + .comparison-hero-grid, + .comparison-context-grid, + .comparison-fit-grid, + .comparison-links-grid, + .comparison-pain-grid, + .comparison-scenario-grid, + .comparison-faq-grid, + .comparison-table-header, + .comparison-row { + grid-template-columns: 1fr; + } + + .comparison-row-title { + font-size: 1.7rem; + } +} + +@media (max-width: 768px) { .nav-links { display: none; } @@ -1315,8 +1608,34 @@ h3 { text-align: center; } - .support-grid, - .support-faq-list { - grid-template-columns: 1fr; - } -} + .support-grid, + .support-faq-list { + grid-template-columns: 1fr; + } + + .comparison-hero { + padding-top: 9rem; + } + + .comparison-actions { + flex-direction: column; + align-items: stretch; + } + + .comparison-pain-grid, + .comparison-scenario-grid, + .comparison-faq-grid, + .comparison-links-grid { + grid-template-columns: 1fr; + } + + .comparison-context, + .comparison-theses, + .comparison-table-section, + .comparison-fit, + .comparison-emergency, + .comparison-faq, + .comparison-links { + padding: var(--s8) 0; + } +} diff --git a/greenlns-landing/app/layout.tsx b/greenlns-landing/app/layout.tsx index c22c4cb..a01ffb2 100644 --- a/greenlns-landing/app/layout.tsx +++ b/greenlns-landing/app/layout.tsx @@ -28,20 +28,11 @@ export const metadata: Metadata = { description: 'Identify plants, get care guidance, and manage your collection with GreenLens.', type: 'website', url: siteConfig.domain, - images: [ - { - url: '/og-image.png', - width: 1200, - height: 630, - alt: 'GreenLens – Plant Identifier and Care Planner', - }, - ], }, twitter: { card: 'summary_large_image', title: 'GreenLens - Plant Identifier and Care Planner', description: 'Identify plants, get care guidance, and manage your collection with GreenLens.', - images: ['/og-image.png'], }, alternates: { canonical: '/', diff --git a/greenlns-landing/app/opengraph-image.tsx b/greenlns-landing/app/opengraph-image.tsx new file mode 100644 index 0000000..27641f9 --- /dev/null +++ b/greenlns-landing/app/opengraph-image.tsx @@ -0,0 +1,83 @@ +import { ImageResponse } from 'next/og' + +export const runtime = 'edge' +export const alt = 'GreenLens – Plant Identifier and Care Planner' +export const size = { width: 1200, height: 630 } +export const contentType = 'image/png' + +export default function OGImage() { + return new ImageResponse( + ( +
+
+ Plant Identifier & Care App +
+ +
+ GreenLens +
+ +
+ Identify plants, get AI-powered care plans, and manage your collection. +
+ +
+ {['450+ plant species', 'AI-powered scans', 'iOS & Android'].map((label) => ( +
+ {label} +
+ ))} +
+
+ ), + { ...size }, + ) +} diff --git a/greenlns-landing/app/page.tsx b/greenlns-landing/app/page.tsx index fde43a9..5bf8fe4 100644 --- a/greenlns-landing/app/page.tsx +++ b/greenlns-landing/app/page.tsx @@ -9,6 +9,38 @@ import FAQ from '@/components/FAQ' import CTA from '@/components/CTA' import Footer from '@/components/Footer' +const howToSchema = { + '@context': 'https://schema.org', + '@type': 'HowTo', + name: 'How to identify a plant with GreenLens', + step: [ + { + '@type': 'HowToStep', + position: 1, + name: 'Photograph your plant', + text: 'Open the app, point the camera at your plant and tap Scan.', + }, + { + '@type': 'HowToStep', + position: 2, + name: 'AI identifies instantly', + text: 'In under a second you get the exact name, species and all key details.', + }, + { + '@type': 'HowToStep', + position: 3, + name: 'Receive care plan', + text: 'GreenLens automatically creates a personalized care plan for your plant and location.', + }, + { + '@type': 'HowToStep', + position: 4, + name: 'Track growth', + text: 'Document photos, track watering and get reminded of important care dates.', + }, + ], +} + const faqSchema = { '@context': 'https://schema.org', '@type': 'FAQPage', @@ -34,7 +66,7 @@ const faqSchema = { name: 'Can I use GreenLens offline?', acceptedAnswer: { '@type': 'Answer', - text: 'Some experiences may require a connection, especially for scan-related features. Saved information inside the app can remain available afterward.', + text: 'Plant identification and health checks require an internet connection. Your saved collection, care notes, and watering reminders are available offline.', }, }, { @@ -42,7 +74,7 @@ const faqSchema = { name: 'What kind of plants can I use GreenLens for?', acceptedAnswer: { '@type': 'Answer', - text: 'GreenLens is built for everyday plant owners who want help with houseplants, garden plants, and general care questions.', + text: '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.', }, }, { @@ -59,6 +91,10 @@ const faqSchema = { export default function Home() { return ( <> +