diff --git a/greenlns-landing/app/imprint/page.tsx b/greenlns-landing/app/imprint/page.tsx
index b978eac..8dca5a4 100644
--- a/greenlns-landing/app/imprint/page.tsx
+++ b/greenlns-landing/app/imprint/page.tsx
@@ -12,7 +12,6 @@ const CONTENT = {
contactLabel: 'Kontakt',
registryLabel: 'Register',
vatLabel: 'USt-ID',
- note: 'Vor der Veroeffentlichung muessen alle rechtlichen Angaben mit den echten Firmendaten ersetzt werden.',
},
en: {
title: 'Imprint',
@@ -22,7 +21,6 @@ const CONTENT = {
contactLabel: 'Contact',
registryLabel: 'Registry',
vatLabel: 'VAT ID',
- note: 'Replace all legal placeholders with your real company details before publishing the site.',
},
es: {
title: 'Aviso Legal',
@@ -32,7 +30,6 @@ const CONTENT = {
contactLabel: 'Contacto',
registryLabel: 'Registro',
vatLabel: 'IVA',
- note: 'Sustituye todos los marcadores legales por tus datos reales antes de publicar el sitio.',
},
}
@@ -47,9 +44,9 @@ export default function ImprintPage() {
{c.companyLabel}: {siteConfig.company.legalName}
-
- {c.addressLabel}: {siteConfig.company.addressLine1}
-
+ {siteConfig.company.addressLine1 ? (
+ {c.addressLabel}: {siteConfig.company.addressLine1}
+ ) : null}
{siteConfig.company.addressLine2 ? {siteConfig.company.addressLine2}
: null}
{siteConfig.company.country}
@@ -58,13 +55,12 @@ export default function ImprintPage() {
{c.contactLabel}: {siteConfig.legalEmail}
-
- {c.registryLabel}: {siteConfig.company.registry}
-
-
- {c.vatLabel}: {siteConfig.company.vatId}
-
- {c.note}
+ {siteConfig.company.registry ? (
+ {c.registryLabel}: {siteConfig.company.registry}
+ ) : null}
+ {siteConfig.company.vatId ? (
+ {c.vatLabel}: {siteConfig.company.vatId}
+ ) : null}
)
diff --git a/greenlns-landing/app/layout.tsx b/greenlns-landing/app/layout.tsx
index c88ee1d..c22c4cb 100644
--- a/greenlns-landing/app/layout.tsx
+++ b/greenlns-landing/app/layout.tsx
@@ -1,72 +1,114 @@
-import type { Metadata } from 'next'
-import './globals.css'
-import { LangProvider } from '@/context/LangContext'
-import { siteConfig } from '@/lib/site'
-
-export const metadata: Metadata = {
- metadataBase: new URL(siteConfig.domain),
- title: {
- default: 'GreenLens - Plant Identifier and Care Planner',
- template: '%s | GreenLens',
- },
- description:
- 'GreenLens helps you identify plants, organize your collection, and keep up with care routines in one app.',
- keywords: [
- 'plant identifier by picture',
- 'plant care app',
- 'watering reminders',
- 'houseplant tracker',
- 'plant identification',
- 'plant health check',
- 'Pflanzen App',
- 'GreenLens',
- ],
- authors: [{ name: siteConfig.name }],
- openGraph: {
- title: 'GreenLens - Plant Identifier and Care Planner',
- description: 'Identify plants, get care guidance, and manage your collection with GreenLens.',
- type: 'website',
- url: siteConfig.domain,
- },
- alternates: {
- canonical: '/',
- languages: {
- de: '/',
- en: '/',
- es: '/',
- 'x-default': '/',
- },
- },
-}
-
-export default function RootLayout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
-
-
-
- {children}
-
-
- )
-}
+import type { Metadata } from 'next'
+import { cookies } from 'next/headers'
+import './globals.css'
+import { LangProvider } from '@/context/LangContext'
+import { siteConfig, hasIosStoreUrl } from '@/lib/site'
+
+export const metadata: Metadata = {
+ metadataBase: new URL(siteConfig.domain),
+ title: {
+ default: 'GreenLens - Plant Identifier and Care Planner',
+ template: '%s | GreenLens',
+ },
+ description:
+ 'GreenLens helps you identify plants, organize your collection, and keep up with care routines in one app.',
+ keywords: [
+ 'plant identifier by picture',
+ 'plant care app',
+ 'watering reminders',
+ 'houseplant tracker',
+ 'plant identification',
+ 'plant health check',
+ 'Pflanzen App',
+ 'GreenLens',
+ ],
+ authors: [{ name: siteConfig.name }],
+ openGraph: {
+ title: 'GreenLens - Plant Identifier and Care Planner',
+ 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: '/',
+ languages: {
+ de: '/',
+ en: '/',
+ es: '/',
+ 'x-default': '/',
+ },
+ },
+}
+
+export default async function RootLayout({ children }: { children: React.ReactNode }) {
+ const cookieStore = await cookies()
+ const lang = (cookieStore.get('lang')?.value ?? 'de') as 'de' | 'en' | 'es'
+ const validLangs = ['de', 'en', 'es']
+ const htmlLang = validLangs.includes(lang) ? lang : 'de'
+
+ return (
+
+
+
+
+
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/greenlns-landing/app/page.tsx b/greenlns-landing/app/page.tsx
index 9175ca1..fde43a9 100644
--- a/greenlns-landing/app/page.tsx
+++ b/greenlns-landing/app/page.tsx
@@ -1,29 +1,80 @@
-import Navbar from '@/components/Navbar'
-import Hero from '@/components/Hero'
-import Ticker from '@/components/Ticker'
-import Features from '@/components/Features'
-import BrownLeaf from '@/components/BrownLeaf'
-import Intelligence from '@/components/Intelligence'
-import HowItWorks from '@/components/HowItWorks'
-import FAQ from '@/components/FAQ'
-import CTA from '@/components/CTA'
-import Footer from '@/components/Footer'
-
-export default function Home() {
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )
-}
+import Navbar from '@/components/Navbar'
+import Hero from '@/components/Hero'
+import Ticker from '@/components/Ticker'
+import Features from '@/components/Features'
+import BrownLeaf from '@/components/BrownLeaf'
+import Intelligence from '@/components/Intelligence'
+import HowItWorks from '@/components/HowItWorks'
+import FAQ from '@/components/FAQ'
+import CTA from '@/components/CTA'
+import Footer from '@/components/Footer'
+
+const faqSchema = {
+ '@context': 'https://schema.org',
+ '@type': 'FAQPage',
+ mainEntity: [
+ {
+ '@type': 'Question',
+ name: 'How does GreenLens identify a plant?',
+ acceptedAnswer: {
+ '@type': 'Answer',
+ text: 'GreenLens analyzes the plant photo and combines that with app-side care guidance so you can move from scan to next steps faster.',
+ },
+ },
+ {
+ '@type': 'Question',
+ name: 'Is GreenLens free to use?',
+ acceptedAnswer: {
+ '@type': 'Answer',
+ text: 'GreenLens includes free functionality plus paid options such as subscriptions and credit top-ups for advanced AI features.',
+ },
+ },
+ {
+ '@type': 'Question',
+ 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.',
+ },
+ },
+ {
+ '@type': 'Question',
+ 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.',
+ },
+ },
+ {
+ '@type': 'Question',
+ name: 'How do I start my plant collection in GreenLens?',
+ acceptedAnswer: {
+ '@type': 'Answer',
+ text: 'Start with a scan, review the result, and save the plant to your collection to keep notes, reminders, and follow-up care in one place.',
+ },
+ },
+ ],
+}
+
+export default function Home() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/greenlns-landing/app/sitemap.ts b/greenlns-landing/app/sitemap.ts
index b4f9552..8f07c61 100644
--- a/greenlns-landing/app/sitemap.ts
+++ b/greenlns-landing/app/sitemap.ts
@@ -5,26 +5,32 @@ export default function sitemap(): MetadataRoute.Sitemap {
return [
{
- url: baseUrl,
- lastModified: new Date(),
- changeFrequency: 'weekly',
- priority: 1,
- },
- {
- url: `${baseUrl}/imprint`,
- lastModified: new Date(),
- changeFrequency: 'monthly',
- priority: 0.3,
- },
+ url: baseUrl,
+ lastModified: new Date('2026-04-08'),
+ changeFrequency: 'weekly',
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/support`,
+ lastModified: new Date('2026-04-08'),
+ changeFrequency: 'monthly',
+ priority: 0.5,
+ },
+ {
+ url: `${baseUrl}/imprint`,
+ lastModified: new Date('2026-04-08'),
+ changeFrequency: 'monthly',
+ priority: 0.3,
+ },
{
url: `${baseUrl}/privacy`,
- lastModified: new Date(),
+ lastModified: new Date('2026-04-08'),
changeFrequency: 'monthly',
priority: 0.3,
},
{
url: `${baseUrl}/terms`,
- lastModified: new Date(),
+ lastModified: new Date('2026-04-08'),
changeFrequency: 'monthly',
priority: 0.3,
},
diff --git a/greenlns-landing/context/LangContext.tsx b/greenlns-landing/context/LangContext.tsx
index 74e9ac0..3eeaf71 100644
--- a/greenlns-landing/context/LangContext.tsx
+++ b/greenlns-landing/context/LangContext.tsx
@@ -1,27 +1,46 @@
-'use client'
-
-import { createContext, useContext, useState, ReactNode } from 'react'
-import { Lang, translations } from '@/lib/i18n'
-
-interface LangCtx {
- lang: Lang
- setLang: (l: Lang) => void
- t: typeof translations.de
-}
-
-const LangContext = createContext({
- lang: 'de',
- setLang: () => {},
- t: translations.de,
-})
-
-export function LangProvider({ children }: { children: ReactNode }) {
- const [lang, setLang] = useState('de')
- return (
-
- {children}
-
- )
-}
-
-export const useLang = () => useContext(LangContext)
+'use client'
+
+import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
+import { Lang, translations } from '@/lib/i18n'
+
+interface LangCtx {
+ lang: Lang
+ setLang: (l: Lang) => void
+ t: typeof translations.de
+}
+
+const LangContext = createContext({
+ lang: 'de',
+ setLang: () => {},
+ t: translations.de,
+})
+
+function getInitialLang(): Lang {
+ if (typeof document === 'undefined') return 'de'
+ const match = document.cookie.match(/(?:^|;\s*)lang=([^;]+)/)
+ const val = match?.[1]
+ return val === 'en' || val === 'es' || val === 'de' ? val : 'de'
+}
+
+export function LangProvider({ children }: { children: ReactNode }) {
+ const [lang, setLangState] = useState('de')
+
+ useEffect(() => {
+ setLangState(getInitialLang())
+ }, [])
+
+ const setLang = (l: Lang) => {
+ document.cookie = `lang=${l};path=/;max-age=31536000;SameSite=Lax`
+ // Update for the current page visit without a full reload
+ document.documentElement.lang = l
+ setLangState(l)
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export const useLang = () => useContext(LangContext)
diff --git a/greenlns-landing/lib/site.ts b/greenlns-landing/lib/site.ts
index 93b4dca..a55ee21 100644
--- a/greenlns-landing/lib/site.ts
+++ b/greenlns-landing/lib/site.ts
@@ -1,20 +1,20 @@
-const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || 'https://greenlenspro.com').trim()
-
-export const siteConfig = {
- name: 'GreenLens',
- domain: siteUrl,
- supportEmail: 'knuth.timo@gmail.com',
- legalEmail: 'knuth.timo@gmail.com',
- iosAppStoreUrl: '',
+const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || 'https://greenlenspro.com').trim()
+
+export const siteConfig = {
+ name: 'GreenLens',
+ domain: siteUrl,
+ supportEmail: 'knuth.timo@gmail.com',
+ legalEmail: 'knuth.timo@gmail.com',
+ iosAppStoreUrl: 'https://apps.apple.com/de/app/greenlens-pro/id6759843546?l=en-GB',
androidPlayStoreUrl: '',
company: {
legalName: 'GreenLens',
representative: 'Tim Knuth',
- addressLine1: 'Replace with your legal business address',
+ addressLine1: '',
addressLine2: '',
country: 'Germany',
- registry: 'Replace with your company registry details',
- vatId: 'Replace with your VAT ID or remove this line',
+ registry: '',
+ vatId: '',
},
} as const
diff --git a/greenlns-landing/public/og-image.png b/greenlns-landing/public/og-image.png
new file mode 100644
index 0000000..b1c1183
Binary files /dev/null and b/greenlns-landing/public/og-image.png differ