Sprint 11: Freunde & Chat + Phosphor-Icon-Vollmigration

- Freundschaften (pending/accepted), Nutzersuche, Anfragen per Push
- Direktnachrichten mit Polling, iMessage-Stil, Deep-Links aus Push
- Alle Seiten (map, places, diary, health, dog-profile, sitting, knigge,
  forum, wiki, walks) vollständig auf Phosphor-Icons migriert
- Wikidata-Rassen-Scraper (~833 neue Rassen, lokal gespiegelte Fotos)
- TheDogAPI lokal gespiegelt (169 Rassen + Fotos)
- Quiz-Result-Cards horizontal (korrekte Bildproportionen)
- SW by-v89
This commit is contained in:
rene 2026-04-15 21:33:53 +02:00
parent 96bd57f0ad
commit 097295c628
44 changed files with 9980 additions and 300 deletions

View file

@ -65,10 +65,10 @@ window.Page_walks = (() => {
<!-- Toolbar -->
<div class="walks-toolbar">
<div class="walks-view-toggle" id="walks-view-toggle">
<button class="walks-view-btn active" data-view="liste">📋 Liste</button>
<button class="walks-view-btn" data-view="karte">🗺 Karte</button>
<button class="walks-view-btn active" data-view="liste">${UI.icon('list')} Liste</button>
<button class="walks-view-btn" data-view="karte">${UI.icon('map-trifold')} Karte</button>
</div>
<button class="btn btn-primary btn-sm" id="walks-create-btn">+ Treffen planen</button>
<button class="btn btn-primary btn-sm" id="walks-create-btn">${UI.icon('plus')} Treffen planen</button>
</div>
<!-- Liste -->
@ -143,7 +143,7 @@ window.Page_walks = (() => {
if (!_data.length) {
el.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">🐕</div>
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('dog')}</div>
<p style="color:var(--c-text-secondary)">Noch keine Treffen in deiner Nähe.</p>
<button class="btn btn-primary" style="margin-top:var(--space-4)" id="walks-first-btn">
Erstes Treffen planen
@ -160,12 +160,12 @@ window.Page_walks = (() => {
let html = '';
if (heute.length) {
html += `<div class="walks-section-label">🌟 Heute</div>`;
html += `<div class="walks-section-label">${UI.icon('star')} Heute</div>`;
html += heute.map(w => _walkCardHTML(w)).join('');
}
if (upcoming.length) {
html += `<div class="walks-section-label">📅 Demnächst</div>`;
html += `<div class="walks-section-label">${UI.icon('calendar-dots')} Demnächst</div>`;
html += upcoming.map(w => _walkCardHTML(w)).join('');
}
@ -190,12 +190,12 @@ window.Page_walks = (() => {
</div>
<div class="walks-card-body">
<div class="walks-card-title">${_esc(w.titel)}</div>
${w.ort_name ? `<div class="walks-card-ort">📍 ${_esc(w.ort_name)}</div>` : ''}
${w.ort_name ? `<div class="walks-card-ort">${UI.icon('map-pin')} ${_esc(w.ort_name)}</div>` : ''}
<div class="walks-card-meta">
<span class="walks-badge ${isFull ? 'walks-badge--full' : 'walks-badge--open'}">
${isFull ? '🔴 Voll' : `🟢 ${spots} Platz${spots !== 1 ? 'e' : ''} frei`}
</span>
<span class="walks-badge">🐾 ${w.teilnehmer_count}/${w.max_teilnehmer}</span>
<span class="walks-badge">${UI.icon('paw-print')} ${w.teilnehmer_count}/${w.max_teilnehmer}</span>
${isOwn ? '<span class="walks-badge walks-badge--own">Mein Treffen</span>' : ''}
</div>
</div>
@ -241,7 +241,7 @@ window.Page_walks = (() => {
html: `<div style="background:${color};color:#fff;font-size:14px;font-weight:700;
width:32px;height:32px;border-radius:50%;display:flex;align-items:center;
justify-content:center;box-shadow:0 2px 5px rgba(0,0,0,0.3);
border:2px solid rgba(255,255,255,0.8)">🐕</div>`,
border:2px solid rgba(255,255,255,0.8)">${UI.icon('dog')}</div>`,
iconSize: [32, 32], iconAnchor: [16, 16],
});
const m = L.marker([w.lat, w.lon], { icon })
@ -273,8 +273,8 @@ window.Page_walks = (() => {
const teilnehmerHTML = walk.teilnehmer?.length
? walk.teilnehmer.map(t => `
<div class="walks-participant">
<span class="walks-participant-name">🧑 ${_esc(t.user_name)}</span>
${t.hunde ? `<span class="walks-participant-hunde">🐕 ${_esc(t.hunde)}</span>` : ''}
<span class="walks-participant-name">${UI.icon('user')} ${_esc(t.user_name)}</span>
${t.hunde ? `<span class="walks-participant-hunde">${UI.icon('dog')} ${_esc(t.hunde)}</span>` : ''}
</div>`).join('')
: `<p style="color:var(--c-text-muted)">Noch keine Teilnehmer.</p>`;
@ -284,12 +284,12 @@ window.Page_walks = (() => {
${_fmtDate(walk.datum)}<br>
<strong>um ${walk.uhrzeit} Uhr</strong>
</div>
${walk.ort_name ? `<div style="margin-top:var(--space-2);color:var(--c-text-secondary)">📍 ${_esc(walk.ort_name)}</div>` : ''}
${walk.ort_name ? `<div style="margin-top:var(--space-2);color:var(--c-text-secondary)">${UI.icon('map-pin')} ${_esc(walk.ort_name)}</div>` : ''}
<div style="margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap">
<span class="walks-badge ${isFull ? 'walks-badge--full' : 'walks-badge--open'}">
${isFull ? '🔴 Voll' : `🟢 ${spots} Platz${spots !== 1 ? 'e' : ''} frei`}
</span>
<span class="walks-badge">🐾 ${walk.teilnehmer_count}/${walk.max_teilnehmer} Teilnehmer</span>
<span class="walks-badge">${UI.icon('paw-print')} ${walk.teilnehmer_count}/${walk.max_teilnehmer} Teilnehmer</span>
${isOwn ? '<span class="walks-badge walks-badge--own">Dein Treffen</span>' : ''}
</div>
</div>
@ -330,11 +330,11 @@ window.Page_walks = (() => {
} else {
footer = `
<button type="button" class="btn btn-secondary flex-1" id="wd-close">Schließen</button>
<button type="button" class="btn btn-primary flex-1" id="wd-join">🐕 Mitmachen</button>
<button type="button" class="btn btn-primary flex-1" id="wd-join">${UI.icon('dog')} Mitmachen</button>
`;
}
UI.modal.open({ title: `🐕 ${walk.titel}`, body, footer });
UI.modal.open({ title: `${UI.icon('dog')} ${walk.titel}`, body, footer });
document.getElementById('wd-close')?.addEventListener('click', UI.modal.close);
@ -398,14 +398,14 @@ window.Page_walks = (() => {
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer;
padding:var(--space-2) 0">
<input type="checkbox" name="dog" value="${d.id}" checked>
🐕 ${_esc(d.name)}
${UI.icon('dog')} ${_esc(d.name)}
</label>`).join('')
: `<p style="color:var(--c-text-muted)">Keine Hunde im Profil — du kannst trotzdem mitmachen.</p>`;
const body = `
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
${_fmtDate(walk.datum)} um ${walk.uhrzeit} Uhr<br>
${walk.ort_name ? `📍 ${_esc(walk.ort_name)}` : ''}
${walk.ort_name ? `${UI.icon('map-pin')} ${_esc(walk.ort_name)}` : ''}
</p>
<div class="form-group">
<label class="form-label">Mit welchen Hunden?</label>
@ -415,7 +415,7 @@ window.Page_walks = (() => {
const footer = `
<button type="button" class="btn btn-secondary flex-1" id="join-cancel">Abbrechen</button>
<button type="button" class="btn btn-primary flex-1" id="join-confirm">🐕 Mitmachen</button>
<button type="button" class="btn btn-primary flex-1" id="join-confirm">${UI.icon('dog')} Mitmachen</button>
`;
UI.modal.open({ title: `Treffen beitreten`, body, footer });
@ -485,12 +485,12 @@ window.Page_walks = (() => {
value="${_esc(v.ort_name || '')}"
placeholder="z. B. Parkeingang Nordseite, U-Bahn Volkspark"
style="flex:1">
<button type="button" class="btn btn-secondary" id="walk-gps-btn" title="GPS">📍</button>
<button type="button" class="btn btn-secondary" id="walk-gps-btn" title="GPS">${UI.icon('map-pin')}</button>
</div>
<input type="hidden" name="lat" id="walk-lat" value="${v.lat || ''}">
<input type="hidden" name="lon" id="walk-lon" value="${v.lon || ''}">
<small id="walk-gps-hint" style="color:var(--c-text-secondary)">
${v.lat ? '✅ Position gespeichert' : 'GPS-Button für aktuellen Standort'}
${v.lat ? `${UI.icon('check')} Position gespeichert` : 'GPS-Button für aktuellen Standort'}
</small>
</div>
@ -512,11 +512,11 @@ window.Page_walks = (() => {
const footer = `
<button type="button" class="btn btn-secondary flex-1" id="wf-cancel">Abbrechen</button>
<button type="submit" form="walk-form" class="btn btn-primary flex-1">
${isEdit ? 'Speichern' : '📅 Treffen planen'}
${isEdit ? 'Speichern' : `${UI.icon('calendar-dots')} Treffen planen`}
</button>
`;
UI.modal.open({ title: isEdit ? 'Treffen bearbeiten' : '🐕 Treffen planen', body, footer });
UI.modal.open({ title: isEdit ? 'Treffen bearbeiten' : `${UI.icon('dog')} Treffen planen`, body, footer });
document.getElementById('wf-cancel')?.addEventListener('click', UI.modal.close);
@ -528,7 +528,7 @@ window.Page_walks = (() => {
_userPos = pos;
document.getElementById('walk-lat').value = pos.lat;
document.getElementById('walk-lon').value = pos.lon;
document.getElementById('walk-gps-hint').textContent = '✅ Standort ermittelt';
document.getElementById('walk-gps-hint').innerHTML = `${UI.icon('check')} Standort ermittelt`;
} catch { UI.toast.error('GPS nicht verfügbar.'); }
UI.setLoading(btn, false);
});
@ -539,7 +539,7 @@ window.Page_walks = (() => {
const fd = UI.formData(e.target);
if (!fd.lat || !fd.lon) {
UI.toast.warning('Bitte GPS-Position ermitteln (📍).');
UI.toast.warning('Bitte GPS-Position ermitteln.');
return;
}