Feature: Wiki Photo-Gallery mit Thumbnails + Lightbox, alle Fotos einer Rasse anklickbar (SW by-v664)

This commit is contained in:
rene 2026-05-03 21:23:41 +02:00
parent 1d1171e5f2
commit 84e6bfdd82
5 changed files with 223 additions and 33 deletions

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '663'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '664'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.3.0'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';

View file

@ -730,36 +730,34 @@ window.Page_wiki = (() => {
: '';
const _dogSvgLg = _DOG_SILHOUETTE.replace('width="48" height="48"', 'width="56" height="56"');
const photoHtml = rasse.foto_url
? `<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';this.parentElement.nextElementSibling.style.display='flex'">
</div>
<div class="wiki-detail-photo-placeholder" style="display:none">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>`
// Alle Fotos: Hauptbild zuerst, dann Community-Fotos
const allFotos = [];
if (rasse.foto_url) allFotos.push({ foto_url: rasse.foto_url, user_name: null });
(rasse.user_fotos || []).forEach(f => allFotos.push(f));
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)}"
onerror="this.style.display='none';document.getElementById('wiki-photo-fallback').style.display='flex'">
<div id="wiki-photo-fallback" class="wiki-detail-photo-placeholder" style="display:none">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>
${allFotos.length > 1 ? `
<div class="wiki-gallery-strip" id="wiki-gallery-strip">
${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)}" alt="" loading="lazy">
${f.user_name ? `<span class="wiki-gallery-thumb-label">von ${_esc(f.user_name)}</span>` : ''}
</button>`).join('')}
</div>` : ''}
<button class="wiki-gallery-expand" id="wiki-gallery-expand" aria-label="Vollbild">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#arrows-out"></use></svg>
</button>
</div>`
: `<div class="wiki-detail-photo-placeholder">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>`;
const berichteHtml = _renderBerichteHtml(rasse.berichte || [], slug);
const userFotosHtml = (rasse.user_fotos || []).length
? `<div style="margin-top:var(--space-3)">
<div style="font-size:var(--text-xs);color:var(--c-text-muted);
font-weight:700;text-transform:uppercase;letter-spacing:.05em;
margin-bottom:var(--space-2)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#camera"></use></svg> Community-Fotos</div>
<div style="display:flex;gap:var(--space-2);overflow-x:auto;padding-bottom:4px">
${rasse.user_fotos.map(f => `
<div style="flex-shrink:0">
<img src="${_esc(f.foto_url)}" alt="${_esc(f.user_name)}"
style="height:80px;width:80px;object-fit:cover;
border-radius:var(--radius-md);cursor:pointer"
onclick="document.querySelector('.wiki-detail-photo')?.setAttribute('src','${_esc(f.foto_url)}')">
<div style="font-size:9px;color:var(--c-text-muted);text-align:center;
margin-top:2px;max-width:80px;overflow:hidden;text-overflow:ellipsis;
white-space:nowrap">von ${_esc(f.user_name)}</div>
</div>
`).join('')}
</div>
</div>`
: '';
const userFotosHtml = '';
const body = `
${/* 1. Hero */ ''}
@ -851,6 +849,65 @@ window.Page_wiki = (() => {
document.getElementById('wiki-zuchter-placeholder')?.remove();
});
// Gallery-Thumbnails + Lightbox
const mainImg = document.getElementById('wiki-main-photo');
const strip = document.getElementById('wiki-gallery-strip');
if (strip && mainImg) {
strip.querySelectorAll('.wiki-gallery-thumb').forEach(btn => {
btn.addEventListener('click', () => {
const idx = parseInt(btn.dataset.idx);
mainImg.src = allFotos[idx].foto_url;
mainImg.style.display = '';
document.getElementById('wiki-photo-fallback').style.display = 'none';
strip.querySelectorAll('.wiki-gallery-thumb').forEach(b => b.classList.toggle('active', b === btn));
});
});
}
document.getElementById('wiki-gallery-expand')?.addEventListener('click', () => {
const src = mainImg?.src || allFotos[0]?.foto_url;
if (!src) return;
let curIdx = allFotos.findIndex(f => f.foto_url && src.endsWith(f.foto_url.split('/').pop()));
if (curIdx < 0) curIdx = 0;
function _lbOpen(idx) {
const f = allFotos[idx];
const lb = document.getElementById('wiki-lightbox');
lb.querySelector('.wlb-img').src = f.foto_url;
lb.querySelector('.wlb-caption').textContent = f.user_name ? `Foto von ${f.user_name}` : rasse.name;
lb.querySelector('.wlb-counter').textContent = `${idx + 1} / ${allFotos.length}`;
lb.querySelector('.wlb-prev').style.display = allFotos.length > 1 ? '' : 'none';
lb.querySelector('.wlb-next').style.display = allFotos.length > 1 ? '' : 'none';
curIdx = idx;
}
const lb = document.createElement('div');
lb.id = 'wiki-lightbox';
lb.innerHTML = `
<div class="wlb-backdrop"></div>
<div class="wlb-content">
<button class="wlb-close" aria-label="Schließen"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg></button>
<button class="wlb-prev" aria-label="Zurück"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#caret-left"></use></svg></button>
<img class="wlb-img" src="" alt="">
<button class="wlb-next" aria-label="Weiter"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#caret-right"></use></svg></button>
<div class="wlb-caption"></div>
<div class="wlb-counter"></div>
</div>`;
document.body.appendChild(lb);
_lbOpen(curIdx);
const close = () => lb.remove();
lb.querySelector('.wlb-close').addEventListener('click', close);
lb.querySelector('.wlb-backdrop').addEventListener('click', close);
lb.querySelector('.wlb-prev').addEventListener('click', () => _lbOpen((curIdx - 1 + allFotos.length) % allFotos.length));
lb.querySelector('.wlb-next').addEventListener('click', () => _lbOpen((curIdx + 1) % allFotos.length));
document.addEventListener('keydown', function onKey(e) {
if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); }
if (e.key === 'ArrowLeft') lb.querySelector('.wlb-prev').click();
if (e.key === 'ArrowRight') lb.querySelector('.wlb-next').click();
});
});
document.getElementById('wiki-bericht-add-btn')?.addEventListener('click', () => {
UI.modal.close();
setTimeout(() => _showBerichtForm(slug, rasse.name), 350);