Wishlist TV: show synopsis + clickable cast bubbles in the expanded panel
Your idea — use the empty space when a show is expanded. Expanding an orb now lazily loads the show detail and lays out an info bar: the show SYNOPSIS on the left, a row of CAST bubbles on the right (circular photos / initials fallback, clickable -> opens the person page). This surfaces reliable show-level context even for reality shows whose episodes have no TMDB overview (e.g. Love Island). Reuses the existing detail endpoints (owned -> /detail/show, else /tmdb/show); one fetch per show, cached on the group. All scoped under .vwsh-nebula — music wishlist untouched.
This commit is contained in:
parent
e8a0e1256b
commit
853dbcf40f
2 changed files with 67 additions and 7 deletions
|
|
@ -2553,6 +2553,31 @@ body[data-side="video"] #soulsync-toggle { display: none; }
|
|||
.vwsh-nebula .wl-album-tile.vwsh-tile--sel { border: 1px solid hsla(var(--orb-hue, 230), 70%, 60%, 0.6);
|
||||
box-shadow: 0 0 0 1px hsla(var(--orb-hue, 230), 70%, 55%, 0.4), 0 8px 22px -10px hsla(var(--orb-hue, 230), 70%, 45%, 0.6); }
|
||||
|
||||
/* show info bar — synopsis (left) + clickable cast bubbles (right), lazy-loaded */
|
||||
.vwsh-nebula .vwsh-info:empty { display: none; }
|
||||
.vwsh-nebula .vwsh-info { display: flex; gap: 20px; align-items: flex-start; text-align: left; margin: 4px 0 18px; }
|
||||
.vwsh-nebula .vwsh-info-syn { flex: 1 1 0; min-width: 0; font-size: 12.5px; line-height: 1.55; color: rgba(255, 255, 255, 0.6);
|
||||
display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.vwsh-nebula .vwsh-info-cast { flex: 0 0 auto; max-width: 48%; display: flex; flex-wrap: wrap; gap: 12px 10px; justify-content: flex-end; }
|
||||
.vwsh-nebula .vwsh-cast { width: 56px; padding: 0; border: none; background: none; cursor: pointer;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 5px; }
|
||||
.vwsh-nebula .vwsh-cast-img { position: relative; width: 46px; height: 46px; border-radius: 50%; overflow: hidden;
|
||||
background: #1c1c24; border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; }
|
||||
.vwsh-nebula .vwsh-cast:hover .vwsh-cast-img { transform: translateY(-2px);
|
||||
border-color: hsla(var(--orb-hue, 230), 70%, 62%, 0.75); box-shadow: 0 6px 16px -4px hsla(var(--orb-hue, 230), 70%, 50%, 0.6); }
|
||||
.vwsh-nebula .vwsh-cast-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
|
||||
.vwsh-nebula .vwsh-cast--ph .vwsh-cast-img img { display: none; }
|
||||
.vwsh-nebula .vwsh-cast-ini { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 13px; font-weight: 800; color: rgba(255, 255, 255, 0.45); }
|
||||
.vwsh-nebula .vwsh-cast-name { font-size: 9.5px; font-weight: 600; color: rgba(255, 255, 255, 0.55);
|
||||
text-align: center; line-height: 1.2; max-width: 56px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.vwsh-nebula .vwsh-cast:hover .vwsh-cast-name { color: rgba(255, 255, 255, 0.85); }
|
||||
@media (max-width: 640px) {
|
||||
.vwsh-nebula .vwsh-info { flex-direction: column; }
|
||||
.vwsh-nebula .vwsh-info-cast { max-width: 100%; justify-content: flex-start; }
|
||||
}
|
||||
|
||||
/* #1/#2 — the picked season drops its episodes here, full-width, as 2-line cards */
|
||||
.vwsh-nebula .vwsh-ep-area:empty { display: none; }
|
||||
.vwsh-nebula .vwsh-ep-area { margin-top: 14px; }
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
var gstyle = 'animation-delay:' + Math.min(idx * 45, 700) + 'ms;--orb-hue:' + hue +
|
||||
(sh.poster_url ? ";--vwsh-poster:url('" + esc(sh.poster_url) + "')" : '');
|
||||
var prog = total ? Math.max(0, Math.min(1, (sh.done || 0) / total)) : 0; // #4 acquisition progress
|
||||
return '<div class="wl-orb-group" data-vwsh-group style="' + gstyle + '">' +
|
||||
return '<div class="wl-orb-group" data-vwsh-group data-vwsh-tmdb="' + esc(sh.tmdb_id) + '" style="' + gstyle + '">' +
|
||||
'<button class="wl-orb-remove" type="button" data-vwsh-rm="show" data-tmdb="' + esc(sh.tmdb_id) + '" title="Remove show">✕</button>' +
|
||||
'<div class="wl-orb-tooltip">' + esc(sh.title) + '<br><span>' + eps + '</span></div>' +
|
||||
'<div class="wl-orb ' + orbSize(total) + '" data-vwsh-orb style="--vwsh-prog:' + prog + '">' +
|
||||
|
|
@ -109,11 +109,42 @@
|
|||
'</div>' +
|
||||
'<div class="wl-orb-label" data-vwsh-open-show data-vwsh-src="' + src + '" data-vwsh-id="' + esc(openId) + '" title="' + esc(sh.title) + '">' + esc(sh.title) + '</div>' +
|
||||
'<div class="wl-orb-meta">' + eps + (sh.done ? ' · ' + sh.done + ' done' : '') + '</div>' +
|
||||
'<div class="wl-orb-expanded"><div class="wl-album-fan">' + tiles + '</div>' +
|
||||
'<div class="wl-orb-expanded">' +
|
||||
'<div class="vwsh-info" data-vwsh-info></div>' +
|
||||
'<div class="wl-album-fan">' + tiles + '</div>' +
|
||||
'<div class="vwsh-ep-area" data-vwsh-ep-area></div></div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Lazily load the show's synopsis + cast when its orb first expands, and lay
|
||||
// them either side of the season fan (synopsis left, clickable cast right).
|
||||
function loadShowInfo(group) {
|
||||
if (!group || group.getAttribute('data-vwsh-info-loaded')) return;
|
||||
group.setAttribute('data-vwsh-info-loaded', '1');
|
||||
var tmdb = parseInt(group.getAttribute('data-vwsh-tmdb'), 10);
|
||||
var sh = state.showData[tmdb]; if (!sh) return;
|
||||
var url = sh.library_id != null ? '/api/video/detail/show/' + sh.library_id : '/api/video/tmdb/show/' + tmdb;
|
||||
fetch(url, { headers: { Accept: 'application/json' } })
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (d) {
|
||||
var info = group.querySelector('[data-vwsh-info]');
|
||||
if (!d || !info) return;
|
||||
var cast = (d.cast || []).slice(0, 8).map(function (c) {
|
||||
var photo = c.photo
|
||||
? '<img src="' + esc(c.photo) + '" alt="" loading="lazy" onerror="this.parentNode.classList.add(\'vwsh-cast--ph\')">'
|
||||
: '';
|
||||
return '<button class="vwsh-cast' + (c.photo ? '' : ' vwsh-cast--ph') + '" type="button" ' +
|
||||
'data-vwsh-open-person data-id="' + esc(c.tmdb_id) + '" ' +
|
||||
'title="' + esc(c.name) + (c.character ? ' — ' + esc(c.character) : '') + '">' +
|
||||
'<span class="vwsh-cast-img"><span class="vwsh-cast-ini">' + esc(initials(c.name)) + '</span>' + photo + '</span>' +
|
||||
'<span class="vwsh-cast-name">' + esc(c.name) + '</span></button>';
|
||||
}).join('');
|
||||
info.innerHTML = (d.overview ? '<div class="vwsh-info-syn">' + esc(d.overview) + '</div>' : '') +
|
||||
(cast ? '<div class="vwsh-info-cast">' + cast + '</div>' : '');
|
||||
})
|
||||
.catch(function () { /* best-effort */ });
|
||||
}
|
||||
|
||||
// A single episode as a rich card: still + title + meta + synopsis. The card
|
||||
// links to the show detail (episodes have no page of their own).
|
||||
function epCard(sh, se, e) {
|
||||
|
|
@ -278,12 +309,13 @@
|
|||
var rm = e.target.closest('[data-vwsh-rm]');
|
||||
if (rm) { e.preventDefault(); e.stopPropagation(); doRemove(rm); return; }
|
||||
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
||||
var open = e.target.closest('[data-vwsh-open-show], [data-vwsh-open-movie]');
|
||||
var open = e.target.closest('[data-vwsh-open-show], [data-vwsh-open-movie], [data-vwsh-open-person]');
|
||||
if (open) {
|
||||
var person = open.hasAttribute('data-vwsh-open-person');
|
||||
document.dispatchEvent(new CustomEvent('soulsync:video-open-detail', {
|
||||
detail: { kind: open.hasAttribute('data-vwsh-open-show') ? 'show' : 'movie',
|
||||
id: parseInt(open.getAttribute('data-vwsh-id'), 10),
|
||||
source: open.getAttribute('data-vwsh-src') || 'tmdb' },
|
||||
detail: { kind: person ? 'person' : (open.hasAttribute('data-vwsh-open-show') ? 'show' : 'movie'),
|
||||
id: parseInt(person ? open.getAttribute('data-id') : open.getAttribute('data-vwsh-id'), 10),
|
||||
source: person ? 'tmdb' : (open.getAttribute('data-vwsh-src') || 'tmdb') },
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
|
@ -298,7 +330,10 @@
|
|||
return;
|
||||
}
|
||||
var orb = e.target.closest('[data-vwsh-orb]');
|
||||
if (orb) { var g = orb.closest('.wl-orb-group'); if (g) g.classList.toggle('expanded'); } // show → seasons
|
||||
if (orb) { // show → reveal seasons + lazily load the synopsis/cast info bar
|
||||
var g = orb.closest('.wl-orb-group');
|
||||
if (g && g.classList.toggle('expanded')) loadShowInfo(g);
|
||||
}
|
||||
}
|
||||
|
||||
function wire() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue