diff --git a/.gitignore b/.gitignore index cbcf3ae..28e4c9f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,3 @@ __pycache__/ /icons/ .claude/worktrees/ Ban Yaro - Google Play package/ -/unsplash/ diff --git a/MARKETING.md b/MARKETING.md deleted file mode 100644 index d232fe2..0000000 --- a/MARKETING.md +++ /dev/null @@ -1,72 +0,0 @@ -# đŸ Ban Yaro â Marketing-Cockpit - -**Single Source of Truth fĂŒrs Marketing.** Vor jeder Aktion hier prĂŒfen, danach updaten â so wird nichts doppelt gemacht, vergessen oder ĂŒbersehen. Pflege: RenĂ© + Claude. - -_Stand: 2026-06-03_ - -> Diese Datei = Planung & Checkliste. FĂŒr **Live-Daten** (User-Meilenstein, Kanal-Tracking) lohnt zusĂ€tzlich ein Marketing-Tab im **Admin-Bereich** â siehe âAusbau" unten. - -## đ Kanal-Ăberblick -| Kanal / Bereich | Status | NĂ€chster Schritt | -|---|---|---| -| Flyer Print | đą 1000 gedruckt (03.06.) | lokal verteilen | -| Flyer Digital | đĄ Idee | Doppelseiten-PDF + Empfehlungs-QR | -| Lokal (Ebersberg) | ⏠offen | TierĂ€rzte, Hundeschulen, FutterlĂ€den, Tierheim | -| Online-Communities | ⏠offen | FB-Gruppen Landkreis EBE + nebenan.de | -| Empfehlung / Referral | đĄ Infra da (`referral_code`) | Empfehlungs-QR + Tracking sichtbar machen | -| Influencer | đĄ 2 Runden (Mai), kaum Resonanz | Runde 3 erst ab ~50 aktiven Usern | -| Presse / Blogs | đĄ 1 Runde, kaum Resonanz | keine Massenwelle; Nische zuerst | -| Verzeichnisse / Listings | ⏠offen | Product Hunt, PWA-Dirs, Google Business EBE | -| SEO / KI-Auffindbarkeit | đĄ technisch optimiert | Backlinks (Blog-Testberichte) | -| Landing Page | đĄ Redesign-Briefing da | 3 Einstiege, Outcomes statt Features | -| App Store (iOS) | đą in Review (1.0 (3), 03.06.) | Freigabe abwarten | -| Play Store (Android) | đŽ ON HOLD | 12 Closed-Tester / 14 Tage fehlen | -| Merch / NFC-Halsband | đĄ recherchiert | 20 Tags fĂŒr Beta (~33 âŹ) | - -Legende: đą lĂ€uft/erledigt · đĄ angefangen · ⏠offen · đĄ Idee · đŽ blockiert - -## âł Gates / Trigger (nicht zu frĂŒh starten) -- **Influencer & Presse Runde 3** erst ab **~50 aktiven Usern** â vorher zu frĂŒh (GroĂredaktionen fragen zuerst nach Zahlen). â Bei jeder Session aktuelle User-Zahl checken. -- iOS-App ist nativ gebaut & in Review â **ĂŒberholt** die alte âiOS erst ab 10k via Rork/PWABuilder"-Strategie. - -## đ Backlog (konkret als NĂ€chstes) -- [ ] **Flyer lokal verteilen (Ebersberg)** â TierĂ€rzte (Wartezimmer), Hundeschulen/Welpengruppen, FutterlĂ€den, Hundesalons, Tierheim, Hundewiesen-AushĂ€nge, hundefreundliche CafĂ©s. Persönlich erklĂ€ren; AufhĂ€nger: Giftköder-Radar + âDaten in Deutschland". **Lokal bĂŒndeln, nicht streuen** (Community-Dichte fĂŒr Gassi-Treffen/Giftköder). -- [ ] **Digitaler Doppelseiten-Flyer (PDF)** mit **Empfehlungs-QR** fĂŒr Online-/Gruppen-Verteilung. Quelle: `promotion/flyer_a5_*.html`. _Offene Frage: generischer `?ref=empfehlung`-Link vs. pro-User `referral_code`._ -- [ ] **Lokale FB-Gruppen + nebenan.de** â Flyer-Foto + Link posten. -- [ ] **Verzeichnisse** â Product Hunt, progressivewebappstore.com, pwafire.org/directory, Google Business (Ebersberg). -- [ ] **Landing-Page-Redesign** nach Briefing (3 Zielgruppen-Einstiege Hundebesitzer/ZĂŒchter/WelpenkĂ€ufer, Outcomes statt Features, ZĂŒchter-SaaS prominent, Datenschutz als Argument, GrĂŒnder-Story + Foto). -- [ ] **Messung einbauen** â âWie hast du von uns gehört?" im Onboarding + QR-refs pro Kanal. - -## â Erledigt -- [x] 1000 Flyer A5 (zweiseitig) gedruckt â 03.06.2026 -- [x] iOS-App nativ gebaut + eingereicht (1.0 (3), in Review) â Details im Repo `banyaro-ios` -- [x] Influencer-Outreach Runde 1 (5) + Runde 2 (13) â Mai 2026 -- [x] SEO-Grundlagen (llms.txt, Landing About-Section) - -## đ Messung â was bringt wirklich Nutzer? -- **Onboarding-Frage âWie hast du von uns gehört?"** (1 Klick) = billigste & wichtigste Kontrolle. _(noch einzubauen)_ -- **QR-refs pro Ort/Kanal** (z. B. `banyaro.app/?ref=tierarzt-grafing`) â ab nĂ€chster Flyer-Charge. -- **`referral_code`** (in DB, `routes/auth.py`) â Empfehlungen zĂ€hlbar. -- Aktive User aktuell: _[aus Admin eintragen]_ - -## đ Details je Kanal - -### Influencer -2 Runden im Mai gesendet (`partner@banyaro.app`; DKIM/SPF/DMARC aktiv), **kaum Resonanz** â zu frĂŒh (wenige User), teils falsche Adressen (z. B. GEO â richtig `chefredaktion@geo.de`). -**Runde 3:** keine Massenwelle ohne PR-Agentur; **Hundeschulen/-trainer zuerst** (kleines Netzwerk, empfehlen aktiv Tools, Trainingsfeature ist stark), persönliche Mails, AufhĂ€nger = neue Features + echte Nutzerzahlen. -â **Wer schon kontaktiert wurde:** AI-Memory `project_influencer_outreach` (Runde 1: verpinscht, missyminzi, wanderlust_samoyed, viviundholly, doguniversity, dogstv; Runde 2: nami.and.tommy, brina.explores, heimatherzen, pfotentick, flummis_diary, verwolft, wildwildwilli, knutini_, ninja.vom.wolfstor, pupsonality, osman_theparson, babybearyuki, dogswiss). **Vor neuer Runde dort prĂŒfen.** - -### Play Store (Android TWA) -PWABuilder-Paket fertig (`Ban Yaro - Google Play package/`, Package `app.banyaro.twa`). **BLOCKER:** Google verlangt 12 Closed-Tester ĂŒber 14 Tage â Tester fehlen (Engpass, nicht die Technik). assetlinks.json + Play-Console-Eintrag stehen bereit. Nicht priorisieren bis Tester da. - -### Merch / NFC-Halsband -Tag recherchiert: **HID Laundry Tag 16 mm** (shopnfc, SKU RE-ICO2-16, ~1 âŹ/Stk ab 500), fĂŒr `banyaro.app/hund/{id}`. Beta: 20 Stk (~33 âŹ) an erste Nutzer. - -### Flyer -Print: A5 zweiseitig, Quelle `promotion/flyer_a5_allgemein.html` + `flyer_a5_rueckseite.html`, QR â banyaro.app. Vorderseite = alle Hundebesitzer, RĂŒckseite stark ZĂŒchter-fokussiert. - -## đ Ausbau: Live-Tool im Admin-Bereich (optional) -Diese Datei deckt Planung/Checkliste ab (Claude pflegt sie). Der **Admin-Bereich** lohnt sich fĂŒr die Teile mit echten Daten: -- **User-Meilenstein-Anzeige** (aktive User) â blendet automatisch den âOutreach Runde 3"-Hinweis ein, sobald ~50 erreicht. -- **Kanal-Tracking**: Auswertung âWie gehört?" + QR-ref-ZĂ€hler + `referral_code`-Statistik. -- Optional: das Kanal-Board (Status/Backlog) als editierbare Admin-Seite. diff --git a/VERSION b/VERSION index 0948691..41edc23 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1155 \ No newline at end of file +1141 \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index e954c83..df5124d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -511,11 +511,11 @@ async def sitemap(): urls = [ ("https://banyaro.app/", "weekly", "1.0"), ("https://banyaro.app/zuechter", "weekly", "0.9"), - ("https://banyaro.app/wurfboerse", "daily", "0.8"), + ("https://banyaro.app/info", "monthly", "0.8"), + ("https://banyaro.app/presse", "monthly", "0.7"), ("https://banyaro.app/wiki/rassen", "weekly", "0.8"), - ("https://banyaro.app/help", "monthly", "0.7"), ("https://banyaro.app/knigge", "monthly", "0.7"), - ("https://banyaro.app/partner", "monthly", "0.6"), + ("https://banyaro.app/wurfboerse", "daily", "0.8"), ] try: @@ -526,6 +526,12 @@ async def sitemap(): for r in rassen: urls.append((f"https://banyaro.app/wiki/rasse/{r['slug']}", "monthly", "0.7")) + events = conn.execute( + "SELECT id FROM events WHERE datum >= date('now') LIMIT 200" + ).fetchall() + for e in events: + urls.append((f"https://banyaro.app/api/events/{e['id']}", "weekly", "0.5")) + # Ăffentliche ZĂŒchter-Profile breeders = conn.execute( "SELECT bp.zwingername FROM breeder_profiles bp " @@ -1342,47 +1348,12 @@ async def public_dog_page(dog_id: int): # ------------------------------------------------------------------ @app.get("/teilen/{token}") async def invite_page(token: str): - from fastapi.responses import HTMLResponse - with open(f"{STATIC_DIR}/index.html", encoding="utf-8") as _f: - _html = _f.read() - _html = _html.replace( - '', - '' - ) - return HTMLResponse(content=_html, headers={"Cache-Control": "no-store, no-cache"}) + return FileResponse(f"{STATIC_DIR}/index.html", headers={"Cache-Control": "no-store, no-cache"}) @app.get("/breeder/{zwingername}") async def breeder_profile_page(zwingername: str): - from fastapi.responses import HTMLResponse - from urllib.parse import unquote - from database import db as _db - import html as _html_mod - name = unquote(zwingername) - desc = f"HundezĂŒchter {_html_mod.escape(name)} auf Ban Yaro â Wurfbörse, Stammbaum und mehr." - try: - with _db() as conn: - bp = conn.execute( - "SELECT bp.rasse, bp.beschreibung FROM breeder_profiles bp " - "JOIN users u ON u.id = bp.user_id WHERE bp.zwingername=? AND u.rolle='breeder' LIMIT 1", - (name,) - ).fetchone() - if bp and bp["beschreibung"]: - desc = _html_mod.escape(bp["beschreibung"][:160]) - except Exception: - pass - with open(f"{STATIC_DIR}/index.html", encoding="utf-8") as _f: - _page = _f.read() - _page = _page.replace( - '', - f'' - ).replace( - '
Hundewelpen von geprĂŒften ZĂŒchtern
-{count_text}
- {litters_html or 'Aktuell keine WĂŒrfe eingetragen.
Schau bald wieder vorbei!
Keine Orte in der NĂ€he gefunden.
'; + } else { + sugEl.innerHTML = suggestions.map(s => ` + `).join(''); + sugEl.querySelectorAll('.diary-location-suggestion').forEach(el => { + el.addEventListener('click', () => _setName(el.dataset.name)); + }); + } + sugEl.style.display = ''; + } catch (err) { + UI.toast.error(err?.message?.includes('GPS') || lat == null + ? 'GPS nicht verfĂŒgbar.' : 'Ortssuche fehlgeschlagen.'); + } finally { + UI.setLoading(btn, false); + } + } + + document.getElementById('diary-location-btn')?.addEventListener('click', _showSuggestions); document.getElementById('diary-form-delete')?.addEventListener('click', async () => { const ok = await UI.modal.confirm({ diff --git a/backend/static/js/pages/forum.js b/backend/static/js/pages/forum.js index 512ed6d..e2dfe19 100644 --- a/backend/static/js/pages/forum.js +++ b/backend/static/js/pages/forum.js @@ -640,17 +640,6 @@ function _fmtDate(iso) { } catch (err) { UI.toast.error(err.message); } }); - // Liker-Liste anzeigen (Klick auf die Zahl) - const _thLikeCount = document.getElementById('thread-like-count'); - if (_thLikeCount) { - _thLikeCount.style.cursor = 'pointer'; - _thLikeCount.title = 'Wer hat geliked?'; - _thLikeCount.addEventListener('click', e => { - e.stopPropagation(); - if ((thread.likes || 0) > 0) _showLikers('thread', thread.id); - }); - } - // Report thread document.getElementById('thread-report-btn')?.addEventListener('click', () => { _showReportForm('thread', thread.id); @@ -823,9 +812,9 @@ function _fmtDate(iso) { // Like container.querySelectorAll('.forum-post-like:not([data-bound])').forEach(btn => { btn.dataset.bound = '1'; - const postId = parseInt(btn.dataset.postId); btn.addEventListener('click', async () => { if (!uid) { UI.toast.info('Bitte erst anmelden.'); return; } + const postId = parseInt(btn.dataset.postId); try { const res = await API.forum.like('post', postId); btn.classList.toggle('active', res.liked); @@ -833,16 +822,6 @@ function _fmtDate(iso) { if (countEl) countEl.textContent = res.count; } catch (err) { UI.toast.error(err.message); } }); - // Klick auf die Zahl â Liker-Liste - const countEl = btn.querySelector('.forum-post-like-count'); - if (countEl) { - countEl.style.cursor = 'pointer'; - countEl.title = 'Wer hat geliked?'; - countEl.addEventListener('click', e => { - e.stopPropagation(); - if (parseInt(countEl.textContent) > 0) _showLikers('post', postId); - }); - } }); // Report @@ -895,28 +874,6 @@ function _fmtDate(iso) { }); } - // ---------------------------------------------------------- - // Liker-Liste â wer hat geliked? - // ---------------------------------------------------------- - async function _showLikers(targetType, targetId) { - try { - const likers = await API.forum.likers(targetType, targetId); - if (!likers.length) { UI.toast.info('Noch keine Likes.'); return; } - const rows = likers.map(l => ` -