video: person page — photos gallery + lightbox + 'also known as'
person() now returns profile images (thumb+full) and also_known_as. The person page shows an 'Also known as' line and a Photos rail that opens the shared fullscreen lightbox (arrows/Esc/counter).
This commit is contained in:
parent
8bbdd712f9
commit
df8f6a2acd
4 changed files with 92 additions and 2 deletions
|
|
@ -511,7 +511,8 @@ class TMDBClient:
|
|||
return None
|
||||
import requests
|
||||
r = requests.get(self.BASE + "/person/" + str(tmdb_id), params={
|
||||
"api_key": self.api_key, "append_to_response": "combined_credits,external_ids"}, timeout=15)
|
||||
"api_key": self.api_key,
|
||||
"append_to_response": "combined_credits,external_ids,images"}, timeout=15)
|
||||
r.raise_for_status()
|
||||
d = r.json() or {}
|
||||
if not d.get("id"):
|
||||
|
|
@ -542,6 +543,10 @@ class TMDBClient:
|
|||
for c in (cc.get("crew") or []):
|
||||
add(c, c.get("department") or "Crew", c.get("job") or None)
|
||||
credits.sort(key=lambda x: x["popularity"], reverse=True)
|
||||
profiles = (d.get("images") or {}).get("profiles") or []
|
||||
photos = [{"thumb": self.PROFILE + p["file_path"], "full": self.IMG + p["file_path"]}
|
||||
for p in profiles[:16] if p.get("file_path")]
|
||||
akas = [a for a in (d.get("also_known_as") or []) if a][:6]
|
||||
return {
|
||||
"tmdb_id": d.get("id"), "name": d.get("name"),
|
||||
"biography": d.get("biography") or None,
|
||||
|
|
@ -549,7 +554,7 @@ class TMDBClient:
|
|||
"birthday": d.get("birthday") or None, "deathday": d.get("deathday") or None,
|
||||
"place_of_birth": d.get("place_of_birth") or None,
|
||||
"photo": (self.PROFILE + d["profile_path"]) if d.get("profile_path") else None,
|
||||
"credits": credits}
|
||||
"photos": photos, "also_known_as": akas, "credits": credits}
|
||||
|
||||
|
||||
class TVDBClient:
|
||||
|
|
|
|||
|
|
@ -1111,8 +1111,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="vp-body">
|
||||
<p class="vp-aka" data-vp-aka hidden></p>
|
||||
<p class="vp-bio" data-vp-bio></p>
|
||||
<button class="vp-bio-more" type="button" data-vp-bio-more hidden>Read more</button>
|
||||
<div class="vp-photos-section" data-vp-photos-section hidden>
|
||||
<h2 class="vp-credits-title vp-known-title">Photos</h2>
|
||||
<div class="vp-photos" data-vp-photos></div>
|
||||
</div>
|
||||
<div class="vp-known-section" data-vp-known-section hidden>
|
||||
<h2 class="vp-credits-title vp-known-title">Known For</h2>
|
||||
<div class="vp-known-rail" data-vp-known></div>
|
||||
|
|
|
|||
|
|
@ -190,6 +190,53 @@
|
|||
img.src = '/api/video/img?u=' + encodeURIComponent(photoUrl);
|
||||
}
|
||||
|
||||
// ── photos gallery + lightbox (reuses the shared .vd-lightbox styles) ──────
|
||||
var photoFulls = [], lbIdx = 0;
|
||||
function renderPhotos(photos) {
|
||||
var sec = q('[data-vp-photos-section]'), host = q('[data-vp-photos]');
|
||||
photos = photos || [];
|
||||
photoFulls = photos.map(function (p) { return p.full; });
|
||||
if (!sec || !host) return;
|
||||
if (!photos.length) { sec.hidden = true; return; }
|
||||
sec.hidden = false;
|
||||
host.innerHTML = photos.map(function (p, i) {
|
||||
return '<button class="vp-photo-thumb" type="button" data-vp-shot="' + i + '">' +
|
||||
'<img src="' + esc(p.thumb) + '" alt="" loading="lazy"></button>';
|
||||
}).join('');
|
||||
}
|
||||
function openLightbox(idx) {
|
||||
if (!photoFulls.length) return;
|
||||
lbIdx = idx;
|
||||
var ov = document.getElementById('vp-lightbox');
|
||||
if (!ov) {
|
||||
ov = document.createElement('div'); ov.id = 'vp-lightbox'; ov.className = 'vd-lightbox';
|
||||
ov.addEventListener('click', function (e) {
|
||||
if (e.target.closest('[data-vp-lb-prev]')) lbStep(-1);
|
||||
else if (e.target.closest('[data-vp-lb-next]')) lbStep(1);
|
||||
else if (e.target === ov || e.target.closest('[data-vp-lb-close]')) closeLightbox();
|
||||
});
|
||||
document.body.appendChild(ov);
|
||||
}
|
||||
renderLb(); ov.classList.add('vd-lightbox--open');
|
||||
}
|
||||
function renderLb() {
|
||||
var ov = document.getElementById('vp-lightbox'); if (!ov) return;
|
||||
ov.innerHTML = '<button class="vd-lb-close" type="button" data-vp-lb-close aria-label="Close">×</button>' +
|
||||
'<button class="vd-lb-nav vd-lb-prev" type="button" data-vp-lb-prev aria-label="Previous">‹</button>' +
|
||||
'<img class="vd-lb-img" src="' + esc(photoFulls[lbIdx]) + '" alt="">' +
|
||||
'<button class="vd-lb-nav vd-lb-next" type="button" data-vp-lb-next aria-label="Next">›</button>' +
|
||||
'<div class="vd-lb-count">' + (lbIdx + 1) + ' / ' + photoFulls.length + '</div>';
|
||||
}
|
||||
function lbStep(dir) { lbIdx = (lbIdx + dir + photoFulls.length) % photoFulls.length; renderLb(); }
|
||||
function closeLightbox() {
|
||||
var ov = document.getElementById('vp-lightbox');
|
||||
if (ov) { ov.classList.remove('vd-lightbox--open'); ov.innerHTML = ''; }
|
||||
}
|
||||
function lbOpen() {
|
||||
var ov = document.getElementById('vp-lightbox');
|
||||
return ov && ov.classList.contains('vd-lightbox--open');
|
||||
}
|
||||
|
||||
function computeAge(birthday, deathday) {
|
||||
if (!birthday) return null;
|
||||
var b = new Date(birthday), end = deathday ? new Date(deathday) : new Date();
|
||||
|
|
@ -241,9 +288,16 @@
|
|||
var m = q('[data-vp-meta]');
|
||||
if (m) m.innerHTML = meta.map(function (x) { return '<span>' + esc(x) + '</span>'; }).join('');
|
||||
|
||||
var aka = q('[data-vp-aka]');
|
||||
if (aka) {
|
||||
var names = d.also_known_as || [];
|
||||
aka.textContent = names.length ? 'Also known as: ' + names.join(' · ') : '';
|
||||
aka.hidden = !names.length;
|
||||
}
|
||||
var bio = q('[data-vp-bio]'), more = q('[data-vp-bio-more]');
|
||||
if (bio) { bio.textContent = d.biography || ''; bio.hidden = !d.biography; bio.classList.remove('vp-bio--open'); }
|
||||
if (more) { more.hidden = !((d.biography || '').length > 320); more.textContent = 'Read more'; }
|
||||
renderPhotos(d.photos);
|
||||
|
||||
applyFilters();
|
||||
var sub = document.querySelector('.video-subpage[data-video-subpage="video-person-detail"]');
|
||||
|
|
@ -264,6 +318,10 @@
|
|||
var ce = q('[data-vp-credits-empty]'); if (ce) ce.hidden = true;
|
||||
var ks = q('[data-vp-known-section]'); if (ks) ks.hidden = true;
|
||||
var k = q('[data-vp-known]'); if (k) k.innerHTML = '';
|
||||
var psec = q('[data-vp-photos-section]'); if (psec) psec.hidden = true;
|
||||
var pho = q('[data-vp-photos]'); if (pho) pho.innerHTML = '';
|
||||
var ak = q('[data-vp-aka]'); if (ak) ak.hidden = true;
|
||||
photoFulls = [];
|
||||
fetch(PERSON_URL + id, { headers: { 'Accept': 'application/json' } })
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (d) {
|
||||
|
|
@ -294,6 +352,8 @@
|
|||
if (deptBtn && r.contains(deptBtn)) {
|
||||
dept = deptBtn.getAttribute('data-vp-dept'); applyFilters(); return;
|
||||
}
|
||||
var shot = e.target.closest('[data-vp-shot]');
|
||||
if (shot && r.contains(shot)) { openLightbox(parseInt(shot.getAttribute('data-vp-shot'), 10) || 0); return; }
|
||||
var moreBtn = e.target.closest('[data-vp-bio-more]');
|
||||
if (moreBtn && r.contains(moreBtn)) {
|
||||
var bio = q('[data-vp-bio]');
|
||||
|
|
@ -319,6 +379,12 @@
|
|||
function init() {
|
||||
document.addEventListener('soulsync:video-open-detail', onOpen);
|
||||
document.addEventListener('click', onClick);
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (!lbOpen()) return;
|
||||
if (e.key === 'Escape') closeLightbox();
|
||||
else if (e.key === 'ArrowLeft') lbStep(-1);
|
||||
else if (e.key === 'ArrowRight') lbStep(1);
|
||||
});
|
||||
var sortSel = document.querySelector('[data-vp-sort]');
|
||||
if (sortSel) sortSel.addEventListener('change', function () {
|
||||
sortBy = sortSel.value; renderCredits();
|
||||
|
|
|
|||
|
|
@ -1361,3 +1361,17 @@ a.vd-prov:hover img, a.vd-prov:hover .vd-prov-ph { border-color: rgba(var(--vd-a
|
|||
.vd-cm-name { display: block; font-size: 13px; font-weight: 700; color: #fff; line-height: 1.3; }
|
||||
.vd-cm-char { display: block; font-size: 12px; color: rgba(255, 255, 255, 0.55); line-height: 1.3; }
|
||||
.vd-cm-eps { display: block; font-size: 11px; color: rgb(var(--vd-accent-rgb)); margin-top: 2px; font-weight: 600; }
|
||||
|
||||
/* ── person: photos rail + also-known-as ─────────────────────────────────── */
|
||||
.vp-aka { font-size: 13.5px; color: rgba(255, 255, 255, 0.6); margin: 0 0 18px; font-style: italic; }
|
||||
.vp-photos-section { position: relative; z-index: 2; margin-bottom: 38px; }
|
||||
.vp-photos { display: flex; gap: 14px; overflow-x: auto; padding-bottom: 12px; scroll-snap-type: x proximity; }
|
||||
.vp-photos::-webkit-scrollbar { height: 8px; }
|
||||
.vp-photos::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 4px; }
|
||||
.vp-photo-thumb {
|
||||
flex: 0 0 120px; width: 120px; aspect-ratio: 2 / 3; scroll-snap-align: start; padding: 0; border: 0; cursor: pointer;
|
||||
border-radius: 10px; overflow: hidden; background: #000; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
.vp-photo-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.vp-photo-thumb:hover { transform: translateY(-4px); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue