product Hunt

This commit is contained in:
2026-06-01 20:18:48 +02:00
parent 9d80dc8875
commit d70a50f47a
7 changed files with 6785 additions and 662 deletions

View File

@@ -1,101 +1,101 @@
services:
landing:
build:
context: ./greenlns-landing
dockerfile: Dockerfile
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
NEXT_PUBLIC_SITE_URL: ${SITE_URL:-https://greenlenspro.com}
networks:
- greenlens_net
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
retries: 5
api:
build:
context: .
dockerfile: server/Dockerfile
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
DATABASE_URL: postgresql://${POSTGRES_USER:-greenlns}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@postgres:5432/${POSTGRES_DB:-greenlns}
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DB: ${POSTGRES_DB:-greenlns}
POSTGRES_USER: ${POSTGRES_USER:-greenlns}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
MINIO_ENDPOINT: minio
MINIO_PORT: 9000
MINIO_USE_SSL: "false"
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-greenlns-minio}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?MINIO_SECRET_KEY is required}
MINIO_BUCKET: ${MINIO_BUCKET:-plant-images}
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-https://greenlenspro.com/storage}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_SCAN_MODEL: ${OPENAI_SCAN_MODEL:-gpt-5-mini}
OPENAI_HEALTH_MODEL: ${OPENAI_HEALTH_MODEL:-gpt-5-mini}
REVENUECAT_WEBHOOK_SECRET: ${REVENUECAT_WEBHOOK_SECRET:-}
REVENUECAT_PRO_ENTITLEMENT_ID: ${REVENUECAT_PRO_ENTITLEMENT_ID:-pro}
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required}
PLANT_IMPORT_ADMIN_KEY: ${PLANT_IMPORT_ADMIN_KEY:-}
depends_on:
postgres:
condition: service_healthy
minio:
condition: service_healthy
networks:
- greenlens_net
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
retries: 5
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-greenlns}
POSTGRES_USER: ${POSTGRES_USER:-greenlns}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
ports:
- "5434:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- greenlens_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
minio:
image: minio/minio:latest
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-greenlns-minio}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?MINIO_SECRET_KEY is required}
command: server /data --console-address ":9001"
volumes:
- ./minio_data:/data # <-- NEU: Lokaler Ordner statt benanntes Volume!
networks:
- greenlens_net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
networks:
greenlens_net:
services:
landing:
build:
context: ./greenlns-landing
dockerfile: Dockerfile
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
NEXT_PUBLIC_SITE_URL: ${SITE_URL:-https://greenlenspro.com}
networks:
- greenlens_net
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
retries: 5
api:
build:
context: .
dockerfile: server/Dockerfile
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
DATABASE_URL: postgresql://${POSTGRES_USER:-greenlns}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@postgres:5432/${POSTGRES_DB:-greenlns}
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DB: ${POSTGRES_DB:-greenlns}
POSTGRES_USER: ${POSTGRES_USER:-greenlns}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
MINIO_ENDPOINT: minio
MINIO_PORT: 9000
MINIO_USE_SSL: "false"
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-greenlns-minio}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?MINIO_SECRET_KEY is required}
MINIO_BUCKET: ${MINIO_BUCKET:-plant-images}
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-https://greenlenspro.com/storage}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_SCAN_MODEL: ${OPENAI_SCAN_MODEL:-gpt-5-mini}
OPENAI_HEALTH_MODEL: ${OPENAI_HEALTH_MODEL:-gpt-5-mini}
REVENUECAT_WEBHOOK_SECRET: ${REVENUECAT_WEBHOOK_SECRET:-}
REVENUECAT_PRO_ENTITLEMENT_ID: ${REVENUECAT_PRO_ENTITLEMENT_ID:-pro}
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required}
PLANT_IMPORT_ADMIN_KEY: ${PLANT_IMPORT_ADMIN_KEY:-}
depends_on:
postgres:
condition: service_healthy
minio:
condition: service_healthy
networks:
- greenlens_net
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
retries: 5
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-greenlns}
POSTGRES_USER: ${POSTGRES_USER:-greenlns}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
ports:
- "5434:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- greenlens_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
minio:
image: minio/minio:latest
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-greenlns-minio}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?MINIO_SECRET_KEY is required}
command: server /data --console-address ":9001"
volumes:
- ./minio_data:/data # <-- NEU: Lokaler Ordner statt benanntes Volume!
networks:
- greenlens_net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
networks:
greenlens_net:
external: true

View File

@@ -1,4 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 4C11 10 6 14 4 18C2 22 4 28 10 28C14 28 16 26 16 26C16 26 18 28 22 28C28 28 30 22 28 18C26 14 21 10 16 4Z" fill="#2A5C3F"/>
<path d="M16 4C14 8 13 12 14 16C15 20 18 22 16 26" stroke="#F4F1E8" stroke-width="1.5" stroke-linecap="round"/>
</svg>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 4C11 10 6 14 4 18C2 22 4 28 10 28C14 28 16 26 16 26C16 26 18 28 22 28C28 28 30 22 28 18C26 14 21 10 16 4Z" fill="#2A5C3F"/>
<path d="M16 4C14 8 13 12 14 16C15 20 18 22 16 26" stroke="#F4F1E8" stroke-width="1.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 356 B

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -68,23 +68,38 @@ export default function Hero() {
{t.hero.desc}
</p>
<div className="hero-actions reveal delay-3">
<a href="#cta" className="btn-primary" id="hero-cta-primary">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z" />
<path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12" />
<div className="hero-actions reveal delay-3">
<a href="#cta" className="btn-primary" id="hero-cta-primary">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z" />
<path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12" />
</svg>
&nbsp;{t.hero.primary}
</a>
<a href="#features" className="btn-outline" id="hero-cta-secondary">
{t.hero.secondary}
</a>
</div>
{/* Segmentation widget */}
<div className="hero-seg reveal delay-4" role="group" aria-label={t.hero.segTitle}>
<p className="hero-seg-title">{t.hero.segTitle}</p>
<div className="hero-seg-options">
<a href="#features" className="btn-outline" id="hero-cta-secondary">
{t.hero.secondary}
</a>
</div>
<a
className="product-hunt-badge reveal delay-4"
href="https://www.producthunt.com/products/greenlens?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-greenlens"
target="_blank"
rel="noopener noreferrer"
aria-label="GreenLens on Product Hunt"
>
<img
alt="GreenLens - Scan plants, understand care, and grow smarter | Product Hunt"
width="250"
height="54"
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1160891&theme=light&t=1780336122877"
/>
</a>
{/* Segmentation widget */}
<div className="hero-seg reveal delay-4" role="group" aria-label={t.hero.segTitle}>
<p className="hero-seg-title">{t.hero.segTitle}</p>
<div className="hero-seg-options">
<button
className={`hero-seg-btn${segChoice === 0 ? ' hero-seg-btn--active' : ''}`}
onClick={() => handleSeg(0)}
@@ -120,11 +135,20 @@ export default function Hero() {
</div>
</div>
<style jsx>{`
.hero-seg {
margin-top: 2rem;
background: rgba(244,241,232,0.06);
border: 1px solid rgba(244,241,232,0.12);
<style jsx>{`
.product-hunt-badge {
display: block;
width: fit-content;
margin-top: 1rem;
}
.product-hunt-badge img {
width: 250px;
height: 54px;
}
.hero-seg {
margin-top: 2rem;
background: rgba(244,241,232,0.06);
border: 1px solid rgba(244,241,232,0.12);
border-radius: 16px;
padding: 1.2rem 1.5rem;
max-width: 460px;
@@ -178,12 +202,18 @@ export default function Hero() {
display: inline-block;
transition: border-color 0.2s, background 0.2s;
}
.hero-seg-btn--active .hero-seg-radio {
border-color: var(--green-light);
background: var(--green-light);
box-shadow: 0 0 0 3px rgba(86,160,116,0.2);
}
`}</style>
.hero-seg-btn--active .hero-seg-radio {
border-color: var(--green-light);
background: var(--green-light);
box-shadow: 0 0 0 3px rgba(86,160,116,0.2);
}
@media (max-width: 1024px) {
.product-hunt-badge {
margin-left: auto;
margin-right: auto;
}
}
`}</style>
</section>
)
}

View File

@@ -528,5 +528,5 @@ Keep the visual language consistent across all slides in the slideshow.
- `Slide 4:` Create a premium botanical editorial slide in vertical 9:16. Use the exact GreenLens Botanical Archive design system: digital herbarium aesthetic, high-end editorial archive mood, soft sand background #fbfaf0, layered stone-paper surfaces #f5f4ea and #e4e3d9, deep forest green accents #204e2b and #386641, tertiary earth details only when useful, tactile paper texture, subtle lithography feel, natural daylight, tonal depth instead of borders, no hard divider lines, no clutter, no generic app UI, no harsh shadows, premium magazine composition with centered text and generous whitespace. Scene: brown leaf-tip macro with refined editorial detail. Add large clean overlay text: "3. Brown tips" Add smaller subtext: "Not always dryness." Text style: bold, modern, minimal, high contrast, polished editorial layout with strong editorial hierarchy inspired by Plus Jakarta Sans headlines and Manrope body copy. Text placement: centered, never top-heavy, with balanced spacing, generous safe margins, and a consistent premium botanical magazine feel. Final requirement: the text must stay exactly in the middle.
- `Slide 5:` Create a premium botanical editorial slide in vertical 9:16. Use the exact GreenLens Botanical Archive design system: digital herbarium aesthetic, high-end editorial archive mood, soft sand background #fbfaf0, layered stone-paper surfaces #f5f4ea and #e4e3d9, deep forest green accents #204e2b and #386641, tertiary earth details only when useful, tactile paper texture, subtle lithography feel, natural daylight, tonal depth instead of borders, no hard divider lines, no clutter, no generic app UI, no harsh shadows, premium magazine composition with centered text and generous whitespace. Scene: wilted leaf or soft leaf specimen card in clean layout. Add large clean overlay text: "4. Wilting or softness" Add smaller subtext: "Symptoms need context." Text style: bold, modern, minimal, high contrast, polished editorial layout with strong editorial hierarchy inspired by Plus Jakarta Sans headlines and Manrope body copy. Text placement: centered, never top-heavy, with balanced spacing, generous safe margins, and a consistent premium botanical magazine feel. Final requirement: the text must stay exactly in the middle.
- `Slide 6:` Create a premium botanical editorial slide in vertical 9:16. Use the exact GreenLens Botanical Archive design system: digital herbarium aesthetic, high-end editorial archive mood, soft sand background #fbfaf0, layered stone-paper surfaces #f5f4ea and #e4e3d9, deep forest green accents #204e2b and #386641, tertiary earth details only when useful, tactile paper texture, subtle lithography feel, natural daylight, tonal depth instead of borders, no hard divider lines, no clutter, no generic app UI, no harsh shadows, premium magazine composition with centered text and generous whitespace. Scene: summary slide with five refined specimen cards in premium hierarchy. Add large clean overlay text: "5. Sudden decline" Add smaller subtext: "It usually starts earlier than you think." Text style: bold, modern, minimal, high contrast, polished editorial layout with strong editorial hierarchy inspired by Plus Jakarta Sans headlines and Manrope body copy. Text placement: centered, never top-heavy, with balanced spacing, generous safe margins, and a consistent premium botanical magazine feel. Final requirement: the text must stay exactly in the middle.
- `Slide 7:` Create a premium botanical editorial slide in vertical 9:16. Use the exact GreenLens Botanical Archive design system: digital herbarium aesthetic, high-end editorial archive mood, soft sand background #fbfaf0, layered stone-paper surfaces #f5f4ea and #e4e3d9, deep forest green accents #204e2b and #386641, tertiary earth details only when useful, tactile paper texture, subtle lithography feel, natural daylight, tonal depth instead of borders, no hard divider lines, no clutter, no generic app UI, no harsh shadows, premium magazine composition with centered text and generous whitespace. Scene: final month-close CTA in polished GreenLens style. Add large clean overlay text: "Follow and scan with GreenLens next time." Add smaller subtext: "Month two starts with better diagnosis." Text style: bold, modern, minimal, high contrast, polished editorial layout with strong editorial hierarchy inspired by Plus Jakarta Sans headlines and Manrope body copy. Text placement: centered, never top-heavy, with balanced spacing, generous safe margins, and a consistent premium botanical magazine feel. Final requirement: the text must stay exactly in the middle.
- `Slide 7:` Create a premium botanical editorial slide in vertical 9:16. Use the exact GreenLens Botanical Archive design system: digital herbarium aesthetic, high-end editorial archive mood, soft sand background #fbfaf0, layered stone-paper surfaces #f5f4ea and #e4e3d9, deep forest green accents #204e2b and #386641, tertiary earth details only when useful, tactile paper texture, subtle lithography feel, natural daylight, tonal depth instead of borders, no hard divider lines, no clutter, no generic app UI, no harsh shadows, premium magazine composition with centered text and generous whitespace. Scene: final month-close CTA in polished GreenLens style. Add large clean overlay text: "Follow and scan with GreenLens next time." Add smaller subtext: "Month two starts with better diagnosis." Text style: bold, modern, minimal, high contrast, polished editorial layout with strong editorial hierarchy inspired by Plus Jakarta Sans headlines and Manrope body copy. Text placement: centered, never top-heavy, with balanced spacing, generous safe margins, and a consistent premium botanical magazine feel. Final requirement: the text must stay exactly in the middle.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
# Google Search Console SEO Report
Property: https://greenlenspro.com/
Current period: 2026-04-27 to 2026-05-24
Previous period: 2026-03-30 to 2026-04-26
## High-Impression Low-CTR Opportunities
_No rows matched this rule._
## Striking-Distance Queries
| Item | Clicks | Impressions | CTR | Position | Click Delta | Impression Delta |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| blumen scanner | 0 | 77 | 0.00% | 7.5 | 0 | 76 |
## Declining Queries
_No rows matched this rule._