Feature: Tierschutz-Check, KI-Züchter-Features, Export, SEO-Update

Tierschutz-System (immer aktiv, nicht abschaltbar):
- welfare_check.py: regelbasierte Prüfung IK, Alter, Deckpause, Wurfanzahl, Genetik
- Grün/Gelb/Rot-Modal bei Wurf anlegen + Probeverpaarung
- Bei kritischem Befund + "Trotzdem fortfahren" → automatische Admin-Mail
- Tierschutz-Check nie durch Nutzer deaktivierbar

KI-Züchter-Features (pro User an/abschaltbar außer Tierschutz):
- routes/zucht_ki.py: 5 Endpunkte — Wurfankündigung, Genetik-Erklärung,
  Paarungsanalyse, Hund-Beschreibung, Jahresbericht
- Toggles in Einstellungen (ki_zucht_* Felder)
- KI-Buttons in litters.js + zuchthunde.js

KI-Routing: Privilegierte Rollen (Admin, Züchter, Moderator, Manager)
nutzen Claude Sonnet primär, lokales LLM als Fallback

Datenexport: routes/breeder_export.py — ZIP mit HTML-Dossier + ODS
(odfpy hinzugefügt in requirements.txt)

Admin-Profil: POST /admin/breeder/create-profile für Schnellprofil ohne
Antragsprozess; Admin-Rolle bleibt erhalten

Wurfformular: Dropdown aus Zuchtkartei für Vater/Mutter mit Auto-Fill;
litters.vater_id + mutter_id als FK auf zucht_hunde

Probeverpaarung: heart-fill Icon + Welfare-Block im Ergebnis

Landing Page: Züchter-Section + Feature-Gruppe, Meta-Tags, JSON-LD,
keywords, softwareVersion 2.1

SEO: llms.txt vollständig überarbeitet, robots.txt Züchter-Pfade,
sitemap.xml um Wurfbörse + Züchter-Profile erweitert

SW by-v474, APP_VER 451
This commit is contained in:
rene 2026-04-28 19:49:54 +02:00
parent 91340be5a3
commit c8ae514c01
20 changed files with 2129 additions and 200 deletions

View file

@ -177,6 +177,9 @@
<rect width="256" height="256" fill="none"/>
<path d="M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM152,160H136v16a8,8,0,0,1-16,0V160H104a8,8,0,0,1,0-16h16V128a8,8,0,0,1,16,0v16h16a8,8,0,0,1,0,16ZM48,80V48H72v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80Z"/>
</symbol>
<symbol id="heart-fill" viewBox="0 0 256 256">
<path d="M240,102c0,70-103.79,126.66-108.21,129a8,8,0,0,1-7.58,0C119.79,228.66,16,172,16,102A62.07,62.07,0,0,1,78,40c20.65,0,38.73,8.88,50,23.89C139.27,48.88,157.35,40,178,40A62.07,62.07,0,0,1,240,102Z"/>
</symbol>
<symbol id="tree-structure" viewBox="0 0 256 256">
<path d="M144,96V80H128a8,8,0,0,0-8,8v80a8,8,0,0,0,8,8h16V160a16,16,0,0,1,16-16h48a16,16,0,0,1,16,16v48a16,16,0,0,1-16,16H160a16,16,0,0,1-16-16V192H128a24,24,0,0,1-24-24V136H72v8a16,16,0,0,1-16,16H24A16,16,0,0,1,8,144V112A16,16,0,0,1,24,96H56a16,16,0,0,1,16,16v8h32V88a24,24,0,0,1,24-24h16V48a16,16,0,0,1,16-16h48a16,16,0,0,1,16,16V96a16,16,0,0,1-16,16H160A16,16,0,0,1,144,96Z"/>
</symbol>

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before After
Before After

View file

