Refactor: 1167 _esc() → UI.escape() in 36 Dateien, SW by-v1113

Bündel 1 aus dem Duplikat-Audit: existierende zentrale Helper nutzen
statt lokale Duplikate.

Pure Migration ohne neuen Code:
- 1167 _esc()-Aufrufe in 36 Page-Modulen migriert auf UI.escape()
- 24 lokale _esc/_escape-Definitionen entfernt
- lost.js hatte _escape() (Variante) — 17 Aufrufe ebenfalls migriert
- jobs.js + breeder.js: tote Alias-Wrapper entfernt

UI.escape() existierte schon — wurde nur überall lokal nochmal
implementiert. Funktional identisch (gleiche 4-replace-chain für
& < > ").

Tests 19/19 grün. Frontend-LOC um ~120 Zeilen reduziert.

Hinweis: _emptyState (7 Stellen) und _icon (8 Stellen) wurden NICHT
migriert — sie haben abweichende Signaturen von UI.emptyState({...})
bzw. UI.icon(name). Eigener Sprint nötig.
This commit is contained in:
rene 2026-05-27 10:15:33 +02:00
parent e7939ce98e
commit c517c9281d
42 changed files with 1115 additions and 1341 deletions

View file

@ -137,7 +137,7 @@ window.Page_wiki = (() => {
try {
subs = await _apiFetch('/api/wiki/foto-submissions');
} catch (e) {
el.innerHTML = `<div class="empty-state"><p>${_esc(e.message)}</p></div>`;
el.innerHTML = `<div class="empty-state"><p>${UI.escape(e.message)}</p></div>`;
return;
}
@ -163,16 +163,16 @@ window.Page_wiki = (() => {
${subs.map(s => `
<div class="card" style="margin-bottom:var(--space-3);padding:var(--space-3)" id="wiki-sub-${s.id}">
<div style="display:flex;gap:var(--space-3);align-items:flex-start">
<img src="${_esc(s.foto_url)}" alt=""
<img src="${UI.escape(s.foto_url)}" alt=""
style="width:100px;height:80px;object-fit:cover;border-radius:var(--radius-md);flex-shrink:0;background:var(--c-surface-2)">
<div class="flex-1-min">
<div style="font-weight:var(--weight-semibold)">${_esc(s.rasse_name)}</div>
<div style="font-weight:var(--weight-semibold)">${UI.escape(s.rasse_name)}</div>
<div class="text-xs-muted">
von ${_esc(s.user_name)} · ${_formatDate(s.created_at)}
von ${UI.escape(s.user_name)} · ${_formatDate(s.created_at)}
</div>
${s.aktuell_foto
? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:4px">
Aktuelles Foto: <img src="${_esc(s.aktuell_foto)}" style="height:20px;vertical-align:middle;border-radius:2px">
Aktuelles Foto: <img src="${UI.escape(s.aktuell_foto)}" style="height:20px;vertical-align:middle;border-radius:2px">
</div>`
: `<div style="font-size:var(--text-xs);color:var(--c-warning);margin-top:4px">Kein Foto vorhanden</div>`
}
@ -335,7 +335,7 @@ window.Page_wiki = (() => {
// Preserve current selection
const cur = _currentGruppe;
sel.innerHTML = `<option value="">Alle Gruppen</option>` +
_gruppen.map(g => `<option value="${_esc(g)}"${g === cur ? ' selected' : ''}>${_esc(g)}</option>`).join('');
_gruppen.map(g => `<option value="${UI.escape(g)}"${g === cur ? ' selected' : ''}>${UI.escape(g)}</option>`).join('');
}
}
@ -389,24 +389,24 @@ window.Page_wiki = (() => {
? fotoUrl.replace(/\.(jpe?g|png|gif|webp)$/i, '_preview.webp')
: fotoUrl;
const photoHtml = fotoUrl
? `<img class="wiki-breed-photo" src="${_esc(srcUrl)}" loading="lazy" alt="${_esc(r.name)}"
onerror="if(this.src.includes('_preview')){this.src='${_esc(fotoUrl)}'}else{this.style.display='none';this.nextElementSibling.style.display='flex'}">`
? `<img class="wiki-breed-photo" src="${UI.escape(srcUrl)}" loading="lazy" alt="${UI.escape(r.name)}"
onerror="if(this.src.includes('_preview')){this.src='${UI.escape(fotoUrl)}'}else{this.style.display='none';this.nextElementSibling.style.display='flex'}">`
: '';
const fallbackHtml = `<div class="wiki-breed-photo-fallback" style="${fotoUrl ? 'display:none' : ''}">${_DOG_SILHOUETTE}</div>`;
return `
<div class="wiki-breed-card" data-slug="${_esc(r.slug)}">
<div class="wiki-breed-card" data-slug="${UI.escape(r.slug)}">
<div class="wiki-breed-photo-wrap">
${photoHtml}
${fallbackHtml}
</div>
<div class="wiki-breed-card-body">
<div class="wiki-breed-card-name">${_esc(r.name)}</div>
<div class="wiki-breed-card-gruppe">${_esc(r.gruppe || '—')}</div>
<div class="wiki-breed-card-name">${UI.escape(r.name)}</div>
<div class="wiki-breed-card-gruppe">${UI.escape(r.gruppe || '—')}</div>
<div class="wiki-breed-badges">
<span class="wiki-badge-groesse wiki-badge-groesse--${_esc(r.groesse)}">${_groesseLabel(r.groesse)}</span>
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${_esc(r.aktivitaet)}">${_aktivLabel(r.aktivitaet)}</span>
<span class="wiki-badge-erfahrung wiki-badge-erfahrung--${_esc(r.erfahrung)}">${_erfahrungLabel(r.erfahrung)}</span>
<span class="wiki-badge-groesse wiki-badge-groesse--${UI.escape(r.groesse)}">${_groesseLabel(r.groesse)}</span>
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${UI.escape(r.aktivitaet)}">${_aktivLabel(r.aktivitaet)}</span>
<span class="wiki-badge-erfahrung wiki-badge-erfahrung--${UI.escape(r.erfahrung)}">${_erfahrungLabel(r.erfahrung)}</span>
</div>
</div>
</div>
@ -472,12 +472,12 @@ window.Page_wiki = (() => {
const rows = [
['Größe', _groesseLabel(rasse.groesse) || '&mdash;'],
['Gewicht', gewicht],
['Lebensdauer', _esc(rasse.lebensdauer) || '&mdash;'],
['Lebensdauer', UI.escape(rasse.lebensdauer) || '&mdash;'],
['Aktivität', _aktivLabel(rasse.aktivitaet) || '&mdash;'],
['Eignung', _erfahrungLabel(rasse.erfahrung) || '&mdash;'],
['Kinder', kinderLabel],
['Wohnung', wohnungLabel],
['FCI-Gruppe', _esc(rasse.gruppe) || '&mdash;'],
['FCI-Gruppe', UI.escape(rasse.gruppe) || '&mdash;'],
];
return `
@ -517,12 +517,12 @@ window.Page_wiki = (() => {
<div class="flex-gap-2">
<button class="btn btn-sm wiki-interesse-btn" id="wiki-btn-hat"
style="flex:1;${hatStyle}"
data-slug="${_esc(slug)}" data-typ="hat">
data-slug="${UI.escape(slug)}" data-typ="hat">
${isLoggedIn ? '' : '&#128274; '}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">
data-slug="${UI.escape(slug)}" data-typ="will">
${isLoggedIn ? '' : '&#128274; '}Ich will einen
</button>
</div>
@ -593,13 +593,13 @@ window.Page_wiki = (() => {
? `<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)"> &bdquo;${_esc(z.zwingername)}&ldquo;</em>` : ''}
<div style="font-weight:var(--weight-semibold)">${UI.escape(z.name)}
${z.zwingername ? `<em style="font-weight:normal;color:var(--c-text-secondary)"> &bdquo;${UI.escape(z.zwingername)}&ldquo;</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 class="text-sm-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>` : ''}
${z.beschreibung ? `<p style="font-size:var(--text-sm);margin-top:var(--space-1)">${UI.escape(z.beschreibung)}</p>` : ''}
${z.website ? `<a href="${UI.escape(z.website)}" target="_blank" rel="noopener" style="font-size:var(--text-sm);color:var(--c-primary)">${UI.escape(z.website)}</a>` : ''}
</div>
`).join('');
@ -627,7 +627,7 @@ window.Page_wiki = (() => {
<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('')}
${DE_BUNDESLAENDER.map(bl => `<option value="${UI.escape(bl)}">${UI.escape(bl)}</option>`).join('')}
</select>
</div>
<div class="form-group" style="grid-column:1/-1">
@ -731,7 +731,7 @@ window.Page_wiki = (() => {
// Temperament chips
const chips = rasse.temperament
? rasse.temperament.split(',').map(t => `<span class="wiki-trait-chip">${_esc(t.trim())}</span>`).join('')
? rasse.temperament.split(',').map(t => `<span class="wiki-trait-chip">${UI.escape(t.trim())}</span>`).join('')
: '';
const _dogSvgLg = _DOG_SILHOUETTE.replace('width="48" height="48"', 'width="56" height="56"');
@ -743,7 +743,7 @@ window.Page_wiki = (() => {
const photoHtml = allFotos.length
? `<div class="wiki-gallery-wrap">
<img class="wiki-detail-photo wiki-gallery-main" id="wiki-main-photo"
src="${_esc(allFotos[0].foto_url)}" alt="${_esc(rasse.name)}"
src="${UI.escape(allFotos[0].foto_url)}" alt="${UI.escape(rasse.name)}"
onerror="this.style.display='none';document.getElementById('wiki-photo-fallback').style.display='flex'">
<div id="wiki-photo-fallback" class="wiki-detail-photo-placeholder hidden">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>
${allFotos.length > 1 ? `
@ -751,10 +751,10 @@ window.Page_wiki = (() => {
${allFotos.map((f, i) => `
<button class="wiki-gallery-thumb${i === 0 ? ' active' : ''}" data-idx="${i}"
aria-label="Foto ${i + 1}">
<img src="${_esc(f.foto_url.startsWith('/media/') ? f.foto_url.replace(/\.(jpe?g|png|gif|webp)$/i,'_preview.webp') : f.foto_url)}"
<img src="${UI.escape(f.foto_url.startsWith('/media/') ? f.foto_url.replace(/\.(jpe?g|png|gif|webp)$/i,'_preview.webp') : f.foto_url)}"
alt="" loading="lazy"
onerror="if(this.src.includes('_preview')){this.src='${_esc(f.foto_url)}'}else{this.style.display='none'}">
${f.user_name ? `<span class="wiki-gallery-thumb-label">von ${_esc(f.user_name)}</span>` : ''}
onerror="if(this.src.includes('_preview')){this.src='${UI.escape(f.foto_url)}'}else{this.style.display='none'}">
${f.user_name ? `<span class="wiki-gallery-thumb-label">von ${UI.escape(f.user_name)}</span>` : ''}
</button>`).join('')}
</div>` : ''}
<button class="wiki-gallery-expand" id="wiki-gallery-expand" aria-label="Vollbild">
@ -771,9 +771,9 @@ window.Page_wiki = (() => {
<div class="wiki-detail-hero" style="text-align:center;margin-bottom:var(--space-4)">
${photoHtml}
${userFotosHtml}
<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 class="text-sm-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>` : ''}
<h1 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin:var(--space-2) 0 var(--space-1)">${UI.escape(rasse.name)}</h1>
${rasse.herkunft ? `<div class="text-sm-secondary">${UI.icon('map-pin')} ${UI.escape(rasse.herkunft)}</div>` : ''}
${rasse.gruppe ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:2px">${UI.escape(rasse.gruppe)}</div>` : ''}
</div>
${/* 2. Charakter-Badges */ chips ? `
@ -785,11 +785,11 @@ window.Page_wiki = (() => {
${/* 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>
<p style="line-height:1.6;color:var(--c-text)">${UI.escape(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>
<p style="line-height:1.6;color:var(--c-text)">${UI.escape(rasse.bred_for)}</p>
</div>` : '')}
${/* 4. Steckbrief */ _renderSteckbriefGrid(rasse)}
@ -797,7 +797,7 @@ window.Page_wiki = (() => {
${/* 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>
<p style="line-height:1.6;color:var(--c-text)">${UI.escape(rasse.vorkommen_de)}</p>
</div>` : ''}
${/* 6. Interesse — wird async befüllt */ `
@ -835,7 +835,7 @@ window.Page_wiki = (() => {
</div>` : ''}`}
`;
UI.modal.open({ title: _esc(rasse.name), body });
UI.modal.open({ title: UI.escape(rasse.name), body });
// Async: load stats + züchter in parallel
Promise.all([_fetchStats(slug), _fetchZuchter(slug)]).then(([stats, zuchter]) => {
@ -936,7 +936,7 @@ window.Page_wiki = (() => {
</p>
<form id="wiki-foto-form" autocomplete="off">
<div class="form-group">
<label class="form-label">Foto von <strong>${_esc(rasseName)}</strong></label>
<label class="form-label">Foto von <strong>${UI.escape(rasseName)}</strong></label>
<input class="form-control" type="file" id="wiki-foto-input"
accept="image/jpeg,image/png,image/webp" required>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:4px">
@ -1030,14 +1030,14 @@ window.Page_wiki = (() => {
return berichte.map(b => `
<div class="wiki-bericht-item" data-id="${b.id}">
<div class="wiki-bericht-header">
<span class="wiki-bericht-autor">${_esc(b.autor)}</span>
<span class="wiki-bericht-autor">${UI.escape(b.autor)}</span>
<span class="wiki-bericht-date">${_formatDate(b.created_at)}</span>
${_appState.user && _appState.user.name === b.autor
? `<button class="btn btn-danger btn-xs wiki-bericht-del" data-id="${b.id}" data-slug="${_esc(slug)}" style="margin-left:auto;padding:2px 8px;font-size:0.7rem">Löschen</button>`
? `<button class="btn btn-danger btn-xs wiki-bericht-del" data-id="${b.id}" data-slug="${UI.escape(slug)}" style="margin-left:auto;padding:2px 8px;font-size:0.7rem">Löschen</button>`
: ''}
</div>
<div class="wiki-bericht-titel">${_esc(b.titel)}</div>
<p class="wiki-bericht-text">${_esc(b.text)}</p>
<div class="wiki-bericht-titel">${UI.escape(b.titel)}</div>
<p class="wiki-bericht-text">${UI.escape(b.text)}</p>
</div>
`).join('');
}
@ -1047,7 +1047,7 @@ window.Page_wiki = (() => {
<form id="wiki-bericht-form" autocomplete="off">
<div class="form-group">
<label class="form-label">Rasse</label>
<input class="form-control" type="text" value="${_esc(rasseName)}" disabled>
<input class="form-control" type="text" value="${UI.escape(rasseName)}" disabled>
</div>
<div class="form-group">
<label class="form-label">Titel</label>
@ -1095,11 +1095,11 @@ window.Page_wiki = (() => {
<div class="wiki-section" data-idx="${i}">
<div class="wiki-section-header">
<span class="wiki-section-icon">${UI.icon(s.icon)}</span>
<span class="wiki-section-titel">${_esc(s.titel)}</span>
<span class="wiki-section-titel">${UI.escape(s.titel)}</span>
<span class="wiki-section-arrow">${UI.icon('caret-down')}</span>
</div>
<div class="wiki-section-body hidden">
<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${_esc(s.text)}</p>
<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${UI.escape(s.text)}</p>
</div>
</div>
`).join('');
@ -1126,13 +1126,13 @@ window.Page_wiki = (() => {
<div class="wiki-section" data-idx="${i}">
<div class="wiki-section-header">
<span class="wiki-section-icon">${UI.icon('map-pin')}</span>
<span class="wiki-section-titel">${_esc(r.land)}</span>
<span class="wiki-section-titel">${UI.escape(r.land)}</span>
<span class="wiki-section-arrow">${UI.icon('caret-down')}</span>
</div>
<div class="wiki-section-body hidden">
<div class="wiki-recht-row"><span class="wiki-recht-label">Leinenpflicht</span><span>${_esc(r.leine)}</span></div>
<div class="wiki-recht-row"><span class="wiki-recht-label">Rasseliste</span><span>${_esc(r.rasse)}</span></div>
<div class="wiki-recht-row"><span class="wiki-recht-label">Hundesteuer</span><span>${_esc(r.steuer)}</span></div>
<div class="wiki-recht-row"><span class="wiki-recht-label">Leinenpflicht</span><span>${UI.escape(r.leine)}</span></div>
<div class="wiki-recht-row"><span class="wiki-recht-label">Rasseliste</span><span>${UI.escape(r.rasse)}</span></div>
<div class="wiki-recht-row"><span class="wiki-recht-label">Hundesteuer</span><span>${UI.escape(r.steuer)}</span></div>
</div>
</div>
`).join('');
@ -1174,8 +1174,8 @@ window.Page_wiki = (() => {
const optionsHtml = frage.optionen.map(o => `
<button class="wiki-quiz-option${_quizAnswers[frage.key] === o.val ? ' selected' : ''}"
data-key="${_esc(frage.key)}" data-val="${_esc(o.val)}">
${_esc(o.label)}
data-key="${UI.escape(frage.key)}" data-val="${UI.escape(o.val)}">
${UI.escape(o.label)}
</button>
`).join('');
@ -1185,7 +1185,7 @@ window.Page_wiki = (() => {
<div class="wiki-quiz-progress" style="width:${progress}%"></div>
</div>
<p class="wiki-quiz-step-info">Frage ${_quizStep + 1} von ${QUIZ_FRAGEN.length}</p>
<p class="wiki-quiz-frage">${_esc(frage.frage)}</p>
<p class="wiki-quiz-frage">${UI.escape(frage.frage)}</p>
<div class="wiki-quiz-options">${optionsHtml}</div>
<div class="wiki-quiz-nav">
${_quizStep > 0
@ -1225,24 +1225,24 @@ window.Page_wiki = (() => {
const cardsHtml = data.results.map(r => {
const photoHtml = r.foto_url
? `<img class="wiki-quiz-result-photo" src="${_esc(r.foto_url)}" loading="lazy" alt="${_esc(r.name)}" onerror="this.style.display='none'">`
? `<img class="wiki-quiz-result-photo" src="${UI.escape(r.foto_url)}" loading="lazy" alt="${UI.escape(r.name)}" onerror="this.style.display='none'">`
: `<div class="wiki-quiz-result-photo-fallback">${UI.icon('dog')}</div>`;
return `
<div class="wiki-quiz-result-card">
<div class="wiki-quiz-result-photo-wrap">${photoHtml}</div>
<div class="wiki-quiz-result-card-body">
<div class="wiki-quiz-result-name">${_esc(r.name)}</div>
<div class="wiki-quiz-result-gruppe">${_esc(r.gruppe || '')}</div>
<div class="wiki-quiz-result-name">${UI.escape(r.name)}</div>
<div class="wiki-quiz-result-gruppe">${UI.escape(r.gruppe || '')}</div>
<div class="wiki-breed-badges" style="margin:var(--space-2) 0">
<span class="wiki-badge-groesse wiki-badge-groesse--${_esc(r.groesse)}">${_groesseLabel(r.groesse)}</span>
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${_esc(r.aktivitaet)}">${_aktivLabel(r.aktivitaet)}</span>
<span class="wiki-badge-groesse wiki-badge-groesse--${UI.escape(r.groesse)}">${_groesseLabel(r.groesse)}</span>
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${UI.escape(r.aktivitaet)}">${_aktivLabel(r.aktivitaet)}</span>
</div>
${r.temperament ? `<p class="wiki-quiz-result-char">${_esc(r.temperament.split(',').slice(0,4).join(', '))}</p>` : ''}
${r.temperament ? `<p class="wiki-quiz-result-char">${UI.escape(r.temperament.split(',').slice(0,4).join(', '))}</p>` : ''}
<div class="wiki-fit-row" style="font-size:var(--text-xs);margin-top:var(--space-1)">
<span>${UI.icon('house-line')} ${r.wohnung_geeignet ? 'Wohnung' : 'Haus'}</span>
<span>${UI.icon('users')} ${r.kinder_geeignet ? 'Kinderfreundlich' : 'Erfahrung nötig'}</span>
</div>
<button class="btn btn-secondary btn-sm wiki-quiz-mehr" data-slug="${_esc(r.slug)}" class="mt-2">Mehr erfahren</button>
<button class="btn btn-secondary btn-sm wiki-quiz-mehr" data-slug="${UI.escape(r.slug)}" class="mt-2">Mehr erfahren</button>
</div>
</div>
`;
@ -1334,14 +1334,7 @@ window.Page_wiki = (() => {
return new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
} catch { return iso; }
}
function _esc(str) {
if (!str) return '';
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
// ----------------------------------------------------------
// ----------------------------------------------------------
// RASSEN-ERKENNUNG PER KI (Wiki-Tab)
// ----------------------------------------------------------
function _bindWikiRasseErkennung(el) {
@ -1405,7 +1398,7 @@ window.Page_wiki = (() => {
Auf diesem Foto konnte kein Hund erkannt werden.<br>
Bitte lade ein deutlicheres Foto hoch.
</p>
${data.hinweis ? `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:var(--space-3)">${_esc(data.hinweis)}</p>` : ''}
${data.hinweis ? `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:var(--space-3)">${UI.escape(data.hinweis)}</p>` : ''}
</div>`,
footer: `<button class="btn btn-secondary" onclick="UI.modal.close()">Schließen</button>`,
});
@ -1418,18 +1411,18 @@ window.Page_wiki = (() => {
return `
<div class="rasse-result-card${isTop ? ' rasse-result-card--top' : ''}">
<div style="display:flex;align-items:center;justify-content:space-between">
<div class="rasse-result-name">${isTop ? '🐕 ' : ''}${_esc(r.name)}</div>
<div class="rasse-result-name">${isTop ? '🐕 ' : ''}${UI.escape(r.name)}</div>
<span class="rasse-result-pct${isTop ? '' : ' rasse-result-pct--dim'}">${r.sicherheit}%</span>
</div>
<div class="rasse-result-bar-wrap">
<div class="rasse-result-bar${isTop ? '' : ' rasse-result-bar--dim'}"
style="width:${r.sicherheit}%"></div>
</div>
${r.beschreibung ? `<div class="rasse-result-desc">${_esc(r.beschreibung)}</div>` : ''}
${r.beschreibung ? `<div class="rasse-result-desc">${UI.escape(r.beschreibung)}</div>` : ''}
${r.wiki_slug ? `
<div class="mt-3">
<button class="btn btn-${isTop ? 'primary' : 'secondary'} btn-sm w-full"
data-action="wiki" data-slug="${_esc(r.wiki_slug)}">
data-action="wiki" data-slug="${UI.escape(r.wiki_slug)}">
Im Wiki nachschlagen
</button>
</div>` : ''}
@ -1443,7 +1436,7 @@ window.Page_wiki = (() => {
<div style="padding-bottom:var(--space-2)">
${data.hinweis ? `<div style="background:var(--c-surface-2);border-radius:var(--radius-md);
padding:var(--space-3);margin-bottom:var(--space-3);font-size:var(--text-sm);
color:var(--c-text-secondary)"> ${_esc(data.hinweis)}</div>` : ''}
color:var(--c-text-secondary)"> ${UI.escape(data.hinweis)}</div>` : ''}
${cardsHtml}
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-2);
text-align:center">