Landing: emotionaler Hero, Social-Proof-Stats, Testimonial-Slots, Scroll-Animationen (SW by-v952)

- Hero-Headline: "Weil jeder Moment mit ihm zählt." (warm/emotional statt Feature-Liste)
- CTA umbenannt: "Kostenlos starten" statt "Ich bin Hundebesitzer"
- Hero-Stats-Zeile: live Nutzer/Hunde/km-Zähler (nur wenn >0)
- Stats-Band: orangener Balken mit 4 Live-Kennzahlen nach der Zwei-Welten-Section
- Testimonial-Section: 3 Platzhalter-Karten zwischen Features und Züchter-Bereich
- Scroll-Animationen: IntersectionObserver auf alle Cards (fade-up)
- API: /api/stats/public — öffentlicher Endpoint, 5-Min-Cache
This commit is contained in:
rene 2026-05-14 18:23:23 +02:00
parent 7e939cf854
commit f9160307bc
4 changed files with 232 additions and 9 deletions

View file

@ -1,3 +1,4 @@
import time
from fastapi import APIRouter, Depends
from database import db
from auth import get_current_user, get_current_user_optional
@ -19,6 +20,35 @@ _STATS_SQL = """
"""
_pub_cache: dict = {"data": None, "ts": 0.0}
_PUB_TTL = 300 # 5 Minuten
@router.get("/public")
async def public_stats():
now = time.time()
if _pub_cache["data"] and now - _pub_cache["ts"] < _PUB_TTL:
return _pub_cache["data"]
with db() as conn:
users = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
dogs = conn.execute("SELECT COUNT(*) FROM dogs").fetchone()[0]
km = conn.execute(
"SELECT ROUND(COALESCE(SUM(distanz_km),0),0) FROM routes"
).fetchone()[0]
posts = conn.execute("SELECT COUNT(*) FROM forum_posts").fetchone()[0]
diary = conn.execute("SELECT COUNT(*) FROM diary").fetchone()[0]
data = {
"users": users,
"dogs": dogs,
"km": int(km or 0),
"forum_posts": posts,
"diary_entries": diary,
}
_pub_cache["data"] = data
_pub_cache["ts"] = now
return data
@router.get("/leaderboard")
async def leaderboard(_user=Depends(get_current_user_optional)):
with db() as conn: