SEO blogpost
This commit is contained in:
59
greenlns-landing/app/blog/[slug]/page.tsx
Normal file
59
greenlns-landing/app/blog/[slug]/page.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { blogPosts } from '@/lib/blogPosts'
|
||||
import { BlogPostTemplate } from '@/components/blog/BlogPostTemplate'
|
||||
import { getHreflangAlternates } from '@/lib/localeRoutes'
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return Object.keys(blogPosts).map((slug) => ({ slug }))
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { slug } = await params
|
||||
const post = blogPosts[slug]
|
||||
if (!post) return {}
|
||||
|
||||
const languages = getHreflangAlternates(post.canonical)
|
||||
|
||||
return {
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
// Selbstreferenzierender Canonical. Wichtig für die Syndication: Wenn der
|
||||
// Beitrag auf Medium, DEV.to oder LinkedIn zweitveröffentlicht wird, muss
|
||||
// dort ein rel=canonical auf genau diese URL zeigen.
|
||||
alternates: {
|
||||
canonical: post.canonical,
|
||||
...(languages && { languages }),
|
||||
},
|
||||
openGraph: {
|
||||
type: 'article',
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
url: post.canonical,
|
||||
publishedTime: post.publishedAtIso,
|
||||
modifiedTime: post.updatedAtIso || post.publishedAtIso,
|
||||
authors: [post.author.name],
|
||||
images: [{ url: post.heroImage, width: 1200, height: 630, alt: post.heroImageAlt }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
images: [post.heroImage],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function BlogPostPage({ params }: Props) {
|
||||
const { slug } = await params
|
||||
const post = blogPosts[slug]
|
||||
if (!post) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return <BlogPostTemplate post={post} />
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Metadata } from 'next'
|
||||
import { blogPosts } from '@/lib/blogPosts'
|
||||
import { BlogPostTemplate } from '@/components/blog/BlogPostTemplate'
|
||||
|
||||
const post = blogPosts['houseplant-watering-schedule']
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
alternates: {
|
||||
canonical: post.canonical,
|
||||
},
|
||||
openGraph: {
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
images: [{ url: post.heroImage }],
|
||||
},
|
||||
}
|
||||
|
||||
export default function HouseplantWateringSchedulePage() {
|
||||
return <BlogPostTemplate post={post} />
|
||||
}
|
||||
97
greenlns-landing/app/blog/page.tsx
Normal file
97
greenlns-landing/app/blog/page.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
import SeoNavbar from '@/components/seo/SeoNavbar'
|
||||
import SeoFooter from '@/components/seo/SeoFooter'
|
||||
import { blogPosts } from '@/lib/blogPosts'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'GreenLens Plant Care & Identification Blog',
|
||||
description:
|
||||
'Expert guides on houseplant watering schedules, plant disease diagnosis, pest control, and photo identification.',
|
||||
alternates: {
|
||||
canonical: '/blog',
|
||||
},
|
||||
}
|
||||
|
||||
export default function BlogIndexPage() {
|
||||
const posts = Object.values(blogPosts)
|
||||
|
||||
return (
|
||||
<div className="bg-[#fdfbf6] text-on-surface font-body antialiased min-h-screen relative overflow-hidden flex flex-col justify-between">
|
||||
{/* Header Navigation */}
|
||||
<SeoNavbar locale="en" />
|
||||
|
||||
{/* Decorative Ambient Green 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-[#c2e5cc] blur-[120px] rounded-full mix-blend-multiply opacity-70" />
|
||||
<div className="absolute top-[25%] right-[-5%] w-[50vw] h-[50vw] max-w-[600px] max-h-[600px] bg-[#e5f0cb] blur-[100px] rounded-full mix-blend-multiply opacity-80" />
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="pt-32 pb-24 flex-1">
|
||||
<div className="max-w-[1100px] mx-auto px-6">
|
||||
{/* Hero Header */}
|
||||
<div className="text-center max-w-[750px] mx-auto mb-16">
|
||||
<span className="bg-emerald-600/10 text-emerald-800 border border-emerald-600/20 text-xs font-extrabold tracking-widest uppercase px-4 py-1.5 rounded-full inline-block mb-4 shadow-xs">
|
||||
🌿 Botanical Research & Guides
|
||||
</span>
|
||||
<h1 className="font-display text-4xl sm:text-5xl font-bold text-emerald-950 mb-4 tracking-tight leading-tight">
|
||||
Plant Care & Identification Guides
|
||||
</h1>
|
||||
<p className="text-emerald-900/80 text-lg sm:text-xl font-medium leading-relaxed">
|
||||
Evidence-based guides on indoor plant watering schedules, health diagnostics, pest control, and species identification.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Blog Cards Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{posts.map((post) => (
|
||||
<Link
|
||||
key={post.slug}
|
||||
href={`/blog/${post.slug}`}
|
||||
className="group bg-white rounded-3xl border-2 border-emerald-600/20 overflow-hidden shadow-sm hover:shadow-2xl hover:border-emerald-600/40 transition-all duration-300 flex flex-col"
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="relative h-52 w-full overflow-hidden bg-emerald-950">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={post.heroImage}
|
||||
alt={post.heroImageAlt}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
<div className="absolute top-4 left-4 bg-emerald-950/90 backdrop-blur-md px-3.5 py-1 rounded-full text-[10px] font-extrabold uppercase tracking-wider text-emerald-300 border border-emerald-500/30">
|
||||
{post.category}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card Content */}
|
||||
<div className="p-6 flex-1 flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="text-xs text-emerald-800 font-semibold mb-2">
|
||||
{post.publishedAt} • {post.readTime}
|
||||
</div>
|
||||
<h2 className="font-display text-xl font-bold text-emerald-950 group-hover:text-emerald-700 transition-colors mb-3 leading-snug">
|
||||
{post.title}
|
||||
</h2>
|
||||
<p className="text-emerald-900/70 text-sm line-clamp-3 leading-relaxed mb-6 font-normal">
|
||||
{post.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-emerald-700 font-bold text-sm flex items-center gap-1.5 group-hover:translate-x-1 transition-transform">
|
||||
<span>Read Full Guide</span>
|
||||
<span>→</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Footer Navigation */}
|
||||
<SeoFooter locale="en" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Metadata } from 'next'
|
||||
import { blogPosts } from '@/lib/blogPosts'
|
||||
import { BlogPostTemplate } from '@/components/blog/BlogPostTemplate'
|
||||
|
||||
const post = blogPosts['plant-health-diagnosis-guide']
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
alternates: {
|
||||
canonical: post.canonical,
|
||||
},
|
||||
openGraph: {
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
images: [{ url: post.heroImage }],
|
||||
},
|
||||
}
|
||||
|
||||
export default function PlantHealthDiagnosisGuidePage() {
|
||||
return <BlogPostTemplate post={post} />
|
||||
}
|
||||
22
greenlns-landing/app/blog/which-plant-is-this-guide/page.tsx
Normal file
22
greenlns-landing/app/blog/which-plant-is-this-guide/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Metadata } from 'next'
|
||||
import { blogPosts } from '@/lib/blogPosts'
|
||||
import { BlogPostTemplate } from '@/components/blog/BlogPostTemplate'
|
||||
|
||||
const post = blogPosts['which-plant-is-this-guide']
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
alternates: {
|
||||
canonical: post.canonical,
|
||||
},
|
||||
openGraph: {
|
||||
title: post.metaTitle,
|
||||
description: post.metaDescription,
|
||||
images: [{ url: post.heroImage }],
|
||||
},
|
||||
}
|
||||
|
||||
export default function WhichPlantIsThisGuidePage() {
|
||||
return <BlogPostTemplate post={post} />
|
||||
}
|
||||
@@ -23,6 +23,18 @@ export const metadata: Metadata = {
|
||||
},
|
||||
}
|
||||
|
||||
const engineeringPosts = [
|
||||
{ slug: 'vision-ai-plant-disease-diagnostic', title: 'Building a Plant Disease Diagnostic Engine with Vision AI & Multi-Symptom Scoring', readTime: '8 min read' },
|
||||
{ slug: 'chrome-extension-manifest-v3-image-scanner', title: 'How to Build a Lightweight Chrome Extension (Manifest V3) for Real-Time Image & Canvas Inspection', readTime: '7 min read' },
|
||||
{ slug: 'zero-dependency-python-sdk', title: 'Designing a Zero-Dependency Python SDK for REST APIs with Automatic Retries & Rate-Limit Backoff', readTime: '7 min read' },
|
||||
{ slug: 'react-native-offline-first-sync', title: 'Offline-First Mobile Architecture: Syncing Local SQLite Storage with Remote AI Services in React Native', readTime: '8 min read' },
|
||||
{ slug: 'typescript-cli-automation-tool', title: 'Building a CLI Tool in TypeScript for Automated Image & Asset Diagnostic Workflows', readTime: '6 min read' },
|
||||
{ slug: 'computer-vision-edge-cases-plant-health', title: 'Computer Vision Edge Cases: Why Plant Models Agree on Species But Disagree on Health', readTime: '7 min read' },
|
||||
{ slug: 'programmatic-seo-nextjs-app-router', title: 'Programmatic SEO Infrastructure with Next.js App Router: Type-Safe Page Factories', readTime: '7 min read' },
|
||||
{ slug: 'automated-content-syndication-engine', title: 'Automated Content Syndication: Publishing Developer Posts to DEV.to and Hashnode', readTime: '6 min read' },
|
||||
{ slug: 'cross-platform-design-system-tokens', title: 'Building a Cross-Platform Design System for Web (Next.js) and Mobile (React Native)', readTime: '7 min read' },
|
||||
]
|
||||
|
||||
const tools = [
|
||||
{
|
||||
icon: '💻',
|
||||
@@ -54,11 +66,11 @@ const tools = [
|
||||
{
|
||||
icon: '🧩',
|
||||
eyebrow: 'Chrome Web Store',
|
||||
title: 'Chrome extension (Soon)',
|
||||
title: 'Chrome extension',
|
||||
description: 'Search a local plant catalog, check leaf symptoms, or select a plant photo from the current browser page for AI analysis.',
|
||||
command: 'Browser popup + photo scan tool',
|
||||
href: 'https://greenlenspro.com/support',
|
||||
linkLabel: 'Chrome extension details (Soon)',
|
||||
href: siteConfig.chromeWebStoreUrl,
|
||||
linkLabel: 'Chrome Web Store',
|
||||
},
|
||||
{
|
||||
icon: '🐙',
|
||||
@@ -149,6 +161,34 @@ export default function DevelopersPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-20 md:py-28 border-b border-outline-variant/60" id="engineering-blog">
|
||||
<div className="max-w-[1200px] mx-auto px-6">
|
||||
<div className="max-w-2xl mb-12 md:mb-16">
|
||||
<p className="text-xs font-bold tracking-[0.18em] uppercase text-primary mb-5">Engineering blog</p>
|
||||
<h2 className="font-display text-4xl md:text-5xl font-bold leading-tight text-ink">How we built the GreenLens platform.</h2>
|
||||
<p className="mt-5 max-w-2xl text-base leading-relaxed text-on-surface-variant">
|
||||
Deep dives into the real architecture behind GreenLens: the vision AI diagnostic pipeline, the Chrome extension, the Python SDK, offline-first sync, and more.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{engineeringPosts.map((post) => (
|
||||
<Link
|
||||
key={post.slug}
|
||||
href={`/blog/${post.slug}`}
|
||||
className="flex flex-col rounded-2xl bg-white p-6 border border-outline-variant/70 transition-transform duration-300 hover:-translate-y-1 hover:shadow-[0_18px_40px_-28px_rgba(1,38,31,0.38)]"
|
||||
>
|
||||
<span className="text-[10px] font-bold tracking-[0.15em] uppercase text-primary">{post.readTime}</span>
|
||||
<h3 className="mt-3 font-display text-lg font-bold leading-snug text-ink">{post.title}</h3>
|
||||
<span className="mt-4 inline-flex w-fit items-center text-sm font-bold text-primary">
|
||||
Read the post <span aria-hidden="true" className="ml-2">→</span>
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-20 md:py-28" id="faq">
|
||||
<div className="max-w-[1200px] mx-auto px-6 grid gap-8 lg:grid-cols-[1fr_auto] lg:items-end">
|
||||
<div>
|
||||
|
||||
4
greenlns-landing/app/giessplan-zimmerpflanzen/page.tsx
Normal file
4
greenlns-landing/app/giessplan-zimmerpflanzen/page.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import { buildSeoPageMetadata, createSeoPage } from '@/lib/seoPageFactory'
|
||||
|
||||
export const metadata = buildSeoPageMetadata('giessplan-zimmerpflanzen')
|
||||
export default createSeoPage('giessplan-zimmerpflanzen')
|
||||
4
greenlns-landing/app/pflanzen-diagnose/page.tsx
Normal file
4
greenlns-landing/app/pflanzen-diagnose/page.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import { buildSeoPageMetadata, createSeoPage } from '@/lib/seoPageFactory'
|
||||
|
||||
export const metadata = buildSeoPageMetadata('pflanzen-diagnose')
|
||||
export default createSeoPage('pflanzen-diagnose')
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MetadataRoute } from 'next'
|
||||
import { germanSeoPageSlugs, getGermanSeoPageBySlug } from '@/lib/seoPages'
|
||||
import { spanishSeoPageProfiles, spanishSeoPageSlugs } from '@/lib/spanishSeoPages'
|
||||
import { blogPosts } from '@/lib/blogPosts'
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || 'https://greenlenspro.com').trim()
|
||||
@@ -153,6 +154,18 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.85,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/blog`,
|
||||
lastModified: new Date('2026-08-04'),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.85,
|
||||
},
|
||||
...Object.keys(blogPosts).map((slug) => ({
|
||||
url: `${baseUrl}/blog/${slug}`,
|
||||
lastModified: new Date('2026-08-05'),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
...spanishSeoPageSlugs.map((slug) => ({
|
||||
url: `${baseUrl}${spanishSeoPageProfiles[slug].canonical}`,
|
||||
lastModified: new Date('2026-07-10'),
|
||||
|
||||
4
greenlns-landing/app/welche-pflanze-ist-das/page.tsx
Normal file
4
greenlns-landing/app/welche-pflanze-ist-das/page.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import { buildSeoPageMetadata, createSeoPage } from '@/lib/seoPageFactory'
|
||||
|
||||
export const metadata = buildSeoPageMetadata('welche-pflanze-ist-das')
|
||||
export default createSeoPage('welche-pflanze-ist-das')
|
||||
@@ -0,0 +1,4 @@
|
||||
import { buildSeoPageMetadata, createSeoPage } from '@/lib/seoPageFactory'
|
||||
|
||||
export const metadata = buildSeoPageMetadata('wie-funktioniert-pflanzenerkennung')
|
||||
export default createSeoPage('wie-funktioniert-pflanzenerkennung')
|
||||
@@ -0,0 +1,4 @@
|
||||
import { buildSeoPageMetadata, createSeoPage } from '@/lib/seoPageFactory'
|
||||
|
||||
export const metadata = buildSeoPageMetadata('zimmerpflanzen-mit-wenig-licht')
|
||||
export default createSeoPage('zimmerpflanzen-mit-wenig-licht')
|
||||
@@ -89,7 +89,7 @@ export default function Footer() {
|
||||
<a href="https://marketplace.visualstudio.com/items?itemName=TimoKnuth.greenlens-vscode" target="_blank" rel="noopener noreferrer">VS Code Extension</a>
|
||||
<a href="https://pypi.org/project/greenlens-python/" target="_blank" rel="noopener noreferrer">Python SDK & CLI (PyPI)</a>
|
||||
<a href="https://www.npmjs.com/package/greenlenspro-cli" target="_blank" rel="noopener noreferrer">Node.js CLI (NPM)</a>
|
||||
<Link href="/developers">Chrome Extension (Soon)</Link>
|
||||
<a href={siteConfig.chromeWebStoreUrl} target="_blank" rel="noopener noreferrer">Chrome Extension</a>
|
||||
<a href="https://github.com/knuthtimo-lab/greenlenspro-cli" target="_blank" rel="noopener noreferrer">GitHub Action & Repository</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
525
greenlns-landing/components/blog/BlogPostTemplate.tsx
Normal file
525
greenlns-landing/components/blog/BlogPostTemplate.tsx
Normal file
@@ -0,0 +1,525 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import SeoNavbar from '@/components/seo/SeoNavbar'
|
||||
import SeoFooter from '@/components/seo/SeoFooter'
|
||||
import { BlogPost } from '@/lib/blogPosts'
|
||||
|
||||
const SITE = 'https://greenlenspro.com'
|
||||
|
||||
/**
|
||||
* JSON-LD für Blogposts. BlogPosting liefert Autor und Datum, FAQPage und HowTo
|
||||
* sind die beiden Formate, aus denen AI-Antwortsysteme direkt extrahieren.
|
||||
* Ohne diese Blöcke bleibt der Beitrag für sie unstrukturierter Fließtext.
|
||||
*/
|
||||
function buildBlogSchema(post: BlogPost) {
|
||||
const graph: Record<string, unknown>[] = [
|
||||
{
|
||||
'@type': 'BlogPosting',
|
||||
'@id': `${SITE}${post.canonical}#article`,
|
||||
headline: post.title,
|
||||
description: post.metaDescription,
|
||||
image: `${SITE}${post.heroImage}`,
|
||||
datePublished: post.publishedAtIso,
|
||||
dateModified: post.updatedAtIso || post.publishedAtIso,
|
||||
inLanguage: post.locale,
|
||||
mainEntityOfPage: { '@type': 'WebPage', '@id': `${SITE}${post.canonical}` },
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: post.author.name,
|
||||
jobTitle: post.author.role,
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'GreenLens',
|
||||
url: SITE,
|
||||
},
|
||||
...(post.sources?.length
|
||||
? { citation: post.sources.map((source) => ({ '@type': 'CreativeWork', name: source.title, url: source.url })) }
|
||||
: {}),
|
||||
},
|
||||
]
|
||||
|
||||
if (post.faqs?.length) {
|
||||
graph.push({
|
||||
'@type': 'FAQPage',
|
||||
'@id': `${SITE}${post.canonical}#faq`,
|
||||
mainEntity: post.faqs.map((faq) => ({
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: { '@type': 'Answer', text: faq.answer },
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
if (post.howToSteps?.length) {
|
||||
graph.push({
|
||||
'@type': 'HowTo',
|
||||
'@id': `${SITE}${post.canonical}#howto`,
|
||||
name: post.howToName || post.title,
|
||||
step: post.howToSteps.map((step, index) => ({
|
||||
'@type': 'HowToStep',
|
||||
position: index + 1,
|
||||
name: step.name.replace(/^\d+\.\s*/, ''),
|
||||
text: step.text,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
return { '@context': 'https://schema.org', '@graph': graph }
|
||||
}
|
||||
|
||||
function renderFormattedText(text: string) {
|
||||
if (!text) return null
|
||||
const regex = /\[([^\]]+)\]\(([^)]+)\)/g
|
||||
const parts: React.ReactNode[] = []
|
||||
let lastIndex = 0
|
||||
let match: RegExpExecArray | null
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
parts.push(text.substring(lastIndex, match.index))
|
||||
}
|
||||
const linkText = match[1]
|
||||
const url = match[2]
|
||||
const isExternal = url.startsWith('http')
|
||||
if (isExternal) {
|
||||
parts.push(
|
||||
<a
|
||||
key={match.index}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-emerald-700 underline underline-offset-2 hover:text-emerald-900 font-semibold"
|
||||
>
|
||||
{linkText}
|
||||
</a>
|
||||
)
|
||||
} else {
|
||||
parts.push(
|
||||
<Link
|
||||
key={match.index}
|
||||
href={url}
|
||||
className="text-emerald-700 underline underline-offset-2 hover:text-emerald-900 font-semibold"
|
||||
>
|
||||
{linkText}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
lastIndex = regex.lastIndex
|
||||
}
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
parts.push(text.substring(lastIndex))
|
||||
}
|
||||
|
||||
return parts.length > 0 ? parts : text
|
||||
}
|
||||
|
||||
export function BlogPostTemplate({ post }: { post: BlogPost }) {
|
||||
return (
|
||||
<div className="bg-[#fdfbf6] text-on-surface font-body antialiased min-h-screen relative overflow-hidden">
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(buildBlogSchema(post)) }}
|
||||
/>
|
||||
|
||||
{/* Navigation Header */}
|
||||
<SeoNavbar locale="en" />
|
||||
|
||||
{/* Decorative Green Glow 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-[#c2e5cc] blur-[120px] rounded-full mix-blend-multiply opacity-70" />
|
||||
<div className="absolute top-[20%] right-[-5%] w-[50vw] h-[50vw] max-w-[600px] max-h-[600px] bg-[#e5f0cb] blur-[100px] rounded-full mix-blend-multiply opacity-80" />
|
||||
<div className="absolute top-[50%] left-[10%] w-[40vw] h-[40vw] max-w-[500px] max-h-[500px] bg-[#a8e0b8] blur-[110px] rounded-full mix-blend-multiply opacity-60" />
|
||||
</div>
|
||||
|
||||
{/* Main Container */}
|
||||
<main className="pt-28 pb-20">
|
||||
{/* Header / Breadcrumb & Meta */}
|
||||
<header className="py-12 bg-white/80 backdrop-blur-md border-b border-emerald-900/10 shadow-xs">
|
||||
<div className="max-w-[1320px] mx-auto px-6">
|
||||
{/* Breadcrumbs */}
|
||||
<nav className="flex items-center gap-2 text-xs text-emerald-800/70 mb-6 font-semibold tracking-wide">
|
||||
<Link href="/" className="hover:text-emerald-700 transition-colors">
|
||||
Home
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<Link href="/blog" className="hover:text-emerald-700 transition-colors">
|
||||
Blog
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-emerald-700 font-bold">{post.category}</span>
|
||||
</nav>
|
||||
|
||||
{/* Category & Read Time */}
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<span className="bg-emerald-600/10 text-emerald-800 border border-emerald-600/20 text-xs font-extrabold tracking-wider uppercase px-3.5 py-1 rounded-full shadow-xs">
|
||||
🌿 {post.category}
|
||||
</span>
|
||||
<span className="text-xs text-emerald-900/70 font-semibold">
|
||||
• {post.readTime}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Title & Subtitle */}
|
||||
<h1 className="font-display text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-emerald-950 mb-5 leading-[1.15]">
|
||||
{post.title}
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl text-emerald-900/80 leading-relaxed max-w-[850px] mb-8 font-medium">
|
||||
{post.subtitle}
|
||||
</p>
|
||||
|
||||
{/* Author Info */}
|
||||
<div className="flex items-center gap-4 pt-6 border-t border-emerald-900/10">
|
||||
<div className="w-12 h-12 rounded-full bg-emerald-600 text-white font-bold text-lg flex items-center justify-center shadow-md border-2 border-emerald-400">
|
||||
🌱
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-sm text-emerald-950">{post.author.name}</div>
|
||||
<div className="text-xs text-emerald-700 font-medium">
|
||||
{post.author.role} • Updated {post.publishedAt}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Content & Sidebar Grid */}
|
||||
<div className="max-w-[1320px] mx-auto px-6 pt-10 grid grid-cols-1 lg:grid-cols-12 gap-12">
|
||||
{/* Main Article Content */}
|
||||
<div className="lg:col-span-8">
|
||||
{/* Featured Hero Image */}
|
||||
<div className="relative w-full h-[360px] sm:h-[450px] rounded-2xl overflow-hidden shadow-2xl mb-10 border-2 border-emerald-900/10 group">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={post.heroImage}
|
||||
alt={post.heroImageAlt}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Dynamic Hero Overlay Badge */}
|
||||
<div className="absolute bottom-6 left-6 right-6 sm:right-auto max-w-[350px] bg-white/95 backdrop-blur-md p-4 rounded-xl border-2 border-emerald-600/30 flex items-center gap-3 shadow-2xl">
|
||||
<div className="w-2 h-10 bg-emerald-600 rounded-full shrink-0"></div>
|
||||
<div>
|
||||
<div className="font-display font-bold text-sm text-emerald-950 mb-0.5">
|
||||
{post.heroImageBadgeTitle}
|
||||
</div>
|
||||
<div className="text-[10px] font-extrabold tracking-[0.15em] uppercase text-emerald-700">
|
||||
{post.heroImageBadgeLabel}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Direct Answer Summary Callout Box (AI-Overview Ready) */}
|
||||
{post.directAnswer && (
|
||||
<div className="bg-emerald-500/10 border-l-4 border-emerald-600 p-6 rounded-r-2xl mb-10 shadow-sm border-y border-r border-emerald-600/20">
|
||||
<div className="flex items-center gap-2 text-emerald-800 font-extrabold text-xs uppercase tracking-wider mb-2">
|
||||
<span>⚡ Quick Botanical Answer</span>
|
||||
</div>
|
||||
<p className="text-emerald-950 font-medium leading-relaxed text-base max-w-[72ch]">
|
||||
{renderFormattedText(post.directAnswer)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile TOC */}
|
||||
{post.toc && post.toc.length > 0 && (
|
||||
<div className="lg:hidden bg-white p-6 rounded-2xl border-2 border-emerald-600/20 mb-10 shadow-sm">
|
||||
<div className="font-extrabold text-xs text-emerald-800 uppercase tracking-widest mb-3">
|
||||
Table of Contents
|
||||
</div>
|
||||
<ul className="space-y-2.5 text-sm font-medium">
|
||||
{post.toc.map((item) => (
|
||||
<li key={item.id}>
|
||||
<a href={`#${item.id}`} className="text-emerald-700 hover:text-emerald-900 hover:underline">
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Body Sections */}
|
||||
<div className="space-y-12 text-emerald-950 text-base leading-relaxed">
|
||||
{post.sections.map((section, sIdx) => (
|
||||
<React.Fragment key={section.id}>
|
||||
<section id={section.id} className="scroll-mt-28">
|
||||
{section.eyebrow && (
|
||||
<div className="text-xs font-extrabold tracking-widest text-emerald-700 uppercase mb-1">
|
||||
{section.eyebrow}
|
||||
</div>
|
||||
)}
|
||||
<h2 className="font-display text-2xl sm:text-3xl font-bold text-emerald-950 mb-4">
|
||||
{section.title}
|
||||
</h2>
|
||||
<p className="text-emerald-900/80 text-base sm:text-lg leading-relaxed mb-6 font-normal max-w-[68ch]">
|
||||
{renderFormattedText(section.body)}
|
||||
</p>
|
||||
|
||||
{/* Bullets */}
|
||||
{section.bullets && section.bullets.length > 0 && (
|
||||
<ul className="space-y-3 mb-6 bg-white p-6 rounded-2xl border border-emerald-600/20 shadow-xs">
|
||||
{section.bullets.map((bullet, idx) => (
|
||||
<li key={idx} className="flex items-start gap-3 text-sm sm:text-base text-emerald-900">
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-emerald-600 mt-2 shrink-0 shadow-xs" />
|
||||
<span className="max-w-[76ch]">{renderFormattedText(bullet)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
{section.table && (
|
||||
<div className="overflow-x-auto mb-8 rounded-2xl border-2 border-emerald-600/20 shadow-sm bg-white">
|
||||
<table className="w-full text-left border-collapse text-sm">
|
||||
<thead className="bg-emerald-900 text-white font-bold">
|
||||
<tr>
|
||||
{section.table.headers.map((h, i) => (
|
||||
<th key={i} className="p-3.5 sm:p-4 border-b border-emerald-800">
|
||||
{h}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-emerald-600/10">
|
||||
{section.table.rows.map((row, rIdx) => (
|
||||
<tr key={rIdx} className="hover:bg-emerald-500/5 transition-colors">
|
||||
{row.map((cell, cIdx) => (
|
||||
<td
|
||||
key={cIdx}
|
||||
className={`p-3.5 sm:p-4 text-emerald-900 ${
|
||||
cIdx === 0 ? 'font-bold text-emerald-950' : ''
|
||||
}`}
|
||||
>
|
||||
{renderFormattedText(cell)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Callout */}
|
||||
{section.callout && (
|
||||
<div
|
||||
className={`p-5 rounded-2xl border-2 mb-6 ${
|
||||
section.callout.type === 'warning'
|
||||
? 'bg-amber-500/10 border-amber-500/30 text-amber-950'
|
||||
: 'bg-emerald-500/10 border-emerald-600/30 text-emerald-950'
|
||||
}`}
|
||||
>
|
||||
<div className="font-bold text-sm mb-1">{section.callout.title}</div>
|
||||
<div className="text-sm opacity-90">{renderFormattedText(section.callout.body)}</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Mid-Content Conversion CTA Widget after 2nd section */}
|
||||
{sIdx === 1 && (
|
||||
<div className="my-10 p-6 sm:p-8 rounded-2xl bg-gradient-to-br from-emerald-900 via-emerald-950 to-ink text-white border-2 border-emerald-400/30 shadow-xl flex flex-col sm:flex-row items-center justify-between gap-6">
|
||||
<div className="space-y-1.5 text-center sm:text-left">
|
||||
<div className="flex items-center justify-center sm:justify-start gap-2 text-xs font-extrabold tracking-widest text-emerald-400 uppercase">
|
||||
<span>🌿 Instant AI Health Diagnosis</span>
|
||||
</div>
|
||||
<h3 className="font-display text-xl sm:text-2xl font-bold text-white">
|
||||
Unsure what is wrong with your plant?
|
||||
</h3>
|
||||
<p className="text-white/80 text-sm max-w-[480px]">
|
||||
Scan your plant leaf with GreenLens AI for an immediate disease check, pest identification, and custom care plan.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
className="shrink-0 bg-emerald-500 hover:bg-emerald-400 text-ink font-bold px-6 py-3.5 rounded-full text-sm shadow-xl hover:scale-105 transition-all duration-200"
|
||||
>
|
||||
Start Free Scan ➔
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
{/* HowTo Step Guide */}
|
||||
{post.howToSteps && post.howToSteps.length > 0 && (
|
||||
<section
|
||||
id="howto-step"
|
||||
className="scroll-mt-28 bg-white p-6 sm:p-8 rounded-2xl border-2 border-emerald-600/20 shadow-md"
|
||||
>
|
||||
<div className="text-xs font-extrabold tracking-widest text-emerald-700 uppercase mb-2">
|
||||
Action Plan
|
||||
</div>
|
||||
<h2 className="font-display text-2xl font-bold text-emerald-950 mb-6">
|
||||
{post.howToName ?? 'Step-by-Step Action Guide'}
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
{post.howToSteps.map((step, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-start gap-4 p-4.5 rounded-xl bg-emerald-50/50 border border-emerald-600/20"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-full bg-emerald-600 text-white font-bold flex items-center justify-center shrink-0 text-sm shadow-sm">
|
||||
{idx + 1}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-emerald-950 text-base mb-1">{step.name}</div>
|
||||
<div className="text-emerald-900/80 text-sm">{step.text}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* FAQs */}
|
||||
{post.faqs && post.faqs.length > 0 && (
|
||||
<section id="faq" className="scroll-mt-28 pt-8 border-t-2 border-emerald-600/20">
|
||||
<div className="text-xs font-extrabold tracking-widest text-emerald-700 uppercase mb-2">
|
||||
Questions & Answers
|
||||
</div>
|
||||
<h2 className="font-display text-2xl sm:text-3xl font-bold text-emerald-950 mb-6">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
{post.faqs.map((faq, idx) => (
|
||||
<div key={idx} className="p-6 rounded-2xl bg-white border border-emerald-600/20 shadow-xs">
|
||||
<h3 className="font-bold text-base sm:text-lg text-emerald-950 mb-2">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<p className="text-emerald-900/80 text-sm sm:text-base leading-relaxed">
|
||||
{faq.answer}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* App Banner */}
|
||||
<div className="p-8 sm:p-10 rounded-3xl bg-gradient-to-br from-emerald-900 via-emerald-950 to-ink text-white border-2 border-emerald-400/30 text-center shadow-2xl my-12 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-64 h-64 bg-emerald-500/10 blur-3xl rounded-full pointer-events-none" />
|
||||
<span className="text-xs font-extrabold tracking-widest uppercase text-emerald-400 block mb-2">
|
||||
GreenLens Pro iOS App
|
||||
</span>
|
||||
<h3 className="font-display text-2xl sm:text-3xl font-bold mt-1 mb-3 text-white">
|
||||
Identify Any Plant & Get Custom Care Plans
|
||||
</h3>
|
||||
<p className="text-white/80 text-sm sm:text-base max-w-[600px] mx-auto mb-6 leading-relaxed">
|
||||
Scan your houseplants in seconds, diagnose leaf diseases, and get automatic watering reminders tailored to your space.
|
||||
</p>
|
||||
<a
|
||||
href="https://apps.apple.com/app/greenlens"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 bg-emerald-500 hover:bg-emerald-400 text-ink font-bold px-7 py-3.5 rounded-full shadow-xl hover:scale-105 transition-all duration-200"
|
||||
>
|
||||
<span>Start First Scan Free — No Account Needed</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className="lg:col-span-4 hidden lg:block">
|
||||
<div className="sticky top-28 space-y-8">
|
||||
{/* Table of Contents */}
|
||||
{post.toc && post.toc.length > 0 && (
|
||||
<div className="bg-white p-6 rounded-2xl border-2 border-emerald-600/20 shadow-sm">
|
||||
<div className="font-extrabold text-xs uppercase tracking-widest text-emerald-800 mb-4">
|
||||
Table of Contents
|
||||
</div>
|
||||
<nav className="space-y-2 text-sm font-medium">
|
||||
{post.toc.map((item) => (
|
||||
<a
|
||||
key={item.id}
|
||||
href={`#${item.id}`}
|
||||
className="block text-emerald-900/70 hover:text-emerald-700 transition-colors py-1 border-l-2 border-transparent hover:border-emerald-600 pl-3"
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Related Articles */}
|
||||
{post.relatedPosts && post.relatedPosts.length > 0 && (
|
||||
<div className="bg-white p-6 rounded-2xl border-2 border-emerald-600/20 shadow-sm">
|
||||
<div className="font-extrabold text-xs uppercase tracking-widest text-emerald-800 mb-4">
|
||||
Related Guides
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{post.relatedPosts.map((related) => (
|
||||
<Link
|
||||
key={related.slug}
|
||||
href={`/blog/${related.slug}`}
|
||||
className="group block p-3.5 rounded-xl hover:bg-emerald-50 border border-transparent hover:border-emerald-600/20 transition-all"
|
||||
>
|
||||
<div className="text-[10px] font-extrabold text-emerald-700 uppercase tracking-wider mb-1">
|
||||
{related.category} • {related.readTime}
|
||||
</div>
|
||||
<div className="font-bold text-sm text-emerald-950 group-hover:text-emerald-700 transition-colors line-clamp-2 leading-snug">
|
||||
{related.title}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{/* Methodik & Quellen — Vertrauenssignal, bewusst am Ende und vollständig sichtbar */}
|
||||
{(post.methodology || post.sources?.length) && (
|
||||
<div className="max-w-[1320px] mx-auto px-6 mt-16 lg:pr-[calc(33.333%+3rem)]">
|
||||
<section className="bg-white/80 border border-emerald-900/10 rounded-2xl p-7">
|
||||
{post.methodology && (
|
||||
<>
|
||||
<h2 className="text-sm font-extrabold uppercase tracking-widest text-emerald-800 mb-3">
|
||||
How this guide was compiled
|
||||
</h2>
|
||||
<p className="text-[15px] leading-relaxed text-emerald-950/80">{post.methodology}</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{post.sources && post.sources.length > 0 && (
|
||||
<>
|
||||
<h2 className="text-sm font-extrabold uppercase tracking-widest text-emerald-800 mt-7 mb-3">
|
||||
Sources
|
||||
</h2>
|
||||
<ul className="space-y-2">
|
||||
{post.sources.map((source) => (
|
||||
<li key={source.url} className="text-[15px] leading-relaxed">
|
||||
<a
|
||||
href={source.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-emerald-700 underline underline-offset-2 hover:text-emerald-900"
|
||||
>
|
||||
{source.title}
|
||||
</a>
|
||||
<span className="text-emerald-950/60"> — {source.publisher}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
|
||||
<p className="text-xs text-emerald-950/50 mt-6">
|
||||
Published {post.publishedAt}
|
||||
{post.updatedAt ? ` · Last updated ${post.updatedAt}` : ''} · Written by {post.author.name},{' '}
|
||||
{post.author.role}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* Site Footer */}
|
||||
<SeoFooter locale="en" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -177,7 +177,7 @@ export default function SeoFooter({ locale, showMethodology = true }: SeoFooterP
|
||||
<li><a href="https://marketplace.visualstudio.com/items?itemName=TimoKnuth.greenlens-vscode" target="_blank" rel="noopener noreferrer" className="hover:text-white">VS Code Extension</a></li>
|
||||
<li><a href="https://pypi.org/project/greenlens-python/" target="_blank" rel="noopener noreferrer" className="hover:text-white">Python SDK & CLI (PyPI)</a></li>
|
||||
<li><a href="https://www.npmjs.com/package/greenlenspro-cli" target="_blank" rel="noopener noreferrer" className="hover:text-white">Node.js CLI (NPM)</a></li>
|
||||
<li><Link href="/developers" className="hover:text-white">Chrome Extension (Soon)</Link></li>
|
||||
<li><a href={siteConfig.chromeWebStoreUrl} target="_blank" rel="noopener noreferrer" className="hover:text-white">Chrome Extension</a></li>
|
||||
<li><a href="https://github.com/knuthtimo-lab/greenlenspro-cli#github-action" target="_blank" rel="noopener noreferrer" className="hover:text-white">GitHub Action</a></li>
|
||||
<li><a href="https://github.com/knuthtimo-lab/greenlenspro-cli" target="_blank" rel="noopener noreferrer" className="hover:text-white">GitHub Repository</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -74,8 +74,8 @@ export function SeoHero({
|
||||
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 w-[90%] bg-surface/95 backdrop-blur p-4 rounded-xl border border-surface-container-highest flex items-center gap-3 shadow-xl">
|
||||
<div className="w-1.5 h-8 bg-primary rounded-full"></div>
|
||||
<div>
|
||||
<div className="font-display font-semibold text-sm text-primary mb-0.5">{copy.hero.floatTitle}</div>
|
||||
<div className="text-[10px] font-semibold tracking-[0.1em] uppercase text-on-surface-variant">{copy.hero.floatLabel}</div>
|
||||
<div className="font-display font-semibold text-sm text-primary mb-0.5">{profile.heroImageBadgeTitle ?? copy.hero.floatTitle}</div>
|
||||
<div className="text-[10px] font-semibold tracking-[0.1em] uppercase text-on-surface-variant">{profile.heroImageBadgeLabel ?? copy.hero.floatLabel}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
3633
greenlns-landing/lib/blogPosts.ts
Normal file
3633
greenlns-landing/lib/blogPosts.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,11 @@ const routeGroups: Partial<Record<Lang, string>>[] = [
|
||||
{ de: '/pflanzen-erkennen-kostenlos', en: '/best-plant-identification-app' },
|
||||
{ en: '/plant-scanner', es: '/es/escaner-de-plantas' },
|
||||
{ de: '/vs/google-lens', es: '/es/comparar/google-lens' },
|
||||
// Ratgeber-Cluster: deutsche SEO-Seite und englischer Blogpost sind echte
|
||||
// Übersetzungspaare, auch wenn die Templates unterschiedlich sind.
|
||||
{ de: '/giessplan-zimmerpflanzen', en: '/blog/houseplant-watering-schedule' },
|
||||
{ de: '/pflanzen-diagnose', en: '/blog/plant-health-diagnosis-guide' },
|
||||
{ de: '/welche-pflanze-ist-das', en: '/blog/which-plant-is-this-guide' },
|
||||
]
|
||||
|
||||
const fuzzyTargets: Record<string, Partial<Record<Lang, string>>> = {
|
||||
@@ -68,7 +73,7 @@ const englishPaths = new Set([
|
||||
export function getLocaleForPath(pathname: string): Lang {
|
||||
if (pathname === '/es' || pathname.startsWith('/es/')) return 'es'
|
||||
if (pathname === '/vs/google-lens') return 'de'
|
||||
if (englishPaths.has(pathname) || pathname.startsWith('/vs/')) return 'en'
|
||||
if (englishPaths.has(pathname) || pathname.startsWith('/vs/') || pathname.startsWith('/blog')) return 'en'
|
||||
return 'de'
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,23 @@
|
||||
const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || 'https://greenlenspro.com').trim()
|
||||
|
||||
export const siteConfig = {
|
||||
name: 'GreenLens',
|
||||
domain: siteUrl,
|
||||
const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || 'https://greenlenspro.com').trim()
|
||||
|
||||
export const siteConfig = {
|
||||
name: 'GreenLens',
|
||||
domain: siteUrl,
|
||||
supportEmail: 'timo@greenlenspro.com',
|
||||
legalEmail: 'timo@greenlenspro.com',
|
||||
iosAppStoreUrl: 'https://apps.apple.com/de/app/greenlens-pro/id6759843546?l=en-GB',
|
||||
androidPlayStoreUrl: '',
|
||||
company: {
|
||||
legalName: 'GreenLens',
|
||||
representative: 'Tim Knuth',
|
||||
addressLine1: '',
|
||||
addressLine2: '',
|
||||
country: 'Germany',
|
||||
registry: '',
|
||||
vatId: '',
|
||||
},
|
||||
} as const
|
||||
|
||||
export const hasIosStoreUrl = siteConfig.iosAppStoreUrl.trim().length > 0
|
||||
export const hasAndroidStoreUrl = siteConfig.androidPlayStoreUrl.trim().length > 0
|
||||
iosAppStoreUrl: 'https://apps.apple.com/de/app/greenlens-pro/id6759843546?l=en-GB',
|
||||
androidPlayStoreUrl: '',
|
||||
chromeWebStoreUrl: 'https://chromewebstore.google.com/detail/mhjlfgkdckkimpjeifippefjhalcafjg',
|
||||
company: {
|
||||
legalName: 'GreenLens',
|
||||
representative: 'Tim Knuth',
|
||||
addressLine1: '',
|
||||
addressLine2: '',
|
||||
country: 'Germany',
|
||||
registry: '',
|
||||
vatId: '',
|
||||
},
|
||||
} as const
|
||||
|
||||
export const hasIosStoreUrl = siteConfig.iosAppStoreUrl.trim().length > 0
|
||||
export const hasAndroidStoreUrl = siteConfig.androidPlayStoreUrl.trim().length > 0
|
||||
|
||||
@@ -89,6 +89,11 @@ export const spanishSeoPageProfiles: Record<string, SeoPageProfile> = {
|
||||
label: 'Diagnosticar enfermedades de plantas',
|
||||
description: 'Analiza sintomas y recibe el siguiente paso mas seguro.',
|
||||
},
|
||||
{
|
||||
href: '/es/como-funciona-el-reconocimiento-de-plantas-por-ia',
|
||||
label: 'Como funciona el reconocimiento de plantas por IA',
|
||||
description: 'Entiende que hay detras del escaneo antes de confiar en el resultado.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -175,6 +180,11 @@ export const spanishSeoPageProfiles: Record<string, SeoPageProfile> = {
|
||||
label: 'Diagnostico de plantas',
|
||||
description: 'Cuando una planta muestra sintomas, revisa la causa probable.',
|
||||
},
|
||||
{
|
||||
href: '/es/plantas-de-interior-poca-luz',
|
||||
label: 'Plantas de interior con poca luz',
|
||||
description: 'Elige especies que toleren el rincon mas oscuro de tu casa.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -436,6 +446,369 @@ export const spanishSeoPageProfiles: Record<string, SeoPageProfile> = {
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
'como-funciona-el-reconocimiento-de-plantas-por-ia': {
|
||||
slug: 'como-funciona-el-reconocimiento-de-plantas-por-ia',
|
||||
locale: 'es',
|
||||
metaTitle: '¿Cómo Funciona Realmente el Reconocimiento de Plantas por IA?',
|
||||
metaDescription:
|
||||
'¿Cómo identifica una app la especie de una planta a partir de una sola foto? Explicamos cómo funciona la IA detrás del reconocimiento de plantas, qué tan precisa es realmente y cómo sacar el mejor resultado en cada escaneo.',
|
||||
canonical: '/es/como-funciona-el-reconocimiento-de-plantas-por-ia',
|
||||
h1: '¿Cómo Funciona Realmente el Reconocimiento de Plantas por IA?',
|
||||
tagline: 'La IA acierta el nombre en segundos. Lo dificil es saber cuando conviene dudar de ella.',
|
||||
directAnswer:
|
||||
'Las apps de reconocimiento de plantas usan redes neuronales convolucionales entrenadas con millones de fotos etiquetadas para comparar tu imagen con miles de especies y devolver una lista de coincidencias con un porcentaje de confianza. Con una foto nitida de una especie comun, los modelos bien entrenados superan el 95% de precision.',
|
||||
definitionBlock:
|
||||
'El reconocimiento de plantas por IA usa una rama del aprendizaje profundo llamada Redes Neuronales Convolucionales (CNN). El modelo detecta y pondera caracteristicas visuales como la forma de la hoja, el margen, la venacion, la textura y la estructura floral, y las compara contra millones de imagenes de entrenamiento para devolver las especies mas probables.',
|
||||
lastUpdated: 'Agosto 2026',
|
||||
lastUpdatedIso: '2026-08-05',
|
||||
includeAppSchema: true,
|
||||
heroImage: '/hero-image.png',
|
||||
heroImageAlt:
|
||||
'Persona fotografiando una hoja con el movil para identificarla mediante inteligencia artificial',
|
||||
contentSections: [
|
||||
{
|
||||
eyebrow: 'Como funciona',
|
||||
title: 'Que pasa en los dos segundos tras la foto',
|
||||
body:
|
||||
'La IA no "ve" la planta como tu. Detecta y pondera cientos de caracteristicas visuales de forma simultanea, entrenadas sobre imagenes de jardines botanicos, bases de datos de ciencia ciudadana como iNaturalist y herbarios digitales.',
|
||||
bullets: [
|
||||
'Forma de la hoja: lobulada, ovalada, lanceolada, palmeada, lineal.',
|
||||
'Margen foliar: liso, serrado, ondulado, dentado.',
|
||||
'Venacion: pinnada, palmeada, paralela.',
|
||||
'Textura superficial, distribucion del color y estructura floral, si existe.',
|
||||
'El modelo devuelve una lista de las 3 a 5 especies mas probables, cada una con un porcentaje de confianza.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Gratis vs. de pago',
|
||||
title: 'Que suele incluir cada nivel',
|
||||
body:
|
||||
'La identificacion gratuita cubre la mayoria de los usos cotidianos. Esto es lo que suele estar en cada nivel, en la categoria en general.',
|
||||
bullets: [
|
||||
'Gratis: nombre comun y cientifico, resumen basico de cuidados, aviso de toxicidad y acceso a foros.',
|
||||
'De pago: diagnostico de enfermedades por foto, escaneos ilimitados, identificacion detallada de plagas, diario de crecimiento y modo sin conexion.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Casos de uso',
|
||||
title: '5 situaciones donde esta tecnologia marca la diferencia',
|
||||
body: 'Del regalo sin etiqueta a la compra en el vivero, estas son las situaciones donde escanear ahorra tiempo y evita errores.',
|
||||
bullets: [
|
||||
'La planta misteriosa: sin nombre ni etiqueta, el escaneo te da la especie y sus necesidades basicas al instante.',
|
||||
'"¿Es venenosa?": consulta la ficha de toxicidad de inmediato, aunque conviene llamar siempre al centro de toxicologia ante cualquier duda real.',
|
||||
'Identificacion en rutas y naturaleza: ayuda a distinguir bayas o plantas invasoras, pero verifica siempre con una segunda fuente antes de tocar o consumir algo silvestre.',
|
||||
'Compras mas inteligentes en el vivero: escanea el ejemplar del mostrador antes de comprarlo para saber si se adapta a tu espacio.',
|
||||
'Aprender con los ninos: convierte un paseo en un juego de identificacion natural.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Consejos practicos',
|
||||
title: 'Como hacer la foto perfecta para maxima precision',
|
||||
body: 'La precision es cosa de dos: la IA solo puede trabajar con lo que le das.',
|
||||
bullets: [
|
||||
'Fotografia una sola hoja bien desarrollada, con luz natural y sin flash, llenando al menos el 50% del encuadre.',
|
||||
'Un fondo blanco liso y una flor visible, si la hay, mejoran mucho la precision.',
|
||||
'Evita vistas aereas de toda la planta, contraluz, plantulas muy jovenes, capturas de pantalla y hojas muy enfermas o necroticas.',
|
||||
'Si la confianza es baja, prueba con el enves de la hoja, una seccion del tallo o el cepellon si la planta esta recien trasplantada.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Comparativa',
|
||||
title: 'Reconocimiento por IA vs. Google Lens',
|
||||
body: 'Google Lens tambien identifica plantas, y es gratis. La diferencia esta en lo que ocurre despues del nombre.',
|
||||
bullets: [
|
||||
'Precision de especie: Google Lens usa conocimiento general; una app dedicada usa un conjunto de datos botanico especializado.',
|
||||
'Instrucciones de cuidado y diagnostico de enfermedades: ausentes en Google Lens, disponibles justo despues del escaneo en una app especializada.',
|
||||
'Comprobacion de toxicidad: indirecta via busqueda web en Google Lens, directa en los resultados de una app dedicada.',
|
||||
'Si solo quieres un nombre ocasional, Google Lens basta. Si te importan tus plantas, una app especializada aporta mucho mas.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Limites',
|
||||
title: 'Las plantas mas dificiles de identificar',
|
||||
body: 'Ninguna app es perfecta. Estas son las situaciones donde incluso la mejor tecnologia tiene dificultades.',
|
||||
bullets: [
|
||||
'Cultivares variegados: escanea la hoja mas "normal" de la planta.',
|
||||
'Plantulas: espera hasta que aparezcan las primeras hojas verdaderas.',
|
||||
'Especies muy raras: usa iNaturalist para identificacion comunitaria.',
|
||||
'Hojas muy enfermas: fotografia una hoja sana si esta disponible.',
|
||||
'Suculentas y cactus de aspecto similar: fotografia desde multiples angulos.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Siguiente frontera',
|
||||
title: 'Diagnostico de enfermedades de plantas',
|
||||
body: 'Identificar la especie es solo el comienzo. Las apps avanzadas tambien analizan sintomas visibles para sugerir la causa mas probable.',
|
||||
bullets: [
|
||||
'Hojas amarillas: exceso de riego, falta de agua, deficiencia nutricional o dano por plagas.',
|
||||
'Puntas marrones: baja humedad, toxicidad por fluor o falta de riego.',
|
||||
'Polvo blanco: oidio (hongos) o depositos minerales del agua dura.',
|
||||
'Residuo pegajoso: melaza de pulgones o cochinillas.',
|
||||
],
|
||||
},
|
||||
],
|
||||
featureTable: {
|
||||
title: 'Que distingue a GreenLens de un reconocimiento de plantas generico',
|
||||
alternativeLabel: 'Apps de identificacion genericas',
|
||||
rows: [
|
||||
{
|
||||
feature: 'Precision por especie',
|
||||
greenlens: 'Modelo apoyado en un conjunto de datos botanico especializado, con buen rendimiento en especies comunes.',
|
||||
alternative: 'Motores de proposito general con menos precision en flora.',
|
||||
},
|
||||
{
|
||||
feature: 'Despues del escaneo',
|
||||
greenlens: 'Conecta el resultado con cuidado, riego y diagnostico de salud en el mismo flujo.',
|
||||
alternative: 'Suele terminar en el nombre de la especie.',
|
||||
},
|
||||
{
|
||||
feature: 'Catalogo de cuidado',
|
||||
greenlens: 'Fichas verificadas a mano para unas 240 especies, en crecimiento cada mes.',
|
||||
alternative: 'Presume reconocer miles de especies, pero sin fichas de cuidado verificadas.',
|
||||
},
|
||||
],
|
||||
},
|
||||
greenLensIf: [
|
||||
'Quieres saber que planta tienes delante y como cuidarla, no solo su nombre.',
|
||||
'Te interesa entender que hace la IA detras del escaneo antes de confiar en el resultado.',
|
||||
'Buscas una segunda opinion rapida antes de comprar una planta en el vivero.',
|
||||
],
|
||||
notBestIf: [
|
||||
'Necesitas identificacion botanica profesional certificada para fines cientificos.',
|
||||
'Quieres verificar si una planta silvestre es segura para comer — consulta siempre una guia de campo o un experto, nunca solo una app.',
|
||||
],
|
||||
offer: {
|
||||
eyebrow: 'Pruebalo primero',
|
||||
title: 'Escanea y compruebalo tu mismo',
|
||||
body:
|
||||
'Fotografia una hoja y mira lo que recibes antes de decidir nada: nombre de la especie, cuidados y una revision de salud si la planta muestra sintomas. Sin cuenta y sin datos de pago.',
|
||||
terms:
|
||||
'Un escaneo gratis, sin crear cuenta. Tres mas despues de registrarte. A partir de ahi necesitas GreenLens Pro: suscripcion mensual o anual, con los precios actuales en la App Store. Se renueva automaticamente y puedes cancelar cuando quieras desde los ajustes del iPhone.',
|
||||
ctaLabel: 'Haz tu primer escaneo gratis — sin cuenta',
|
||||
},
|
||||
faqs: [
|
||||
{
|
||||
question: '¿Qué apps ofrecen la mejor identificación gratuita de plantas?',
|
||||
answer:
|
||||
'GreenLens Pro, PlantNet e iNaturalist ofrecen una identificación gratuita potente. La precisión depende mucho de la calidad de la foto.',
|
||||
},
|
||||
{
|
||||
question: '¿Pueden las apps identificar plantas a partir de una foto de mi galería?',
|
||||
answer:
|
||||
'Sí. Todas las principales apps de identificación de plantas aceptan imágenes de tu galería, no solo fotos tomadas en directo.',
|
||||
},
|
||||
{
|
||||
question: '¿Cuántas plantas puede reconocer un identificador de plantas por IA?',
|
||||
answer:
|
||||
'Los motores genéricos presumen de reconocer entre 10.000 y 400.000+ especies a nivel básico, pero reconocer la forma de una hoja no es lo mismo que saber cómo mantener esa planta con vida. GreenLens ha construido a mano fichas de cuidado detalladas y comprobadas para unas 240 especies hasta ahora, y añade especies nuevas cada mes.',
|
||||
},
|
||||
{
|
||||
question: '¿Es seguro usar una app para identificar plantas silvestres comestibles?',
|
||||
answer:
|
||||
'Como primer paso, sí. Pero nunca consumas nada basándote únicamente en la identificación de una app: verifica siempre con una guía de campo o un experto.',
|
||||
},
|
||||
{
|
||||
question: '¿Funcionan las apps identificadoras de plantas sin conexión?',
|
||||
answer:
|
||||
'Algunas ofrecen modo sin conexión limitado para las especies más comunes. La cobertura completa normalmente requiere conexión a internet.',
|
||||
},
|
||||
],
|
||||
relatedLinks: [
|
||||
{
|
||||
href: '/es/identificador-de-plantas',
|
||||
label: 'Identificador de plantas',
|
||||
description: 'Escanea una planta y recibe su nombre junto con el plan de cuidado.',
|
||||
},
|
||||
{
|
||||
href: '/es/diagnosticar-enfermedades-plantas',
|
||||
label: 'Diagnosticar enfermedades de plantas',
|
||||
description: 'Cuando el problema no es el nombre sino un sintoma, empieza aqui.',
|
||||
},
|
||||
{
|
||||
href: '/es/plantas-de-interior-poca-luz',
|
||||
label: 'Plantas de interior con poca luz',
|
||||
description: 'Descubre que especies toleran rincones oscuros y como evaluarlo sin aparatos.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
'plantas-de-interior-poca-luz': {
|
||||
slug: 'plantas-de-interior-poca-luz',
|
||||
locale: 'es',
|
||||
metaTitle: 'Plantas de interior con poca luz — guia completa',
|
||||
metaDescription:
|
||||
'¿Qué plantas de interior necesitan poca luz? Descubre las 10 plantas más resistentes para habitaciones oscuras, sombra y pasillos, con consejos para evaluar la luz de tu casa.',
|
||||
canonical: '/es/plantas-de-interior-poca-luz',
|
||||
h1: 'Las 10 Mejores Plantas de Interior con Poca Luz',
|
||||
tagline: 'No toda casa tiene ventanales al sur — estas plantas no los necesitan.',
|
||||
directAnswer:
|
||||
'Zamioculca, sansevieria, aspidistra, potos y espatifilo estan entre las plantas de interior mas tolerantes a la poca luz: heredaron del sotobosque tropical la capacidad de funcionar con tasas de fotosintesis reducidas y sobrevivir con 300-800 lux, muy por debajo de los 2.000+ lux que necesitan la mayoria de las plantas de interior comunes.',
|
||||
definitionBlock:
|
||||
'Poca luz no significa sin luz: toda planta verde necesita fotones para hacer fotosintesis. En botanica la intensidad se mide en lux, y las plantas tolerantes a sombra evolucionaron bajo el dosel de los bosques tropicales para funcionar con 300-800 lux, un nivel mucho mas bajo que la luz indirecta brillante.',
|
||||
lastUpdated: 'Agosto 2026',
|
||||
lastUpdatedIso: '2026-08-05',
|
||||
includeAppSchema: true,
|
||||
heroImage: '/hero-plant.png',
|
||||
heroImageAlt:
|
||||
'Rincon de una habitacion con poca luz natural decorado con plantas de interior resistentes a la sombra',
|
||||
contentSections: [
|
||||
{
|
||||
eyebrow: 'Conceptos basicos',
|
||||
title: 'Que significa realmente "poca luz" para las plantas',
|
||||
body: 'Un error comun en jardineria de interior es pensar que "poca luz" equivale a "sin luz". Estos son los niveles orientativos, de mas a menos intensidad.',
|
||||
bullets: [
|
||||
'Luz indirecta brillante: mas de 2.000 lux — suculentas, ficus, cactus.',
|
||||
'Sombra media: 1.000-2.000 lux — monstera, potos, filodendro.',
|
||||
'Poca luz (tolerantes a sombra): 300-800 lux — sansevieria, zamioculca, cuna de Moises, aspidistra.',
|
||||
'Menos de 300 lux: sin una lampara de crecimiento LED, incluso las plantas mas resistentes terminan debilitandose.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'El top 10',
|
||||
title: 'Las 10 mejores plantas de interior para poca luz',
|
||||
body: 'De la zamioculca a la cinta, estas diez especies estan entre las mas resistentes a la sombra para interiores.',
|
||||
bullets: [
|
||||
'Zamioculca (Zamioculcas zamiifolia): 300-600 lux. Riega muy poco, en zonas sombrias cada 4 a 6 semanas.',
|
||||
'Sansevieria / lengua de suegra (Dracaena trifasciata): 400-800 lux. Evita el exceso de agua para prevenir pudricion de raices.',
|
||||
'Aspidistra (Aspidistra elatior): 300-700 lux. Ideal para pasillos frios y entradas sin sol directo.',
|
||||
'Potos (Epipremnum aureum): 500-1.000 lux. Excelente para estantes altos o macetas colgantes en zonas sombrias.',
|
||||
'Espatifilo / cuna de Moises (Spathiphyllum): 400-800 lux. Avisa que necesita agua inclinando sus hojas.',
|
||||
'Aglaonema: 500-800 lux. Las variedades verde oscuro toleran mejor la sombra que las rosadas o rojas.',
|
||||
'Palmera de salon (Chamaedorea elegans): 600-1.000 lux. Pulveriza sus hojas para mantener la humedad.',
|
||||
'Filodendro hoja de corazon (Philodendron hederaceum): 500-900 lux. Deja secar la capa superior de tierra entre riegos.',
|
||||
'Calatea (Calathea / Goeppertia): 600-1.000 lux. No tolera el sol directo pero exige alta humedad.',
|
||||
'Cinta / mala madre (Chlorophytum comosum): 500-1.200 lux. Muy adaptable y facil de reproducir por hijuelos.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Riego rapido',
|
||||
title: 'Luz y riego de un vistazo',
|
||||
body: 'Un resumen de la frecuencia de riego en verano e invierno para las especies mas populares de esta lista.',
|
||||
bullets: [
|
||||
'Zamioculca (300-600 lux): cada 3 semanas en verano, cada 5-6 semanas en invierno.',
|
||||
'Sansevieria (400-800 lux): cada 2-3 semanas en verano, cada 4 semanas en invierno.',
|
||||
'Aspidistra (300-700 lux): cada 2 semanas en verano, cada 3-4 semanas en invierno.',
|
||||
'Potos (500-1.000 lux): semanal en verano, cada 2 semanas en invierno.',
|
||||
'Espatifilo (400-800 lux): semanal en verano, cada 1-2 semanas en invierno.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Senales de alerta',
|
||||
title: 'Sintomas de falta de luz: como detectarla',
|
||||
body: 'Cuando una planta recibe menos luz de la que necesita, muestra senales claras.',
|
||||
bullets: [
|
||||
'Etiolacion: tallos alargados y debiles con gran espacio entre hojas, buscando la fuente de luz.',
|
||||
'Perdida de variegacion: las hojas veteadas se vuelven verde oscuro solido para maximizar la clorofila.',
|
||||
'Crecimiento estancado: la planta no produce hojas nuevas durante meses.',
|
||||
'Hojas amarillas o pudricion: la fotosintesis se ralentiza, la planta consume menos agua y el riego excesivo pudre las raices con mas facilidad.',
|
||||
],
|
||||
},
|
||||
{
|
||||
eyebrow: 'Sin aparatos',
|
||||
title: 'Como evaluar la luz de tu casa sin ningun aparato',
|
||||
body: 'No necesitas un luxometro: con un poco de observacion puedes averiguarlo tu mismo.',
|
||||
bullets: [
|
||||
'La prueba de la sombra: coloca la mano a 30 cm sobre la superficie al mediodia y observa la nitidez de la sombra.',
|
||||
'La orientacion de la ventana importa: sur da sol directo todo el dia, este luz matutina suave, oeste sol intenso por la tarde y norte la luz mas constante y suave, a menudo ideal para plantas tolerantes a sombra.',
|
||||
'La distancia a la ventana es tan importante como la orientacion: un rincon a 2 metros de una ventana luminosa ya puede estar en el rango de poca luz.',
|
||||
'La luz cambia con las estaciones: un lugar con luz media en verano puede convertirse en poca luz en invierno, asi que observa tus plantas durante el ano.',
|
||||
],
|
||||
},
|
||||
],
|
||||
featureTable: {
|
||||
title: 'Que aporta GreenLens a la hora de elegir planta segun la luz',
|
||||
alternativeLabel: 'Buscar por tu cuenta',
|
||||
rows: [
|
||||
{
|
||||
feature: 'Requisito de luz por especie',
|
||||
greenlens: 'Cada ficha del catalogo indica si la planta necesita poca luz, media/indirecta, indirecta brillante o sol directo.',
|
||||
alternative: 'Hay que buscar y contrastar varias fuentes para cada especie.',
|
||||
},
|
||||
{
|
||||
feature: 'Antes de comprar',
|
||||
greenlens: 'Escanea la planta en el vivero y comprueba si encaja con el rincon que tienes en mente.',
|
||||
alternative: 'Sueles decidir en la tienda sin verificar el requisito real de luz.',
|
||||
},
|
||||
{
|
||||
feature: 'Catalogo de cuidado',
|
||||
greenlens: 'Unas 240 especies con fichas verificadas a mano, en crecimiento cada mes.',
|
||||
alternative: 'Bases de datos genericas sin verificacion de cuidado real.',
|
||||
},
|
||||
],
|
||||
},
|
||||
greenLensIf: [
|
||||
'Tienes un rincon oscuro y no sabes que planta sobrevivira ahi.',
|
||||
'Quieres comprobar el requisito de luz de una planta antes de comprarla.',
|
||||
'Ya tienes plantas y sospechas que la falta de luz esta detras de su estancamiento.',
|
||||
],
|
||||
notBestIf: [
|
||||
'Buscas exclusivamente plantas de sol directo o de exterior.',
|
||||
'Necesitas mediciones exactas de lux con un luxometro profesional.',
|
||||
],
|
||||
offer: {
|
||||
eyebrow: 'Antes de comprar',
|
||||
title: 'Comprueba el requisito de luz antes de decidir',
|
||||
body:
|
||||
'Escanea o busca la planta en el catalogo de GreenLens y consulta su requisito real de luz antes de llevarla a un rincon donde no prosperaria.',
|
||||
terms:
|
||||
'Un escaneo gratis, sin crear cuenta. Tres mas despues de registrarte. A partir de ahi necesitas GreenLens Pro: suscripcion mensual o anual, con los precios actuales en la App Store. Se renueva automaticamente y puedes cancelar cuando quieras desde los ajustes del iPhone.',
|
||||
ctaLabel: 'Haz tu primer escaneo gratis — sin cuenta',
|
||||
},
|
||||
howToName: 'Como evaluar la luz de tu casa sin aparatos',
|
||||
howToSteps: [
|
||||
{
|
||||
name: 'Haz la prueba de la sombra',
|
||||
text: 'Coloca la mano a 30 cm sobre la superficie donde iria la planta, al mediodia: sombra nitida es luz brillante, sombra suave es luz media, sombra apenas perceptible es poca luz, y sin sombra visible es demasiado oscuro sin una lampara LED.',
|
||||
},
|
||||
{
|
||||
name: 'Revisa la orientacion de la ventana',
|
||||
text: 'Sur recibe sol directo todo el dia, este da luz matutina suave, oeste trae sol intenso por la tarde y norte ofrece la luz mas constante y suave, a menudo la mejor opcion para plantas tolerantes a la sombra.',
|
||||
},
|
||||
{
|
||||
name: 'Mide la distancia a la ventana',
|
||||
text: 'La intensidad lumínica cae rapido cuanto mas te alejas: un rincon a 2 metros de una ventana luminosa ya puede estar en el rango de poca luz.',
|
||||
},
|
||||
{
|
||||
name: 'Ten en cuenta la estacion del ano',
|
||||
text: 'Un lugar con luz media en verano puede convertirse en poca luz en invierno porque el sol esta mas bajo y los dias son mas cortos; acerca tus plantas a la ventana si hace falta.',
|
||||
},
|
||||
],
|
||||
faqs: [
|
||||
{
|
||||
question: '¿Puede sobrevivir una planta en un baño o pasillo sin ventanas?',
|
||||
answer:
|
||||
'Ninguna planta verde sobrevive sin luz a largo plazo. En habitaciones sin ventanas debes utilizar lámparas de crecimiento LED (300+ lux durante 8-10 horas al día).',
|
||||
},
|
||||
{
|
||||
question: '¿Por qué se secan las puntas de las hojas en invierno?',
|
||||
answer:
|
||||
'Las puntas marrones suelen deberse a la baja humedad por la calefacción o al uso de agua de grifo con cloro o cal.',
|
||||
},
|
||||
{
|
||||
question: '¿Debo regar menos las plantas que están en la sombra?',
|
||||
answer:
|
||||
'Sí. En zonas con poca luz las plantas consumen agua mucho más despacio. Comprueba siempre los primeros 3 cm de tierra antes de volver a regar.',
|
||||
},
|
||||
],
|
||||
relatedLinks: [
|
||||
{
|
||||
href: '/es/como-funciona-el-reconocimiento-de-plantas-por-ia',
|
||||
label: 'Cómo funciona el reconocimiento de plantas por IA',
|
||||
description: 'Entiende que hay detras del escaneo antes de confiar en el resultado.',
|
||||
},
|
||||
{
|
||||
href: '/es/identificador-de-plantas',
|
||||
label: 'Identificador de plantas',
|
||||
description: 'Identifica la especie y consulta su requisito de luz en el mismo escaneo.',
|
||||
},
|
||||
{
|
||||
href: '/es/app-para-cuidar-plantas',
|
||||
label: 'App para cuidar plantas',
|
||||
description: 'Organiza el riego y la ubicacion de cada planta segun sus necesidades.',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const spanishSeoPageSlugs = Object.keys(spanishSeoPageProfiles)
|
||||
|
||||
BIN
greenlns-landing/public/hero-giessplan.png
Normal file
BIN
greenlns-landing/public/hero-giessplan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 774 KiB |
BIN
greenlns-landing/public/hero-pflanzen-diagnose.png
Normal file
BIN
greenlns-landing/public/hero-pflanzen-diagnose.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 665 KiB |
BIN
greenlns-landing/public/hero-welche-pflanze-ist-das.png
Normal file
BIN
greenlns-landing/public/hero-welche-pflanze-ist-das.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 723 KiB |
Reference in New Issue
Block a user