@ -615,6 +615,7 @@ const API = (() => {
profile(zwingername) { return get(`/breeder/profil/${encodeURIComponent(zwingername)}`); },
mapMarkers() { return get('/breeder/map'); },
updateProfile(data) { return put('/breeder/profile', data); },
adminCreateProfile() { return post('/admin/breeder/create-profile', {}); },
pendingList() { return get('/admin/breeders/pending'); },
documents(userId) { return get(`/admin/breeder/${userId}/documents`); },
documentUrl(userId, docId) { return `/api/admin/breeder/${userId}/document/${docId}`; },
@ -631,6 +632,7 @@ const API = (() => {
create(data) { return post('/litters', data); },
update(id, data) { return put(`/litters/${id}`, data); },
remove(id) { return del(`/litters/${id}`); },
welfareConfirm(id) { return post(`/litters/${id}/welfare-confirm`, {}); },
// Welpen
puppies(id) { return get(`/litters/${id}/puppies`); },
addPuppy(id, data) { return post(`/litters/${id}/puppies`, data); },
@ -653,43 +655,51 @@ const API = (() => {
remove(id) { return del(`/breeder/photos/${id}`); },
};
// Öffentliche API
return {
get, post, put, patch, del, upload,
auth, dogs, diary, health, tieraerzte, poison,
places, routes, walks, events, sitting, forum, lost, knigge, weather, push,
friends, chat, webcal, importData, sharing, widget, notifications, services, ratings, sittingAccess, training, notes,
// ----------------------------------------------------------
// ZUCHTKARTEI (Hunde-Stammdaten, Gesundheit, Genetik, Titel)
// ----------------------------------------------------------
const zuchthunde = {
// Hunde
list() { return get('/zuchthunde'); },
get(id) { return get(`/zuchthunde/${id}`); },
create(data) { return post('/zuchthunde', data); },
update(id, data) { return put(`/zuchthunde/${id}`, data); },
remove(id) { return del(`/zuchthunde/${id}`); },
pedigree(id, gen=4) { return get(`/zuchthunde/${id}/pedigree?generations=${gen}`); },
// Gesundheitstests
healthTests(id) { return get(`/zuchthunde/${id}/health-tests`); },
addHealthTest(id, data) { return post(`/zuchthunde/${id}/health-tests`, data); },
updateHealthTest(tid, data) { return put(`/zuchthunde/health-tests/${tid}`, data); },
deleteHealthTest(tid) { return del(`/zuchthunde/health-tests/${tid}`); },
// Gentests
geneticTests(id) { return get(`/zuchthunde/${id}/genetic-tests`); },
addGeneticTest(id, data) { return post(`/zuchthunde/${id}/genetic-tests`, data); },
updateGeneticTest(tid, data) { return put(`/zuchthunde/genetic-tests/${tid}`, data); },
deleteGeneticTest(tid) { return del(`/zuchthunde/genetic-tests/${tid}`); },
// Titel
titles(id) { return get(`/zuchthunde/${id}/titles`); },
addTitle(id, data) { return post(`/zuchthunde/${id}/titles`, data); },
updateTitle(tid, data) { return put(`/zuchthunde/titles/${tid}`, data); },
deleteTitle(tid) { return del(`/zuchthunde/titles/${tid}`); },
// Probeverpaarung
trialMating(vaterId, mutterId) { return post('/zuchthunde/trial-mating', { vater_id: vaterId, mutter_id: mutterId }); },
};
breeder, litters, breederPhotos, zuchthunde,
// ----------------------------------------------------------
// ZÜCHTER-KI
// ----------------------------------------------------------
const zuchtKi = {
wurfankuendigung(litterId) { return post('/zucht-ki/wurfankuendigung', { litter_id: litterId }); },
genetikErklaerung(litterId, ziel) { return post('/zucht-ki/genetik-erklaerung', { litter_id: litterId, zielgruppe: ziel }); },
paarungAnalyse(vaterId, mutterId, ik, welfareLevel) {
return post('/zucht-ki/paarung-analyse', { vater_id: vaterId, mutter_id: mutterId, ik_prozent: ik, welfare_level: welfareLevel });
},
hundBeschreibung(hundId) { return post('/zucht-ki/hund-beschreibung', { hund_id: hundId }); },
jahresbericht() { return post('/zucht-ki/jahresbericht', {}); },
};
// Öffentliche API
return {
get, post, put, patch, del, upload,
auth, dogs, diary, health, tieraerzte, poison,
places, routes, walks, events, sitting, forum, lost, knigge, weather, push,
friends, chat, webcal, importData, sharing, widget, notifications, services, ratings, sittingAccess, training, notes,
breeder, litters, breederPhotos, zuchthunde, zuchtKi,
subscribeToPush, getLocation, clientNow,
APIError,
};

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '444'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '451'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {

View file

@ -192,6 +192,13 @@ window.Page_litters = (() => {
});
});
el.querySelectorAll('.litters-ki-announce-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
_showKiAnnouncement(id);
});
});
el.querySelectorAll('.litters-add-puppy-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
@ -249,6 +256,11 @@ window.Page_litters = (() => {
title="Elterntier-Fotos verwalten">
${UI.icon('users')} Eltern
</button>
${_appState.user?.ki_zucht_wurfankuendigung !== 0 ? `
<button class="btn btn-ghost btn-sm litters-ki-announce-btn" data-id="${l.id}"
title="KI: Wurfankündigung schreiben">
${UI.icon('sparkle')} Ankündigung
</button>` : ''}
<button class="btn btn-ghost btn-sm litters-edit-btn" data-id="${l.id}"
title="Bearbeiten">
${UI.icon('pencil-simple')}
@ -477,24 +489,42 @@ window.Page_litters = (() => {
// ----------------------------------------------------------
// Wurf-Formular (neu / bearbeiten)
// ----------------------------------------------------------
function _showLitterForm(litter) {
async function _showLitterForm(litter) {
const isEdit = !!litter;
const v = litter || {};
const today = new Date().toISOString().slice(0, 10);
// Zuchtkartei laden für Elterntier-Auswahl
let zuchthunde = [];
try { zuchthunde = await API.zuchthunde.list(); } catch {}
const maennlich = zuchthunde.filter(h => h.geschlecht !== 'weiblich');
const weiblich = zuchthunde.filter(h => h.geschlecht !== 'maennlich');
const buildSelect = (name, idName, list, currentId, currentName, placeholder) => {
const opts = list.map(h => {
const label = h.name + (h.rufname ? ` (${h.rufname})` : '') + (h.zuchtbuchnummer ? ` · ${h.zuchtbuchnummer}` : '');
return `<option value="${h.id}" data-name="${_esc(h.name)}" ${currentId == h.id ? 'selected' : ''}>${_esc(label)}</option>`;
}).join('');
return `
<select class="form-control" name="${idName}" id="${idName}-sel" style="margin-bottom:var(--space-2)">
<option value=""> ${placeholder} </option>
${opts}
</select>
<input class="form-control" type="text" name="${name}" id="${name}-txt"
value="${_esc(currentName || '')}" placeholder="oder Namen frei eingeben">`;
};
const body = `
<form id="litter-form" autocomplete="off">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="form-group">
<label class="form-label">Vatername</label>
<input class="form-control" type="text" name="vater_name"
value="${_esc(v.vater_name || '')}" placeholder="Name des Vaters">
<label class="form-label">Vater</label>
${buildSelect('vater_name', 'vater_id', maennlich, v.vater_id, v.vater_name, 'Aus Zuchtkartei')}
</div>
<div class="form-group">
<label class="form-label">Muttername</label>
<input class="form-control" type="text" name="mutter_name"
value="${_esc(v.mutter_name || '')}" placeholder="Name der Mutter">
<label class="form-label">Mutter</label>
${buildSelect('mutter_name', 'mutter_id', weiblich, v.mutter_id, v.mutter_name, 'Aus Zuchtkartei')}
</div>
</div>
@ -583,6 +613,16 @@ window.Page_litters = (() => {
document.getElementById('lf-cancel')?.addEventListener('click', UI.modal.close);
// Auto-Fill: Dropdown → Namenfeld befüllen
['vater', 'mutter'].forEach(role => {
document.getElementById(`${role}_id-sel`)?.addEventListener('change', e => {
const sel = e.target;
const opt = sel.options[sel.selectedIndex];
const txt = document.getElementById(`${role}_name-txt`);
if (txt) txt.value = opt.value ? (opt.dataset.name || '') : '';
});
});
document.getElementById('litter-form')?.addEventListener('submit', async e => {
e.preventDefault();
const btn = document.getElementById('lf-submit');
@ -591,6 +631,8 @@ window.Page_litters = (() => {
const payload = {
vater_name: fd.get('vater_name')?.trim() || null,
mutter_name: fd.get('mutter_name')?.trim() || null,
vater_id: fd.get('vater_id') ? parseInt(fd.get('vater_id')) : null,
mutter_id: fd.get('mutter_id') ? parseInt(fd.get('mutter_id')) : null,
geburt_datum: fd.get('geburt_datum') || null,
erwartetes_datum: fd.get('erwartetes_datum') || null,
welpen_gesamt: fd.get('welpen_gesamt') ? parseInt(fd.get('welpen_gesamt')) : null,
@ -608,14 +650,16 @@ window.Page_litters = (() => {
const updated = await API.litters.update(litter.id, payload);
const idx = _litters.findIndex(l => l.id === litter.id);
if (idx !== -1) _litters[idx] = updated;
UI.modal.close();
UI.toast.success('Wurf aktualisiert.');
_renderList();
} else {
const created = await API.litters.create(payload);
_litters.unshift(created);
UI.toast.success('Wurf angelegt.');
UI.modal.close();
_renderList();
_showWelfareModal(created.welfare, created.id);
}
UI.modal.close();
_renderList();
});
});
}
@ -967,6 +1011,133 @@ window.Page_litters = (() => {
});
}
// ----------------------------------------------------------
// Tierschutz-Check Modal
// ----------------------------------------------------------
function _showWelfareModal(welfare, litterId) {
if (!welfare) return;
const color = { ok: '#16a34a', info: '#3b82f6', warning: '#f59e0b', critical: '#dc2626' }[welfare.level] || '#6b7280';
const title = { ok: 'Alles prima', info: 'Hinweis', warning: 'Bitte beachten', critical: 'Kritischer Hinweis' }[welfare.level];
const icon = { ok: 'check-circle', info: 'info', warning: 'warning', critical: 'warning-circle' }[welfare.level];
const issueHTML = (welfare.issues || []).map(i => `
<div style="display:flex;gap:8px;padding:8px 0;border-bottom:1px solid rgba(0,0,0,.06)">
<span style="color:${color};flex-shrink:0">${UI.icon('warning')}</span>
<span style="font-size:var(--text-sm)">${_esc(i.text)}</span>
</div>`).join('');
const okHTML = (welfare.ok_points || []).map(p => `
<div style="display:flex;gap:8px;padding:4px 0">
<span style="color:#16a34a;flex-shrink:0">${UI.icon('check')}</span>
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">${_esc(p)}</span>
</div>`).join('');
const isProblematic = welfare.level === 'warning' || welfare.level === 'critical';
UI.modal.open({
title: `<span style="color:${color}">${UI.icon(icon)} Tierschutz-Check: ${title}</span>`,
body: `
<div style="background:${color}18;border:1.5px solid ${color}40;border-radius:var(--radius-md);
padding:var(--space-4);margin-bottom:var(--space-4)">
${issueHTML || ''}
${okHTML}
</div>
${welfare.level === 'critical' ? `
<div style="font-size:var(--text-sm);color:var(--c-text-secondary);
background:var(--c-surface-2);border-radius:var(--radius-sm);
padding:var(--space-3)">
${UI.icon('info')} Wenn du fortfährst, wird der Administrator informiert.
</div>` : ''}
`,
footer: isProblematic ? `
<div style="display:flex;gap:var(--space-2);width:100%">
<button class="btn btn-secondary flex-1" id="welfare-back-btn">
${UI.icon('arrow-left')} Zurück
</button>
<button class="btn btn-ghost flex-1" id="welfare-confirm-btn"
style="color:${color}">
Trotzdem fortfahren
</button>
</div>` : `
<button class="btn btn-primary" data-modal-close style="width:100%">
${UI.icon('check')} Verstanden
</button>`,
});
document.getElementById('welfare-back-btn')?.addEventListener('click', () => {
UI.modal.close?.();
const litter = _litters.find(l => l.id === litterId);
API.litters.remove(litterId).catch(() => {});
_litters = _litters.filter(l => l.id !== litterId);
_renderList();
setTimeout(() => _showLitterForm(null), 150);
});
document.getElementById('welfare-confirm-btn')?.addEventListener('click', async () => {
await API.litters.welfareConfirm(litterId).catch(() => {});
UI.modal.close?.();
UI.toast.info('Wurf gespeichert.');
});
}
// ----------------------------------------------------------
// KI: Wurfankündigung
// ----------------------------------------------------------
async function _showKiAnnouncement(litterId) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Wurfankündigung`,
body: `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-6)">
KI schreibt Wurfankündigung
</p>`,
footer: '',
});
let text = '';
try {
const result = await API.zuchtKi.wurfankuendigung(litterId);
text = result.text || result.content || result.ankuendigung || JSON.stringify(result);
} catch (err) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Wurfankündigung`,
body: `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;
}
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Wurfankündigung`,
body: `<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>`,
footer: `
<button class="btn btn-secondary flex-1" id="ki-announce-copy">
${UI.icon('clipboard-text')} Kopieren
</button>
<button class="btn btn-primary flex-1" id="ki-announce-use">
${UI.icon('check')} In Beschreibung übernehmen
</button>`,
});
document.getElementById('ki-announce-copy')?.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(text);
UI.toast.success('Text kopiert.');
} catch {
UI.toast.error('Kopieren nicht möglich.');
}
});
document.getElementById('ki-announce-use')?.addEventListener('click', async () => {
const btn = document.getElementById('ki-announce-use');
await UI.asyncButton(btn, async () => {
await API.litters.update(litterId, { beschreibung: text });
UI.modal.close();
UI.toast.success('Beschreibung aktualisiert.');
await _loadLitters();
});
});
}
return { init, refresh, onDogChange };
})();

View file

@ -685,6 +685,30 @@ window.Page_settings = (() => {
_loadBreederCard();
}
// ----------------------------------------------------------
// KI-Toggle-Zeile (Hilfsfunktion für Züchter-Card)
// ----------------------------------------------------------
function _kiToggleRow(key, label, user) {
const active = user[key] !== 0;
return `
<div style="display:flex;justify-content:space-between;align-items:center;
padding:var(--space-2) 0;font-size:var(--text-sm)">
<span>${_esc(label)}</span>
<button class="by-toggle ki-toggle-btn" data-key="${_esc(key)}"
data-active="${active ? '1' : '0'}"
style="position:relative;display:inline-block;width:44px;height:24px;
border:none;border-radius:12px;cursor:pointer;flex-shrink:0;
background:${active ? 'var(--c-primary)' : 'var(--c-border)'};
transition:background .2s">
<span class="by-toggle-thumb"
style="position:absolute;top:2px;left:${active ? '22px' : '2px'};
width:20px;height:20px;border-radius:50%;
background:#fff;transition:left .2s;
box-shadow:0 1px 3px rgba(0,0,0,.3)"></span>
</button>
</div>`;
}
// ----------------------------------------------------------
// ZÜCHTER-CARD — asynchron laden und in Slot rendern
// ----------------------------------------------------------
@ -722,7 +746,30 @@ window.Page_settings = (() => {
${rolle === 'breeder' && profile ? `
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" style="margin-top:var(--space-3)">
${UI.icon('pencil-simple')} Profil bearbeiten
</button>` : ''}`;
</button>` : ''}
${rolle === 'admin' && !profile ? `
<button class="btn btn-primary btn-sm" id="breeder-admin-create-btn" style="margin-top:var(--space-3)">
${UI.icon('plus')} Admin-Züchterprofil anlegen
</button>` : ''}
${rolle === 'admin' && profile ? `
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" style="margin-top:var(--space-3)">
${UI.icon('pencil-simple')} Profil bearbeiten
</button>` : ''}
${profile ? `
<div style="margin-top:var(--space-4);padding-top:var(--space-3);border-top:1px solid var(--c-border)">
<div style="font-size:var(--text-xs);font-weight:600;color:var(--c-text-secondary);
text-transform:uppercase;letter-spacing:0.05em;margin-bottom:var(--space-3)">
KI-Züchter-Assistenz
</div>
${_kiToggleRow('ki_zucht_wurfankuendigung', 'Wurfankündigungen schreiben', _appState.user || {})}
${_kiToggleRow('ki_zucht_genetik', 'Genetik-Erklärung für Käufer', _appState.user || {})}
${_kiToggleRow('ki_zucht_paarung', 'Paarungsanalyse', _appState.user || {})}
${_kiToggleRow('ki_zucht_beschreibung', 'Hunde-Beschreibungen', _appState.user || {})}
${_kiToggleRow('ki_zucht_jahresbericht', 'Jahresauswertung', _appState.user || {})}
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-2)">
${UI.icon('info')} Der Tierschutz-Check läuft immer automatisch und ist nicht abschaltbar.
</div>
</div>` : ''}`;
} else if (breeder_status === 'pending') {
statusBadge = `<span class="badge" style="background:#f59e0b;color:#fff">
${UI.icon('hourglass')} Antrag wird geprüft
@ -770,6 +817,48 @@ window.Page_settings = (() => {
slot.querySelector('#breeder-edit-profile-btn')?.addEventListener('click', () =>
_openBreederEditModal(profile)
);
slot.querySelector('#breeder-admin-create-btn')?.addEventListener('click', async (e) => {
const btn = e.currentTarget;
btn.disabled = true;
btn.textContent = 'Wird angelegt…';
try {
await API.breeder.adminCreateProfile();
UI.toast.success('Admin-Züchterprofil angelegt. Bitte Seite neu laden.');
_loadBreederCard();
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Anlegen.');
btn.disabled = false;
btn.innerHTML = `${UI.icon('plus')} Admin-Züchterprofil anlegen`;
}
});
// KI-Toggle-Handler
slot.querySelectorAll('.ki-toggle-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const key = btn.dataset.key;
const active = btn.dataset.active === '1';
const newVal = active ? 0 : 1;
// Optimistisches UI-Update
btn.dataset.active = newVal ? '1' : '0';
btn.style.background = newVal ? 'var(--c-primary)' : 'var(--c-border)';
const thumb = btn.querySelector('.by-toggle-thumb');
if (thumb) thumb.style.left = newVal ? '22px' : '2px';
try {
const updated = await API.patch('/profile', { [key]: newVal });
if (_appState?.user) _appState.user[key] = newVal;
UI.toast.success(newVal ? 'KI-Feature aktiviert.' : 'KI-Feature deaktiviert.');
} catch (err) {
// Revert
btn.dataset.active = active ? '1' : '0';
btn.style.background = active ? 'var(--c-primary)' : 'var(--c-border)';
if (thumb) thumb.style.left = active ? '22px' : '2px';
UI.toast.error(err?.message || 'Einstellung konnte nicht gespeichert werden.');
}
});
});
}
// ----------------------------------------------------------

View file

@ -108,8 +108,16 @@ window.Page_zuchthunde = (() => {
${UI.icon('plus')} Hund anlegen
</button>
<button class="btn btn-secondary btn-sm" id="zh-trial-btn">
${UI.icon('dna')} Probeverpaarung
${UI.icon('heart-fill')} Probeverpaarung
</button>
<a href="/api/breeder/export" download class="btn btn-ghost btn-sm" id="zh-export-btn"
title="Alle Daten herunterladen (HTML + ODS)">
${UI.icon('download-simple')} Export
</a>
${_appState?.user?.ki_zucht_jahresbericht !== 0 ? `
<a class="btn btn-ghost btn-sm" id="zh-jahresbericht-btn">
${UI.icon('chart-bar')} Jahresbericht
</a>` : ''}
</div>
<div style="padding:0 0 var(--space-3)">
<input class="form-control" id="zh-search" type="search"
@ -123,6 +131,7 @@ window.Page_zuchthunde = (() => {
document.getElementById('zh-new-btn')?.addEventListener('click', () => _showHundForm(null));
document.getElementById('zh-trial-btn')?.addEventListener('click', () => _showTrialMatingModal());
document.getElementById('zh-jahresbericht-btn')?.addEventListener('click', () => _showJahresbericht());
document.getElementById('zh-search')?.addEventListener('input', e => {
_query = e.target.value.toLowerCase().trim();
@ -215,6 +224,13 @@ window.Page_zuchthunde = (() => {
});
});
el.querySelectorAll('.zh-ki-desc-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
_showKiDesc(id);
});
});
// Offene Sektionen wiederherstellen
Object.entries(_openSections).forEach(([id, sec]) => {
if (sec) _openSection(parseInt(id), sec);
@ -259,6 +275,10 @@ window.Page_zuchthunde = (() => {
title="Stammbaum">
${UI.icon('tree-structure')} Stammbaum
</button>
${_appState.user?.ki_zucht_beschreibung !== 0 ? `
<button class="btn btn-ghost btn-sm zh-ki-desc-btn" data-id="${h.id}">
${UI.icon('sparkle')} Beschreibung
</button>` : ''}
<button class="btn btn-ghost btn-sm zh-link-btn" data-id="${h.id}"
title="Profil-Link kopieren">
${UI.icon('link-simple')}
@ -1134,6 +1154,39 @@ window.Page_zuchthunde = (() => {
}).join('')
: `<li style="color:var(--c-text-muted)">Keine gemeinsamen Vorfahren gefunden.</li>`;
const welfare = result.welfare;
let welfareHTML = '';
if (welfare) {
const wColor = { ok: '#16a34a', info: '#3b82f6', warning: '#f59e0b', critical: '#dc2626' }[welfare.level] || '#6b7280';
const wTitle = { ok: 'Alles prima', info: 'Hinweis', warning: 'Bitte beachten', critical: 'Kritischer Hinweis' }[welfare.level];
const wIcon = { ok: 'check-circle', info: 'info', warning: 'warning', critical: 'warning-circle' }[welfare.level];
const wIssueHTML = (welfare.issues || []).map(i => `
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(0,0,0,.06)">
<span style="color:${wColor};flex-shrink:0">${UI.icon('warning')}</span>
<span style="font-size:var(--text-sm)">${_esc(i.text)}</span>
</div>`).join('');
const wOkHTML = (welfare.ok_points || []).map(p => `
<div style="display:flex;gap:8px;padding:4px 0">
<span style="color:#16a34a;flex-shrink:0">${UI.icon('check')}</span>
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">${_esc(p)}</span>
</div>`).join('');
welfareHTML = `
<div>
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm);margin-bottom:var(--space-2);
color:${wColor}">
${UI.icon(wIcon)} Tierschutz-Check: ${wTitle}
</div>
<div style="background:${wColor}18;border:1.5px solid ${wColor}40;border-radius:var(--radius-md);
padding:var(--space-3)">
${wIssueHTML || ''}
${wOkHTML}
</div>
</div>`;
}
const body = `
<div style="display:flex;flex-direction:column;gap:var(--space-4)">
<div style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);
@ -1149,6 +1202,7 @@ window.Page_zuchthunde = (() => {
</div>
</div>
</div>
${welfareHTML}
<div>
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm);margin-bottom:var(--space-2)">
Gemeinsame Vorfahren
@ -1159,11 +1213,22 @@ window.Page_zuchthunde = (() => {
</div>
</div>`;
const kiPaarungBtn = _appState?.user?.ki_zucht_paarung !== 0
? `<button type="button" class="btn btn-secondary btn-sm" id="trial-ki-btn">
${UI.icon('sparkle')} KI-Analyse anfordern
</button>`
: '';
const footer = `
<button type="button" class="btn btn-secondary flex-1" id="zhresult-back">
${UI.icon('arrow-left')} Zurück
</button>
<button type="button" class="btn btn-primary flex-1" id="zhresult-close">Schließen</button>`;
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
${kiPaarungBtn}
<div style="display:flex;gap:var(--space-2)">
<button type="button" class="btn btn-secondary flex-1" id="zhresult-back">
${UI.icon('arrow-left')} Zurück
</button>
<button type="button" class="btn btn-primary flex-1" id="zhresult-close">Schließen</button>
</div>
</div>`;
UI.modal.open({
title: `${UI.icon('dna')} Ergebnis Probeverpaarung`,
@ -1173,6 +1238,164 @@ window.Page_zuchthunde = (() => {
document.getElementById('zhresult-close')?.addEventListener('click', UI.modal.close);
document.getElementById('zhresult-back')?.addEventListener('click', () => _showTrialMatingModal());
document.getElementById('trial-ki-btn')?.addEventListener('click', () => _showKiPaarung(result));
}
// ----------------------------------------------------------
// KI: Hund-Beschreibung
// ----------------------------------------------------------
async function _showKiDesc(hundId) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Hunde-Beschreibung`,
body: `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-6)">KI erstellt Beschreibung…</p>`,
footer: '',
});
let text = '';
try {
const result = await API.zuchtKi.hundBeschreibung(hundId);
text = result.text || result.content || result.beschreibung || JSON.stringify(result);
} catch (err) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Hunde-Beschreibung`,
body: `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;
}
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Hunde-Beschreibung`,
body: `<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>`,
footer: `
<button class="btn btn-secondary flex-1" id="ki-desc-copy">
${UI.icon('clipboard-text')} Kopieren
</button>
<button class="btn btn-primary flex-1" data-modal-close>Schließen</button>`,
});
document.getElementById('ki-desc-copy')?.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(text);
UI.toast.success('Text kopiert.');
} catch {
UI.toast.error('Kopieren nicht möglich.');
}
});
}
// ----------------------------------------------------------
// KI: Jahresbericht
// ----------------------------------------------------------
async function _showJahresbericht() {
UI.modal.open({
title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
body: `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-6)">KI analysiert deine Zuchtkartei…</p>`,
footer: '',
});
let text = '';
try {
const result = await API.zuchtKi.jahresbericht();
text = result.text || result.content || result.bericht || JSON.stringify(result);
} catch (err) {
UI.modal.open({
title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
body: `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;
}
UI.modal.open({
title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
body: `<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>`,
footer: `
<button class="btn btn-secondary flex-1" id="ki-bericht-copy">
${UI.icon('clipboard-text')} Kopieren
</button>
<button class="btn btn-primary flex-1" data-modal-close>Schließen</button>`,
});
document.getElementById('ki-bericht-copy')?.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(text);
UI.toast.success('Bericht kopiert.');
} catch {
UI.toast.error('Kopieren nicht möglich.');
}
});
}
// ----------------------------------------------------------
// KI: Paarungsanalyse
// ----------------------------------------------------------
async function _showKiPaarung(trialResult) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Paarungsanalyse`,
body: `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-6)">KI analysiert die Verpaarung…</p>`,
footer: '',
});
let result;
try {
result = await API.zuchtKi.paarungAnalyse(
trialResult.vater_id,
trialResult.mutter_id,
trialResult.ik_prozent,
trialResult.welfare?.level
);
} catch (err) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Paarungsanalyse`,
body: `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;
}
const empfehlung = result.empfehlung || result.recommendation || '';
const text = result.text || result.content || result.analyse || JSON.stringify(result);
const empfehlungColor = {
empfohlen: '#16a34a',
bedingt: '#f59e0b',
nicht_empfohlen: '#dc2626',
}[empfehlung] || '#6b7280';
const empfehlungLabel = {
empfohlen: 'Empfohlen',
bedingt: 'Bedingt empfohlen',
nicht_empfohlen: 'Nicht empfohlen',
}[empfehlung] || empfehlung;
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Paarungsanalyse`,
body: `
<div style="display:flex;flex-direction:column;gap:var(--space-4)">
${empfehlung ? `
<div style="padding:var(--space-3);border-radius:var(--radius-md);
background:${empfehlungColor}18;border:1.5px solid ${empfehlungColor}40;
font-weight:var(--weight-semibold);color:${empfehlungColor}">
${UI.icon('check-circle')} ${_esc(empfehlungLabel)}
</div>` : ''}
<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>
</div>`,
footer: `
<button class="btn btn-secondary flex-1" id="ki-paarung-copy">
${UI.icon('clipboard-text')} Kopieren
</button>
<button class="btn btn-primary flex-1" data-modal-close>Schließen</button>`,
});
document.getElementById('ki-paarung-copy')?.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(text);
UI.toast.success('Analyse kopiert.');
} catch {
UI.toast.error('Kopieren nicht möglich.');
}
});
}
// ----------------------------------------------------------

View file

@ -3,15 +3,16 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ban Yaro — Die deutschsprachige Hunde-Plattform</title>
<meta name="description" content="Ban Yaro ist die kostenlose All-in-One Hunde-App für Deutschland, Österreich und die Schweiz. Tagebuch, Impfpass, Giftköder-Alarm, Gassi-Community, Hundesitting, Trainings-Tracker — DSGVO-konform, ohne App Store.">
<title>Ban Yaro — Hunde-App für Besitzer, Züchter & Welpen-Käufer</title>
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für Deutschland, Österreich und die Schweiz. Tagebuch, Impfpass, Wurfbörse, Stammbaum, Inzucht-Check, Giftköder-Alarm — DSGVO-konform, ohne App Store.">
<meta name="keywords" content="Hunde App, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://banyaro.app/info">
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:title" content="Ban Yaro — Die deutschsprachige Hunde-Plattform">
<meta property="og:description" content="Alles rund um deinen Hund — Tagebuch, Impfpass, Giftköder-Alarm, Gassi-Community, Hundesitting. Kostenlos, DSGVO-konform, ohne App Store.">
<meta property="og:title" content="Ban Yaro — Hunde-App für Besitzer, Züchter & Welpen-Käufer">
<meta property="og:description" content="Tagebuch, Impfpass, Wurfbörse, Stammbaum, Inzucht-Koeffizient, Giftköder-Alarm, Gassi-Community — alles in einer DSGVO-konformen App ohne App Store.">
<meta property="og:url" content="https://banyaro.app/info">
<meta property="og:image" content="https://banyaro.app/icons/icon-512.png">
<meta property="og:locale" content="de_DE">
@ -19,8 +20,8 @@
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Ban Yaro — Die deutschsprachige Hunde-Plattform">
<meta name="twitter:description" content="Alles rund um deinen Hund — Tagebuch, Impfpass, Giftköder-Alarm, Gassi-Community. Kostenlos, DSGVO-konform.">
<meta name="twitter:title" content="Ban Yaro — Hunde-App für Besitzer, Züchter & Welpen-Käufer">
<meta name="twitter:description" content="Wurfbörse, Stammbaum, Inzucht-Koeffizient, Tierschutz-Check — und alles rund um deinen Hund. Kostenlos, DSGVO-konform.">
<meta name="twitter:image" content="https://banyaro.app/icons/icon-512.png">
<!-- Structured Data -->
@ -30,7 +31,7 @@
"@type": "MobileApplication",
"name": "Ban Yaro",
"alternateName": "Ban Yaro — Die Hunde-Plattform",
"description": "Ban Yaro ist die kostenlose, deutschsprachige All-in-One Hunde-App. Digitales Tagebuch, Impfpass, Giftköder-Alarm, Gassi-Community, Hundesitting und mehr — DSGVO-konform, ohne App Store.",
"description": "Ban Yaro ist die kostenlose, deutschsprachige All-in-One Hunde-App für Hundebesitzer und Züchter. Tagebuch, Impfpass, Wurfbörse, Stammbaum, Inzucht-Koeffizient, Tierschutz-Check, Giftköder-Alarm, Gassi-Community — DSGVO-konform, ohne App Store.",
"url": "https://banyaro.app",
"applicationCategory": "LifestyleApplication",
"applicationSubCategory": "PetApplication",
@ -69,11 +70,19 @@
"Virtueller KI-Trainer mit täglichen Übungsempfehlungen und Fortschrittsprognose",
"Wöchentlicher KI-Lober — jeden Montag 2-3 Sätze Lob für die Vorwoche",
"Trainings-Gamification: Streaks, Abzeichen, Trainingskalender",
"Kommandos & Fähigkeiten im Hundeprofil — praktisch für Hundesitter"
"Kommandos & Fähigkeiten im Hundeprofil — praktisch für Hundesitter",
"Wurfbörse — öffentliche Wurfankündigungen mit Filtersuche nach Rasse und Status",
"Züchter-Profile mit verifizierten Gesundheitstests und Gentests",
"Stammbaum-Visualisierung bis 4 Generationen",
"Inzucht-Koeffizient nach Wright's Formel mit Ampel-Bewertung",
"Probeverpaarung mit IK-Simulation und genetischer Risikoanalyse",
"Tierschutz-Check automatisch bei jeder Verpaarung — nicht abschaltbar",
"KI-Züchter-Assistenz: Wurfankündigungen, Genetik-Erklärung, Paarungsanalyse",
"Datenexport als HTML und ODS — keine Datenfalle"
],
"screenshot": "https://banyaro.app/icons/icon-512.png",
"softwareVersion": "2.0",
"datePublished": "2026-04-25",
"softwareVersion": "2.1",
"datePublished": "2026-04-28",
"areaServed": ["DE", "AT", "CH"],
"audience": {
"@type": "Audience",
@ -386,6 +395,7 @@
<div class="container">
<span class="nav-brand">Ban Yaro</span>
<a href="#funktionen">Funktionen</a>
<a href="#zuechter">Züchter</a>
<a href="#vergleich">Vergleich</a>
<a href="#preise">Preise</a>
<a href="#warum">Warum Ban Yaro?</a>
@ -516,6 +526,67 @@
</div>
</div>
<div class="feature-group">
<div class="feature-group-label">Für Züchter</div>
<div class="feature-grid">
<div class="feature-card">
<span class="feature-icon">🐾</span>
<div><h3>Wurfbörse</h3><p>Öffentliche Wurfankündigungen mit Filter nach Rasse und Status. Interessenten schreiben direkt per Nachricht an. Für Käufer kostenlos.</p><span class="feature-tag" style="background:#7c3aed22;color:#7c3aed">Züchter</span></div>
</div>
<div class="feature-card">
<span class="feature-icon">🌳</span>
<div><h3>Stammbaum</h3><p>4 Generationen visuell dargestellt. Klickbare Knoten öffnen das Hunde-Profil. Teilen per Link für Käufer-Dokumentation.</p><span class="feature-tag" style="background:#7c3aed22;color:#7c3aed">Züchter</span></div>
</div>
<div class="feature-card">
<span class="feature-icon">🧬</span>
<div><h3>Inzucht-Koeffizient</h3><p>Automatische Berechnung nach Wright's Formel. Ampel-Bewertung: optimal unter 2,5%, kritisch ab 12,5%. Probeverpaarung simuliert jeden beliebigen Anpaarungspartner.</p><span class="feature-tag" style="background:#7c3aed22;color:#7c3aed">Züchter</span></div>
</div>
<div class="feature-card">
<span class="feature-icon">🩺</span>
<div><h3>Gesundheitsdokumentation</h3><p>HD, ED, Augen, Herz, DNA-Tests — alle Nachweise strukturiert erfasst. Farbcodierte Ergebnis-Badges auf einen Blick.</p><span class="feature-tag" style="background:#7c3aed22;color:#7c3aed">Züchter</span></div>
</div>
<div class="feature-card">
<span class="feature-icon">🛡️</span>
<div><h3>Tierschutz-Check</h3><p>Automatische Prüfung bei jeder Verpaarung: Alter, Wurfhäufigkeit, Deckpause, genetische Risiken. Nicht abschaltbar — weil die Tiere zählen.</p><span class="feature-tag" style="background:#7c3aed22;color:#7c3aed">Züchter</span></div>
</div>
<div class="feature-card">
<span class="feature-icon">🤖</span>
<div><h3>KI-Assistenz</h3><p>Wurfankündigungen schreiben, Genetik-Erklärungen für Käufer formulieren, Paarungsanalyse mit Empfehlung, Jahresauswertung. Nutzt Claude Sonnet direkt.</p><span class="feature-tag" style="background:#7c3aed22;color:#7c3aed">Züchter</span></div>
</div>
<div class="feature-card">
<span class="feature-icon">📊</span>
<div><h3>Datenexport</h3><p>Alle Zuchtkartei-Daten als HTML-Dossier (druckbar, mit Stammbaum-Visualisierung) und ODS-Tabelle (editierbar in LibreOffice/Excel). Keine Datenfalle.</p><span class="feature-tag" style="background:#7c3aed22;color:#7c3aed">Züchter</span></div>
</div>
<div class="feature-card">
<span class="feature-icon">📄</span>
<div><h3>Kaufvertrag</h3><p>Automatisch ausgefüllter Kaufvertrag pro Welpe als druckbares Dokument — mit Chip-Nummer, Geburtsdatum, Käufer- und Züchterdaten.</p><span class="feature-tag" style="background:#7c3aed22;color:#7c3aed">Züchter</span></div>
</div>
</div>
</div>
</div>
</section>
<section id="zuechter" style="background: linear-gradient(135deg, #7c3aed08 0%, #a78bfa10 100%); border-top: 1px solid #ede9fe; border-bottom: 1px solid #ede9fe;">
<div class="container">
<h2>Die Plattform für verantwortungsvolle Züchter</h2>
<p class="section-intro">Ban Yaro ist die erste Hunde-App die Zucht-Management, Tierschutz-Checks und KI-Assistenz in einer Plattform verbindet — gedacht für Züchter die ihre Tiere ernst nehmen.</p>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 2rem; margin-top: 2rem;">
<div style="background: white; border-radius: 12px; padding: 1.5rem; border: 1px solid #ede9fe;">
<div style="font-size: 2rem; margin-bottom: 0.75rem;">🐕‍🦺</div>
<h3 style="color: #7c3aed; margin-bottom: 0.5rem;">Für Käufer</h3>
<p style="color: #4b5563; font-size: 0.95rem; line-height: 1.6;">Finde deinen Welpen in der Wurfbörse mit vollem Einblick in Gesundheitstests, Gentests und Stammbaum der Eltern. Schreibe direkt mit dem Züchter. Keine versteckten Händler.</p>
</div>
<div style="background: white; border-radius: 12px; padding: 1.5rem; border: 1px solid #ede9fe;">
<div style="font-size: 2rem; margin-bottom: 0.75rem;"></div>
<h3 style="color: #7c3aed; margin-bottom: 0.5rem;">Transparenz als Standard</h3>
<p style="color: #4b5563; font-size: 0.95rem; line-height: 1.6;">Verifizierte Züchter-Profile mit öffentlich sichtbaren Gesundheitsdaten. Der Tierschutz-Check läuft bei jeder Verpaarung automatisch — Ergebnisse gehen direkt an den Admin wenn kritische Grenzen überschritten werden.</p>
</div>
</div>
</div>
</section>

View file

@ -1,6 +1,6 @@
# Ban Yaro — Die deutschsprachige Hunde-Plattform
# https://banyaro.app
# Letzte Aktualisierung: 2026-04-25
# Letzte Aktualisierung: 2026-04-28
## Was ist Ban Yaro?
@ -15,11 +15,12 @@ Ban Yaro ist kostenlos nutzbar (Freemium-Modell). Die App ist auf allen Smartpho
## Zielgruppe
- Deutschsprachige Hundebesitzer (Deutschland, Österreich, Schweiz)
- Verantwortungsvolle Hundezüchter (VDH und andere Verbände)
- Welpen-Interessenten und Käufer
- Hundeschulen und Hundetrainer
- Tierärzte und Praxen
- Züchter
## Funktionen (aktuell verfügbar)
## Funktionen
### Hunde-Profil & Tagebuch
- Digitales Hunde-Profil (Name, Rasse, Geburtstag, Foto, Chip-Nummer)
@ -37,175 +38,148 @@ Ban Yaro ist kostenlos nutzbar (Freemium-Modell). Die App ist auf allen Smartpho
- Printbarer Heimtierausweis (PDF)
### Pflege-System
- 43 rassenspezifische Pflegetipps in 10 Kategorien: Fell, Krallen, Zähne, Ohren, Augen, Pfoten, Parasiten, Saisonal, Gesundheitsvorsorge, Welpen-Pflege
- Fell-Typ-Unterscheidung: kurz / lang / lockig / Doppelmantel
- Unterscheidung Schneiden vs. Trimmen
- Tipp des Tages automatisch nach Rasse und Fell-Typ ausgewählt
- Rassen-Autocomplete im Profil verknüpft mit Pflege-Tipps
- 43 rassenspezifische Pflegetipps in 10 Kategorien
- Fell-Typ-Unterscheidung, Schneiden vs. Trimmen
- Tipp des Tages automatisch nach Rasse ausgewählt
### Training & KI-Trainer
- Tägliches Trainings-Tagebuch (Wiederholungen, Erfolgsquote 0100%, Hundestimmung, Zufriedenheit)
- Übungsfortschritt in 5 Stufen — von "noch nicht gezeigt" bis "sitzt sicher"
- Virtueller KI-Trainer: analysiert letzte 20 Sessions, empfiehlt täglich welche Übungen anstehen
- Fortschrittsprognose bis zur Meisterschaft (Trendanalyse)
- 104 Übungen in 7 Kategorien
- KI-Trainingsplan erstellen (Plus-Feature)
- Trainingskalender im Habit-Tracker-Stil
- Gamification: Streaks, Abzeichen, XP
- Kommandos & Fähigkeiten sichtbar im Hunde-Profil (für Hundesitter)
- Tägliches Trainings-Tagebuch (Wiederholungen, Erfolgsquote, Hundestimmung)
- Übungsfortschritt in 5 Stufen, 104 Übungen in 7 Kategorien
- Virtueller KI-Trainer: analysiert letzte 20 Sessions, tägliche Empfehlung
- Fortschrittsprognose bis zur Meisterschaft
- Gamification: Streaks, Abzeichen, Trainingskalender
### Wöchentlicher Lober (KI)
- Jeden Montag schreibt die KI automatisch 2-3 Sätze Lob für die Trainingsvorwoche
- Nur Lob, kein Rat, kein Druck — positive Bestärkung
- Basiert auf den geloggten Trainingseinheiten der Vorwoche
### Züchter-Plattform (vollständig)
### Wetter & Zecken-Warnung
- Wetter-Chip direkt in der App (Open-Meteo API, ohne API-Key)
- Zecken-Warnung regelbasiert: aktiv MärzOktober bei Temperatur >7°C
- Push-Benachrichtigungen für Zecken-Saison
Ban Yaro ist die erste Hunde-App mit vollständiger Züchter-Unterstützung:
### Giftköder-Alarm
- Giftköder-Meldungen mit GPS-Koordinaten und Foto
- Push-Benachrichtigung für alle Nutzer im konfigurierbaren Umkreis
- Interaktive Karte (OpenStreetMap/Leaflet)
- Automatisches Ablaufdatum nach 7 Tagen
**Züchter-Verifizierung:**
- Antrag mit Dokumenten-Upload (VDH-Ausweis, Zuchtzulassung)
- Admin-Prüfung und Freischaltung
- Verifiziertes Züchter-Profil mit öffentlicher Seite (banyaro.app/breeder/{zwingername})
### Sicherheit & Community-Alerts
- Verlorener Hund: Alert mit Foto und letzter GPS-Position
- Nearby-Alerts: Push-Benachrichtigungen für Ereignisse in der Nähe
**Wurfbörse:**
- Öffentliche Wurfankündigungen für alle Nutzer zugänglich (banyaro.app/wurfboerse)
- Filtersuche nach Rasse und Status (geplant / verfügbar / geboren)
- Käufer schreiben direkt per integriertem Chat an den Züchter
- Vollständige Eltern-Dokumentation sichtbar: Gesundheitstests, Gentests, Stammbaum
### NFC-Halsband-Tags
- Jeder Hund hat eine öffentliche URL (ohne Login sichtbar)
- "Ich habe diesen Hund gefunden"-Button → Besitzer bekommt Push-Benachrichtigung
- Notfallkontakt ohne Telefonnummer preiszugeben
- Physische NFC-Tags erhältlich (Shop)
**Wurfverwaltung:**
- CRUD für Würfe und einzelne Welpen
- Gewichtsverlauf pro Welpe
- Foto-System mit Sichtbarkeits-Stufen: öffentlich / nach Anfrage / privat
- Automatisch ausgefüllter Kaufvertrag als druckbares HTML-Dokument
### Gassi-Community
- Gassi-Treffen erstellen und beitreten
- GPS-Routen aufzeichnen und teilen (mit Anti-Cheat-Validierung)
- Routen bewerten (Untergrund, Schatten, Leinenpflicht, Sicherheit)
- Beliebte Routen entdecken
**Zuchtkartei:**
- Hunde-Stammdaten: Name, Rufname, Chip, Zuchtbuchnummer, Eltern (Vater/Mutter-Verknüpfung)
- Gesundheitstests: HD, ED, OCD, Augen, Herz, Patella, ZTP — mit farbigen Ergebnis-Badges
- Genetische Tests: MDR1, PRA, DM, vWD und weitere DNA-Marker (clear/carrier/affected)
- Titel & Auszeichnungen: CAC, CACIB, BOB, IPO, BH — chronologisch mit Richter und Ort
### Hundesitting-Netzwerk
- Sitter-Profile mit Erfahrung und Bewertungen
- Buchungsanfragen und Kalender
- Nur 8% Provision (vs. 20% bei Rover/Pawshake)
- Bewertungen verifizierter Buchungen
**Stammbaum:**
- Visualisierung bis 4 Generationen als horizontales CSS-Grid
- Klickbare Knoten navigieren zum jeweiligen Hunde-Profil
- Teilen-Link für Käufer-Dokumentation
- Öffentliches Hunde-Profil (banyaro.app/zucht-profil?id={id})
### Forum
- Rassen-basierte Foren
- KI-Zusammenfassung langer Threads
- Experten-Badge (Tierarzt, Trainer)
**Inzucht-Koeffizient:**
- Automatische Berechnung nach Wright's Formel (bis 8 Generationen)
- Ampel-Bewertung: optimal <2,5% / akzeptabel <6,25% / erhöht <12,5% / kritisch ≥12,5%
- Probeverpaarung: simuliert beliebige Anpaarung ohne Speicherung
### Hunde-Wiki — Rassendatenbank
- 1003 Hunderassen, 97,6% KI-angereichert via Wikipedia-grounded Recherche
- Inhalte: Charakter, Größe, Aktivität, Eignung, Lebensdauer, Temperament
- Community-Fotos im Wiki: User können Fotos einreichen (mit Bildrechte-Bestätigung)
- Moderatoren geben Community-Fotos frei, anschließend Galerie-Ansicht
- Wiki-Foto-Badge als Gamification-Belohnung für Foto-Einreicher
- "Passt diese Rasse zu mir?" Quiz für angehende Hundebesitzer
**Tierschutz-Check (immer aktiv, nicht abschaltbar):**
- Läuft automatisch bei jeder Verpaarung und jedem neuen Wurf
- Prüft: IK, Alter der Zuchthündin (min. 18 Monate), Deckpause (min. 12 Monate),
Wurfanzahl (max. 4 empfohlen, kritisch ab 6), genetische Risiken
- Farbcodierte Rückmeldung: grün (alles ok) / gelb (Hinweis) / rot (kritisch)
- Bei "trotzdem fortfahren" auf rotem Befund: automatische Admin-Benachrichtigung
- Philosophie: informieren statt blockieren, aber volle Transparenz und Accountability
### Hunde-Knigge
- Ratgeber für Begegnungen (fremder Hund, Kinder, Radfahrer)
- Regeln in ÖPNV und öffentlichen Orten
- Haftpflicht-Ratgeber
**KI-Züchter-Assistenz:**
- Wurfankündigungen schreiben (KI generiert Text aus Eltern-Profilen)
- Genetik-Erklärung für Käufer (verständliche Sprache) und Züchter (fachlich)
- Paarungsanalyse mit Empfehlung (empfohlen / bedingt / nicht empfohlen)
- Hunde-Beschreibungen für öffentliche Profile
- Jahresbericht mit Trends und Empfehlungen
- Privilegierte Rollen (Züchter, Moderatoren, Admins) nutzen Claude Sonnet direkt
### Events & Kultur
- Agility-Turniere und Hundeausstellungen (VDH-Import)
**Datenexport:**
- Vollständiger Export als ZIP: HTML-Dossier (druckbar, Stammbaum-Visualisierung)
und ODS-Tabelle (editierbar in LibreOffice/Excel)
- 7 Tabellenblätter: Hunde, Gesundheitstests, Gentests, Titel, Würfe, Welpen, Gewichte
- Keine Datenfalle: Züchter können jederzeit alle eigenen Daten exportieren
### Community-Features
- Giftköder-Alarm mit Push-Benachrichtigungen
- Verlorener Hund Alarm
- Gassi-Treffen organisieren und finden
- GPS-Routen aufzeichnen, teilen, bewerten
- Hundesitting-Netzwerk (nur 8% Provision vs. 20% bei Rover/Pawshake)
- Forum mit Rassen-basierten Unterforen
- Direktnachrichten / Chat
- Freundschaften und Nutzer-Profile
### Wissen
- Hunde-Wiki: 1003 Hunderassen, Wikipedia-grounded, KI-angereichert
- Community-Fotos mit Bildrechte-Bestätigung und Moderation
- Hunde-Knigge (Begegnungen, ÖPNV, Haftpflicht)
- Hundefilme-Datenbank mit "Stirbt der Hund?"-Rubrik
- Veranstaltungskalender
### Hundefreundliche Orte
- Crowd-sourced Datenbank hundefreundlicher Orte
- Restaurants, Parks, Geschäfte
- Detaillierte Bewertungen
### Gamification & Push
- Badges, Streaks, XP — trägt zur Nutzerbindung bei
- Wiki-Foto-Badge für Community-Foto-Beiträge
- Push-Notifications für Alerts, Erinnerungen, Wöchentlicher Lober
- Offline-Modus via Service Worker
- Erste Hilfe Notfallratgeber
## KI-Integration
Ban Yaro nutzt KI an mehreren Stellen der Plattform:
- **Lokale KI**: LM Studio (Gemma-4-31B) auf eigenem Server — für datenschutzkritische Anfragen
- **Cloud-KI**: Claude (Anthropic, Modell: claude-sonnet-4-6) als Fallback und für rechenintensive Aufgaben
- **Symptom-Checker**: KI-gestützte Ersteinschätzung (kostenlos)
- **Virtueller KI-Trainer**: Analysiert letzte 20 Trainings-Sessions, erstellt täglich priorisierte Übungsempfehlung
- **Wöchentlicher Lober**: Vollautomatisch jeden Montag per APScheduler, lobt die Vorwoche in 2-3 Sätzen
- **Breed-Enricher**: Wikipedia-grounded Anreicherung von 1003 Rassen-Datensätzen (97,6% abgeschlossen)
- **KI-Trainingsplan** (Plus-Feature): Erstellt individuellen Trainingsplan auf Basis von Hund und Fortschritt
Ban Yaro nutzt KI an mehreren Stellen:
- **Privilegierte Nutzer** (Züchter, Moderatoren, Admins): Claude Sonnet (Anthropic) primär
- **Standard-Nutzer**: Lokales LLM (LM Studio, Gemma-4-31B) primär, Claude als Fallback
- **Tierschutz-Check**: Regelbasiert, keine KI — läuft immer zuverlässig
- **Symptom-Checker, KI-Trainer, Lober**: Für alle kostenfrei
- **Züchter-KI**: Wurfankündigungen, Genetik-Erklärungen, Paarungsanalyse, Jahresbericht
## Technologie
- Progressive Web App (PWA) — installierbar ohne App Store
- Offline-fähig via Service Worker (Cache-Strategie mit Versionierung)
- Backend: Python/FastAPI + SQLite
- Frontend: Vanilla JS, kein Framework
- Karten: Leaflet.js + OpenStreetMap (kein Google Maps, kein API-Key)
- Wetter: Open-Meteo (kein API-Key erforderlich)
- Karten: Leaflet.js + OpenStreetMap
- Hosting: Deutschland (DSGVO-konform)
- Analytics: Umami v2 (cookieless, DSGVO-konform)
- KI lokal: LM Studio (Gemma-4-31B) auf eigenem Server
- KI lokal: LM Studio (Gemma-4-31B)
- KI Cloud: Claude API (claude-sonnet-4-6, Anthropic)
- Push-Notifications: Web Push (VAPID)
## Monetarisierung
**Kostenlos (immer):**
- Hunde-Profile
- Tagebuch (unbegrenzte Einträge)
- Pflege-System (43 rassenspezifische Tipps)
- Symptom-Checker (KI)
- Giftköder-Alarm & Zecken-Warnung
- Verlorener Hund Alarm
- Wiki & Knigge (1003 Rassen)
- Training-Logging & KI-Trainer
- Wöchentlicher Lober
- Forum & Community
- Gassi-Treffen & Routen
- NFC-Halsband-Profil
- Heimtierausweis (Druck)
**Kostenlos:**
- Alle Basis-Features inkl. Züchter-Antrag, Wurfverwaltung, Stammbaum, Tierschutz-Check
**Ban Yaro Plus (ca. 4,99 €/Monat) — in Entwicklung:**
- Alles aus Kostenlos
- KI-Trainingsplan erstellen
- Erweiterte Statistiken & Fortschrittsanalyse
**Züchter-Provision** (geplant): Wurfbörse bleibt für Käufer kostenlos
**Provisionen:**
- Hundesitting: 8% Provision (Rover/Pawshake: 20%)
**Ban Yaro Plus** (ca. 4,99 €/Monat, in Entwicklung):
- KI-Trainingsplan, erweiterte Statistiken
**Physische Produkte:**
- NFC-Halsband-Tags (ab ca. 6 €)
**Hundesitting**: 8% Provision
## Community-Features
## Öffentliche Seiten (ohne Login)
- Forum mit Rassen-basierten Unterforen
- Community-Fotos im Rassen-Wiki (Einreichung, Moderation, Freigabe)
- Gassi-Treffen organisieren und finden
- GPS-Routen teilen und bewerten
- Hundesitting-Netzwerk (Bewertungen, verifizierte Buchungen)
- Gamification: Badges, Streaks, XP, Wiki-Foto-Badge
- https://banyaro.app — Landing Page
- https://banyaro.app/info — Landing Page (Alias)
- https://banyaro.app/wiki/rassen — Alle Hunderassen
- https://banyaro.app/wiki/rasse/{slug} — Rassen-Detail
- https://banyaro.app/wurfboerse — Öffentliche Wurfbörse (Welpen suchen)
- https://banyaro.app/breeder/{zwingername} — Öffentliches Züchter-Profil
- https://banyaro.app/knigge — Hunde-Knigge
- https://banyaro.app/hund/{id} — Öffentliches Hunde-Profil (NFC-Tag)
## Vergleich mit Konkurrenz
## Öffentliche APIs
| Funktion | Ban Yaro | Dogorama | PetDesk | Tractive |
|----------|----------|----------|---------|----------|
| Kostenlos nutzbar | Ja | Begrenzt | Nein | Nein |
| DSGVO / EU-Hosting | Ja | Nein | Nein | Teilweise |
| Giftköder-Alarm | Ja | Nein | Nein | Nein |
| Gassi-Community | Ja | Ja | Nein | Nein |
| Hundesitting | Ja (8%) | Nein | Nein | Nein |
| Digitaler Impfpass | Ja | Nein | Ja | Nein |
| NFC-Halsband-Tag | Ja | Nein | Nein | Nein |
| Pflege-Tipps rassenspezifisch | Ja | Nein | Nein | Nein |
| Rassen-Wiki (1003, KI-angereichert) | Ja | Nein | Nein | Nein |
| Symptom-Checker (kostenlos) | Ja | Nein | Nein | Nein |
| Offline-Modus | Ja | Nein | Nein | Nein |
| Kein App Store | Ja | Nein | Nein | Nein |
| Sitting-Provision | 8% | | | |
- GET https://banyaro.app/api/wiki/rassen — Liste aller Hunderassen
- GET https://banyaro.app/api/wiki/rassen/{slug} — Rassen-Detail
- GET https://banyaro.app/api/litters — Öffentliche Wurfankündigungen
- GET https://banyaro.app/api/breeder/profil/{zwingername} — Züchter-Profil
- GET https://banyaro.app/api/events — Aktuelle Hundeevents
- GET https://banyaro.app/api/poison — Aktuelle Giftköder-Meldungen
- GET https://banyaro.app/api/lost — Aktuelle Vermisst-Meldungen
- GET https://banyaro.app/api/knigge/articles — Hunde-Knigge Artikel
- GET https://banyaro.app/api/stats — Community-Statistiken
## Domains
@ -216,14 +190,3 @@ Ban Yaro nutzt KI an mehreren Stellen der Plattform:
Website: https://banyaro.app
E-Mail: Über das Kontaktformular in der App
## Öffentliche Daten-APIs (keine Authentifizierung nötig)
- GET https://banyaro.app/api/wiki/rassen — Liste aller Hunderassen (1003 Einträge)
- GET https://banyaro.app/api/wiki/rassen/{slug} — Details zu einer Rasse
- GET https://banyaro.app/api/events — Aktuelle Hundeevents
- GET https://banyaro.app/api/poison — Aktuelle Giftköder-Meldungen
- GET https://banyaro.app/api/lost — Aktuelle Vermisst-Meldungen
- GET https://banyaro.app/api/knigge/articles — Hunde-Knigge Artikel
- GET https://banyaro.app/api/movies/list — Hundefilme-Datenbank
- GET https://banyaro.app/api/stats — Community-Statistiken

View file

@ -4,6 +4,9 @@ Allow: /info
Allow: /wiki/rassen
Allow: /wiki/rasse/
Allow: /hund/
Allow: /breeder/
Allow: /wurfboerse
Allow: /knigge
Disallow: /api/
Disallow: /ausweis/
Disallow: /teilen/

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v465';
const CACHE_VERSION = 'by-v474';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten