Feature: Wiki Photo-Gallery mit Thumbnails + Lightbox, alle Fotos einer Rasse anklickbar (SW by-v664)
This commit is contained in:
parent
1d1171e5f2
commit
84e6bfdd82
5 changed files with 223 additions and 33 deletions
|
|
@ -5571,6 +5571,139 @@ html.modal-open {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Wiki Gallery ────────────────────────────────────────── */
|
||||||
|
.wiki-gallery-wrap {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
.wiki-gallery-main {
|
||||||
|
width: 100%;
|
||||||
|
height: 240px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center top;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.wiki-gallery-strip {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-2);
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: var(--space-2) 0 0;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
.wiki-gallery-strip::-webkit-scrollbar { display: none; }
|
||||||
|
.wiki-gallery-thumb {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 64px; height: 64px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
transition: border-color .15s;
|
||||||
|
}
|
||||||
|
.wiki-gallery-thumb.active { border-color: var(--c-primary); }
|
||||||
|
.wiki-gallery-thumb img {
|
||||||
|
width: 100%; height: 100%; object-fit: cover;
|
||||||
|
}
|
||||||
|
.wiki-gallery-thumb-label {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0; left: 0; right: 0;
|
||||||
|
background: rgba(0,0,0,.55);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 8px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.wiki-gallery-expand {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--space-2);
|
||||||
|
right: var(--space-2);
|
||||||
|
width: 34px; height: 34px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0,0,0,.45);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
transition: background .15s;
|
||||||
|
}
|
||||||
|
.wiki-gallery-expand:hover { background: rgba(0,0,0,.65); }
|
||||||
|
|
||||||
|
/* ── Wiki Lightbox ───────────────────────────────────────── */
|
||||||
|
#wiki-lightbox {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.wlb-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,.88);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
.wlb-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
max-width: min(92vw, 680px);
|
||||||
|
width: 100%;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
.wlb-img {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 72vh;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
}
|
||||||
|
.wlb-close {
|
||||||
|
position: absolute;
|
||||||
|
top: -44px;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(255,255,255,.12);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
width: 36px; height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.wlb-prev, .wlb-next {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(255,255,255,.12);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
width: 40px; height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
transition: background .15s;
|
||||||
|
}
|
||||||
|
.wlb-prev { left: -48px; }
|
||||||
|
.wlb-next { right: -48px; }
|
||||||
|
.wlb-prev:hover, .wlb-next:hover { background: rgba(255,255,255,.25); }
|
||||||
|
.wlb-caption { color: rgba(255,255,255,.75); font-size: var(--text-sm); }
|
||||||
|
.wlb-counter { color: rgba(255,255,255,.45); font-size: var(--text-xs); }
|
||||||
|
|
||||||
/* Steckbrief-Grid */
|
/* Steckbrief-Grid */
|
||||||
.wiki-steckbrief-grid {
|
.wiki-steckbrief-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
||||||
|
|
@ -93,9 +93,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||||
<link rel="stylesheet" href="/css/design-system.css?v=663">
|
<link rel="stylesheet" href="/css/design-system.css?v=664">
|
||||||
<link rel="stylesheet" href="/css/layout.css?v=663">
|
<link rel="stylesheet" href="/css/layout.css?v=664">
|
||||||
<link rel="stylesheet" href="/css/components.css?v=663">
|
<link rel="stylesheet" href="/css/components.css?v=664">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -562,7 +562,7 @@
|
||||||
<script src="/js/api.js?v=94"></script>
|
<script src="/js/api.js?v=94"></script>
|
||||||
<script src="/js/ui.js?v=94"></script>
|
<script src="/js/ui.js?v=94"></script>
|
||||||
<script src="/js/app.js?v=94"></script>
|
<script src="/js/app.js?v=94"></script>
|
||||||
<script src="/js/worlds.js?v=663"></script>
|
<script src="/js/worlds.js?v=664"></script>
|
||||||
|
|
||||||
<!-- Feature-Seiten werden lazy geladen -->
|
<!-- Feature-Seiten werden lazy geladen -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
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 APP_VERSION = '1.3.0'; // ← semantische Version, wird bei make release gesetzt
|
||||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -730,36 +730,34 @@ window.Page_wiki = (() => {
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const _dogSvgLg = _DOG_SILHOUETTE.replace('width="48" height="48"', 'width="56" height="56"');
|
const _dogSvgLg = _DOG_SILHOUETTE.replace('width="48" height="48"', 'width="56" height="56"');
|
||||||
const photoHtml = rasse.foto_url
|
// Alle Fotos: Hauptbild zuerst, dann Community-Fotos
|
||||||
? `<div class="wiki-detail-hero-photo-wrap">
|
const allFotos = [];
|
||||||
<img class="wiki-detail-photo" src="${_esc(rasse.foto_url)}" alt="${_esc(rasse.name)}"
|
if (rasse.foto_url) allFotos.push({ foto_url: rasse.foto_url, user_name: null });
|
||||||
onerror="this.parentElement.style.display='none';this.parentElement.nextElementSibling.style.display='flex'">
|
(rasse.user_fotos || []).forEach(f => allFotos.push(f));
|
||||||
</div>
|
|
||||||
<div class="wiki-detail-photo-placeholder" style="display:none">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>`
|
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>`;
|
: `<div class="wiki-detail-photo-placeholder">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>`;
|
||||||
|
|
||||||
const berichteHtml = _renderBerichteHtml(rasse.berichte || [], slug);
|
const berichteHtml = _renderBerichteHtml(rasse.berichte || [], slug);
|
||||||
|
const userFotosHtml = '';
|
||||||
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 body = `
|
const body = `
|
||||||
${/* 1. Hero */ ''}
|
${/* 1. Hero */ ''}
|
||||||
|
|
@ -851,6 +849,65 @@ window.Page_wiki = (() => {
|
||||||
document.getElementById('wiki-zuchter-placeholder')?.remove();
|
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', () => {
|
document.getElementById('wiki-bericht-add-btn')?.addEventListener('click', () => {
|
||||||
UI.modal.close();
|
UI.modal.close();
|
||||||
setTimeout(() => _showBerichtForm(slug, rasse.name), 350);
|
setTimeout(() => _showBerichtForm(slug, rasse.name), 350);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Offline-Cache + Push Notifications + Tile-Cache
|
Offline-Cache + Push Notifications + Tile-Cache
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const CACHE_VERSION = 'by-v663';
|
const CACHE_VERSION = 'by-v664';
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue