Session 2026-04-21: SEO, Wiki-Anreicherung, Training, Lober
SEO & Crawler:
- robots.txt, llms.txt, sitemap.xml (508 Seiten bei Google)
- SSR-Seiten: /info, /wiki/rassen, /wiki/rasse/{slug}, /knigge
- Open Graph, JSON-LD, Breadcrumbs in index.html
Navigation:
- Training unter "Mein Hund", Wissen collapsible
- Welcome-Seite und Landing-Page auf 5-Gruppen-Struktur
Wiki:
- KI-Anreicherung (Claude API): beschreibung, vorkommen_de, Steckbrief
- "So einen hab ich" / Züchter-Verzeichnis
- Scheduler: 50 Rassen beim Start, 20/Nacht
Training:
- Session-Logging (Erfolgsquote, Stimmung, Zufriedenheit)
- Virtueller KI-Trainer (6h-Cache)
- Trainingskalender (Habit-Tracker)
- Top-Training → automatischer Tagebucheintrag
- Gamification ohne Druck: Badges, Streak, Stats
Fortschritts-Lober:
- Jeden Montag 09:00: Claude schreibt Lob-Text pro Hund
- Push + Karte im Tagebuch
Monitoring:
- 4× täglich Status-Mail mit Scheduler-Status + Wiki-Fortschritt
This commit is contained in:
parent
65d1cf6c7f
commit
180de32e57
22 changed files with 4351 additions and 189 deletions
|
|
@ -393,6 +393,313 @@ window.Page_wiki = (() => {
|
|||
`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// API-Funktionen: Interesse / Stats / Züchter
|
||||
// ----------------------------------------------------------
|
||||
async function _fetchStats(slug) {
|
||||
const r = await fetch(`/api/wiki/rassen/${encodeURIComponent(slug)}/stats`, { credentials: 'include' });
|
||||
return r.ok ? r.json() : null;
|
||||
}
|
||||
|
||||
async function _setInteresse(slug, typ) {
|
||||
const r = await fetch(`/api/wiki/rassen/${encodeURIComponent(slug)}/interesse`, {
|
||||
method: 'POST', credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ typ }),
|
||||
});
|
||||
return r.ok ? r.json() : null;
|
||||
}
|
||||
|
||||
async function _deleteInteresse(slug) {
|
||||
const r = await fetch(`/api/wiki/rassen/${encodeURIComponent(slug)}/interesse`, {
|
||||
method: 'DELETE', credentials: 'include',
|
||||
});
|
||||
return r.ok ? r.json() : null;
|
||||
}
|
||||
|
||||
async function _fetchZuchter(slug) {
|
||||
const r = await fetch(`/api/wiki/rassen/${encodeURIComponent(slug)}/zuchter`);
|
||||
return r.ok ? r.json() : [];
|
||||
}
|
||||
|
||||
async function _submitZuchter(data) {
|
||||
const r = await fetch('/api/wiki/zuchter', {
|
||||
method: 'POST', credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return r.ok;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Render-Helfer: Steckbrief-Grid
|
||||
// ----------------------------------------------------------
|
||||
function _renderSteckbriefGrid(rasse) {
|
||||
const gewicht = (rasse.gewicht_min_kg && rasse.gewicht_max_kg)
|
||||
? `${rasse.gewicht_min_kg}–${rasse.gewicht_max_kg} kg`
|
||||
: (rasse.gewicht_max_kg ? `bis ${rasse.gewicht_max_kg} kg` : '—');
|
||||
|
||||
const kinderLabel = rasse.kinder_geeignet === true
|
||||
? `<span style="color:var(--c-success)">✓ Ja</span>`
|
||||
: rasse.kinder_geeignet === false
|
||||
? `<span style="color:var(--c-warning)">⚡ Bedingt</span>`
|
||||
: '—';
|
||||
|
||||
const wohnungLabel = rasse.wohnung_geeignet
|
||||
? `<span style="color:var(--c-success)">✓ Ja</span>`
|
||||
: `<span style="color:var(--c-text-secondary)">✗ Besser Garten</span>`;
|
||||
|
||||
const rows = [
|
||||
['Größe', _groesseLabel(rasse.groesse) || '—'],
|
||||
['Gewicht', gewicht],
|
||||
['Lebensdauer', _esc(rasse.lebensdauer) || '—'],
|
||||
['Aktivität', _aktivLabel(rasse.aktivitaet) || '—'],
|
||||
['Eignung', _erfahrungLabel(rasse.erfahrung) || '—'],
|
||||
['Kinder', kinderLabel],
|
||||
['Wohnung', wohnungLabel],
|
||||
['FCI-Gruppe', _esc(rasse.gruppe) || '—'],
|
||||
];
|
||||
|
||||
return `
|
||||
<div class="wiki-steckbrief-grid">
|
||||
${rows.map(([label, val]) => `
|
||||
<div class="wiki-steckbrief-item">
|
||||
<span class="wiki-steckbrief-label">${label}</span>
|
||||
<span class="wiki-steckbrief-value">${val}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Render-Helfer: Interesse-Section (Social)
|
||||
// ----------------------------------------------------------
|
||||
function _renderInteresseSection(stats, slug) {
|
||||
const hatCount = stats?.dogs_count ?? '–';
|
||||
const willCount = stats?.will_count ?? '–';
|
||||
const interest = stats?.user_interest ?? null;
|
||||
const isLoggedIn = !!_appState.user;
|
||||
|
||||
const hatActive = interest === 'hat';
|
||||
const willActive = interest === 'will';
|
||||
|
||||
const hatStyle = hatActive ? `background:var(--c-primary);color:#fff;border-color:var(--c-primary)` : '';
|
||||
const willStyle = willActive ? `background:var(--c-primary);color:#fff;border-color:var(--c-primary)` : '';
|
||||
|
||||
return `
|
||||
<div class="wiki-detail-section wiki-interesse-section" id="wiki-interesse-section">
|
||||
<div class="wiki-detail-label">In der Community</div>
|
||||
<div class="wiki-interesse-counts" style="display:flex;gap:var(--space-4);margin-bottom:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||
<span id="wiki-hat-count">🐕 <strong>${hatCount}</strong> haben diesen Hund</span>
|
||||
<span id="wiki-will-count">❤️ <strong>${willCount}</strong> möchten ihn</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-2)">
|
||||
<button class="btn btn-sm wiki-interesse-btn" id="wiki-btn-hat"
|
||||
style="flex:1;${hatStyle}"
|
||||
data-slug="${_esc(slug)}" data-typ="hat">
|
||||
${isLoggedIn ? '' : '🔒 '}Ich hab einen
|
||||
</button>
|
||||
<button class="btn btn-sm wiki-interesse-btn" id="wiki-btn-will"
|
||||
style="flex:1;${willStyle}"
|
||||
data-slug="${_esc(slug)}" data-typ="will">
|
||||
${isLoggedIn ? '' : '🔒 '}Ich will einen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function _bindInteresseButtons(slug) {
|
||||
document.querySelectorAll('.wiki-interesse-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!_appState.user) {
|
||||
UI.toast('Bitte melde dich an, um diese Funktion zu nutzen.', 'info');
|
||||
return;
|
||||
}
|
||||
const typ = btn.dataset.typ;
|
||||
const hatBtn = document.getElementById('wiki-btn-hat');
|
||||
const willBtn = document.getElementById('wiki-btn-will');
|
||||
|
||||
// Determine current state
|
||||
const isActive = btn.style.background.includes('var(--c-primary)') || btn.style.backgroundColor;
|
||||
const currentActive = (hatBtn?.style.background || '').includes('var(--c-primary)') ? 'hat'
|
||||
: (willBtn?.style.background || '').includes('var(--c-primary)') ? 'will' : null;
|
||||
|
||||
// Optimistic disable
|
||||
btn.disabled = true;
|
||||
try {
|
||||
if (currentActive === typ) {
|
||||
await _deleteInteresse(slug);
|
||||
} else {
|
||||
await _setInteresse(slug, typ);
|
||||
}
|
||||
// Reload stats and re-render counts + button states
|
||||
const stats = await _fetchStats(slug);
|
||||
if (stats) {
|
||||
const hatCount = stats.dogs_count ?? '–';
|
||||
const willCount = stats.will_count ?? '–';
|
||||
const interest = stats.user_interest ?? null;
|
||||
|
||||
const hatEl = document.getElementById('wiki-hat-count');
|
||||
const willEl = document.getElementById('wiki-will-count');
|
||||
if (hatEl) hatEl.innerHTML = `🐕 <strong>${hatCount}</strong> haben diesen Hund`;
|
||||
if (willEl) willEl.innerHTML = `❤️ <strong>${willCount}</strong> möchten ihn`;
|
||||
|
||||
const activeStyle = `background:var(--c-primary);color:#fff;border-color:var(--c-primary)`;
|
||||
if (hatBtn) { hatBtn.removeAttribute('style'); if (interest === 'hat') hatBtn.style.cssText = activeStyle; }
|
||||
if (willBtn) { willBtn.removeAttribute('style'); if (interest === 'will') willBtn.style.cssText = activeStyle; }
|
||||
}
|
||||
} catch {
|
||||
UI.toast.error('Aktion fehlgeschlagen.');
|
||||
}
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Render-Helfer: Züchter-Sektion
|
||||
// ----------------------------------------------------------
|
||||
function _renderZuchterSection(zuchter, slug) {
|
||||
const DE_BUNDESLAENDER = [
|
||||
'Baden-Württemberg','Bayern','Berlin','Brandenburg','Bremen','Hamburg',
|
||||
'Hessen','Mecklenburg-Vorpommern','Niedersachsen','Nordrhein-Westfalen',
|
||||
'Rheinland-Pfalz','Saarland','Sachsen','Sachsen-Anhalt',
|
||||
'Schleswig-Holstein','Thüringen',
|
||||
];
|
||||
|
||||
const listHtml = zuchter.length === 0
|
||||
? `<p style="color:var(--c-text-secondary);font-size:var(--text-sm)">Noch keine Züchter eingetragen.</p>`
|
||||
: zuchter.map(z => `
|
||||
<div class="wiki-zuchter-card" style="padding:var(--space-3);border-radius:var(--radius-md);background:var(--c-surface-2);margin-bottom:var(--space-2)">
|
||||
<div style="font-weight:var(--weight-semibold)">${_esc(z.name)}
|
||||
${z.zwingername ? `<em style="font-weight:normal;color:var(--c-text-secondary)"> „${_esc(z.zwingername)}“</em>` : ''}
|
||||
${z.vdh_mitglied ? `<span class="badge badge-sm" style="margin-left:var(--space-1);background:var(--c-primary);color:#fff">VDH</span>` : ''}
|
||||
</div>
|
||||
${(z.ort || z.bundesland) ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">${[z.ort, z.bundesland].filter(Boolean).map(_esc).join(', ')}</div>` : ''}
|
||||
${z.beschreibung ? `<p style="font-size:var(--text-sm);margin-top:var(--space-1)">${_esc(z.beschreibung)}</p>` : ''}
|
||||
${z.website ? `<a href="${_esc(z.website)}" target="_blank" rel="noopener" style="font-size:var(--text-sm);color:var(--c-primary)">${_esc(z.website)}</a>` : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
const formHtml = _appState.user ? `
|
||||
<div id="wiki-zuchter-form-wrap" style="display:none;margin-top:var(--space-3)">
|
||||
<form id="wiki-zuchter-form" autocomplete="off">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)">
|
||||
<div class="form-group" style="grid-column:1/-1">
|
||||
<label class="form-label">Name *</label>
|
||||
<input class="form-control" name="name" required maxlength="100">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Zwingername</label>
|
||||
<input class="form-control" name="zwingername" maxlength="100">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Ort</label>
|
||||
<input class="form-control" name="ort" maxlength="80">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">PLZ</label>
|
||||
<input class="form-control" name="plz" maxlength="10">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Bundesland</label>
|
||||
<select class="form-control" name="bundesland">
|
||||
<option value="">— bitte wählen —</option>
|
||||
${DE_BUNDESLAENDER.map(bl => `<option value="${_esc(bl)}">${_esc(bl)}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="grid-column:1/-1">
|
||||
<label class="form-label">Website</label>
|
||||
<input class="form-control" name="website" type="url" maxlength="200" placeholder="https://…">
|
||||
</div>
|
||||
<div class="form-group" style="grid-column:1/-1">
|
||||
<label class="form-label">Telefon</label>
|
||||
<input class="form-control" name="telefon" type="tel" maxlength="30">
|
||||
</div>
|
||||
<div class="form-group" style="grid-column:1/-1">
|
||||
<label class="form-label">Kurzbeschreibung</label>
|
||||
<textarea class="form-control" name="beschreibung" rows="3" maxlength="500"></textarea>
|
||||
</div>
|
||||
<div class="form-group" style="grid-column:1/-1;display:flex;align-items:center;gap:var(--space-2)">
|
||||
<input type="checkbox" id="wiki-zuchter-vdh" name="vdh_mitglied" value="1" style="width:auto">
|
||||
<label for="wiki-zuchter-vdh" style="margin:0;font-size:var(--text-sm)">VDH-Mitglied</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wiki-zuchter-success" style="display:none;color:var(--c-success);padding:var(--space-3);text-align:center;font-size:var(--text-sm)">
|
||||
Vielen Dank! Dein Eintrag wird geprüft.
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-2)">
|
||||
<button type="button" class="btn btn-ghost flex-1" id="wiki-zuchter-cancel">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary flex-1" id="wiki-zuchter-submit">Eintragen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm" id="wiki-zuchter-add-btn" style="margin-top:var(--space-3)">
|
||||
+ Züchter eintragen
|
||||
</button>
|
||||
` : '';
|
||||
|
||||
return `
|
||||
<div class="wiki-detail-section" id="wiki-zuchter-section">
|
||||
<div class="wiki-detail-label">Züchter</div>
|
||||
<div id="wiki-zuchter-list">${listHtml}</div>
|
||||
${formHtml}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function _bindZuchterForm(slug) {
|
||||
const addBtn = document.getElementById('wiki-zuchter-add-btn');
|
||||
const cancelBtn = document.getElementById('wiki-zuchter-cancel');
|
||||
const formWrap = document.getElementById('wiki-zuchter-form-wrap');
|
||||
const form = document.getElementById('wiki-zuchter-form');
|
||||
|
||||
addBtn?.addEventListener('click', () => {
|
||||
formWrap.style.display = '';
|
||||
addBtn.style.display = 'none';
|
||||
});
|
||||
|
||||
cancelBtn?.addEventListener('click', () => {
|
||||
formWrap.style.display = 'none';
|
||||
addBtn.style.display = '';
|
||||
});
|
||||
|
||||
form?.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const submitBtn = document.getElementById('wiki-zuchter-submit');
|
||||
const fd = new FormData(form);
|
||||
const data = {
|
||||
rasse_slug: slug,
|
||||
name: fd.get('name'),
|
||||
zwingername: fd.get('zwingername') || null,
|
||||
ort: fd.get('ort') || null,
|
||||
plz: fd.get('plz') || null,
|
||||
bundesland: fd.get('bundesland') || null,
|
||||
vdh_mitglied: fd.get('vdh_mitglied') === '1',
|
||||
website: fd.get('website') || null,
|
||||
telefon: fd.get('telefon') || null,
|
||||
beschreibung: fd.get('beschreibung') || null,
|
||||
};
|
||||
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Wird gesendet…';
|
||||
|
||||
const ok = await _submitZuchter(data);
|
||||
if (ok) {
|
||||
form.reset();
|
||||
document.getElementById('wiki-zuchter-success').style.display = '';
|
||||
// Hide submit row
|
||||
submitBtn.closest('div[style*="flex"]').style.display = 'none';
|
||||
} else {
|
||||
UI.toast.error('Fehler beim Einsenden. Bitte versuche es erneut.');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Eintragen';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function _openBreedDetail(slug) {
|
||||
let rasse;
|
||||
try {
|
||||
|
|
@ -402,54 +709,69 @@ window.Page_wiki = (() => {
|
|||
return;
|
||||
}
|
||||
|
||||
const berichteHtml = _renderBerichteHtml(rasse.berichte || [], slug);
|
||||
|
||||
// Temperament chips
|
||||
const chips = rasse.temperament
|
||||
? rasse.temperament.split(',').map(t => `<span class="wiki-trait-chip">${_esc(t.trim())}</span>`).join('')
|
||||
: '';
|
||||
|
||||
// Stats row
|
||||
const gewicht = (rasse.gewicht_min_kg && rasse.gewicht_max_kg)
|
||||
? `${rasse.gewicht_min_kg}–${rasse.gewicht_max_kg} kg`
|
||||
: (rasse.gewicht_max_kg ? `bis ${rasse.gewicht_max_kg} kg` : '—');
|
||||
|
||||
const photoHtml = rasse.foto_url
|
||||
? `<img class="wiki-detail-photo" src="${_esc(rasse.foto_url)}" alt="${_esc(rasse.name)}" onerror="this.style.display='none'">`
|
||||
? `<div class="wiki-detail-hero-photo-wrap">
|
||||
<img class="wiki-detail-photo" src="${_esc(rasse.foto_url)}" alt="${_esc(rasse.name)}" onerror="this.parentElement.style.display='none'">
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
const berichteHtml = _renderBerichteHtml(rasse.berichte || [], slug);
|
||||
|
||||
const body = `
|
||||
${photoHtml}
|
||||
<div class="wiki-detail-badges">
|
||||
<span class="wiki-badge-groesse wiki-badge-groesse--${_esc(rasse.groesse)}">${_groesseLabel(rasse.groesse)}</span>
|
||||
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${_esc(rasse.aktivitaet)}">${_aktivLabel(rasse.aktivitaet)}</span>
|
||||
<span class="wiki-badge-erfahrung wiki-badge-erfahrung--${_esc(rasse.erfahrung)}">${_erfahrungLabel(rasse.erfahrung)}</span>
|
||||
${rasse.gruppe ? `<span class="badge">${_esc(rasse.gruppe)}</span>` : ''}
|
||||
${/* 1. Hero */ ''}
|
||||
<div class="wiki-detail-hero" style="text-align:center;margin-bottom:var(--space-4)">
|
||||
${photoHtml}
|
||||
<h1 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin:var(--space-2) 0 var(--space-1)">${_esc(rasse.name)}</h1>
|
||||
${rasse.herkunft ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">${UI.icon('map-pin')} ${_esc(rasse.herkunft)}</div>` : ''}
|
||||
${rasse.gruppe ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:2px">${_esc(rasse.gruppe)}</div>` : ''}
|
||||
</div>
|
||||
${rasse.herkunft || rasse.bred_for ? `
|
||||
<div class="wiki-detail-section">
|
||||
${rasse.herkunft ? `<div class="wiki-detail-label">Herkunft</div><p>${_esc(rasse.herkunft)}</p>` : ''}
|
||||
${rasse.bred_for ? `<div class="wiki-detail-label">Ursprüngliche Aufgabe</div><p>${_esc(rasse.bred_for)}</p>` : ''}
|
||||
</div>` : ''}
|
||||
${chips ? `
|
||||
|
||||
${/* 2. Charakter-Badges */ chips ? `
|
||||
<div class="wiki-detail-section">
|
||||
<div class="wiki-detail-label">Charakter</div>
|
||||
<div class="wiki-trait-chips">${chips}</div>
|
||||
</div>` : ''}
|
||||
<div class="wiki-stat-row">
|
||||
<div class="wiki-stat-item">
|
||||
<span class="wiki-stat-label">Gewicht</span>
|
||||
<span class="wiki-stat-value">${gewicht}</span>
|
||||
|
||||
${/* 3. Beschreibung */ rasse.beschreibung ? `
|
||||
<div class="wiki-detail-section">
|
||||
<div class="wiki-detail-label">Beschreibung</div>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${_esc(rasse.beschreibung)}</p>
|
||||
</div>` : (rasse.bred_for ? `
|
||||
<div class="wiki-detail-section">
|
||||
<div class="wiki-detail-label">Ursprüngliche Aufgabe</div>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${_esc(rasse.bred_for)}</p>
|
||||
</div>` : '')}
|
||||
|
||||
${/* 4. Steckbrief */ _renderSteckbriefGrid(rasse)}
|
||||
|
||||
${/* 5. Vorkommen */ rasse.vorkommen_de ? `
|
||||
<div class="wiki-detail-section">
|
||||
<div class="wiki-detail-label">Vorkommen in Deutschland</div>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${_esc(rasse.vorkommen_de)}</p>
|
||||
</div>` : ''}
|
||||
|
||||
${/* 6. Interesse — wird async befüllt */ `
|
||||
<div id="wiki-interesse-placeholder">
|
||||
<div class="wiki-detail-section" style="opacity:0.5">
|
||||
<div class="wiki-detail-label">In der Community</div>
|
||||
<div style="height:60px;background:var(--c-surface-2);border-radius:var(--radius-md)"></div>
|
||||
</div>
|
||||
<div class="wiki-stat-item">
|
||||
<span class="wiki-stat-label">Lebenserwartung</span>
|
||||
<span class="wiki-stat-value">${_esc(rasse.lebensdauer || '—')}</span>
|
||||
</div>`}
|
||||
|
||||
${/* 7. Züchter — wird async befüllt */ `
|
||||
<div id="wiki-zuchter-placeholder">
|
||||
<div class="wiki-detail-section" style="opacity:0.5">
|
||||
<div class="wiki-detail-label">Züchter</div>
|
||||
<div style="height:40px;background:var(--c-surface-2);border-radius:var(--radius-md)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wiki-fit-row">
|
||||
<span>${UI.icon('house-line')} Wohnung: ${rasse.wohnung_geeignet ? UI.icon('check') : UI.icon('x')}</span>
|
||||
<span>${UI.icon('users')} Kinder: ${rasse.kinder_geeignet ? UI.icon('check') : UI.icon('x')}</span>
|
||||
</div>
|
||||
</div>`}
|
||||
|
||||
${/* 8. Community-Berichte */ `
|
||||
<div class="wiki-detail-section" id="wiki-berichte-section">
|
||||
<div class="wiki-detail-label">Community-Berichte</div>
|
||||
${berichteHtml}
|
||||
|
|
@ -465,11 +787,30 @@ window.Page_wiki = (() => {
|
|||
<button class="btn btn-ghost w-full" id="wiki-foto-submit-btn" style="font-size:var(--text-sm)">
|
||||
${UI.icon('camera')} ${rasse.foto_url ? 'Besseres Foto vorschlagen' : 'Foto hinzufügen'}
|
||||
</button>
|
||||
</div>` : ''}
|
||||
</div>` : ''}`}
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: _esc(rasse.name), body });
|
||||
|
||||
// Async: load stats + züchter in parallel
|
||||
Promise.all([_fetchStats(slug), _fetchZuchter(slug)]).then(([stats, zuchter]) => {
|
||||
const interessePlaceholder = document.getElementById('wiki-interesse-placeholder');
|
||||
if (interessePlaceholder) {
|
||||
interessePlaceholder.outerHTML = _renderInteresseSection(stats, slug);
|
||||
_bindInteresseButtons(slug);
|
||||
}
|
||||
|
||||
const zuchterPlaceholder = document.getElementById('wiki-zuchter-placeholder');
|
||||
if (zuchterPlaceholder) {
|
||||
zuchterPlaceholder.outerHTML = _renderZuchterSection(zuchter || [], slug);
|
||||
_bindZuchterForm(slug);
|
||||
}
|
||||
}).catch(() => {
|
||||
// Silently remove placeholders on error
|
||||
document.getElementById('wiki-interesse-placeholder')?.remove();
|
||||
document.getElementById('wiki-zuchter-placeholder')?.remove();
|
||||
});
|
||||
|
||||
document.getElementById('wiki-bericht-add-btn')?.addEventListener('click', () => {
|
||||
UI.modal.close();
|
||||
setTimeout(() => _showBerichtForm(slug, rasse.name), 350);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue