Wishlist TV: episodes grouped under clickable season headers; show/season -> show page
Per feedback: - Removed the 'View show' button. The info bar is now strictly synopsis (left) + cast (right). - Episodes are shown grouped under a season header that links to the show page; clicking the show title or a season header navigates to the show detail. Clicking an episode SELECTS it (drives the info bar: episode synopsis + guest cast), click again to go back to show-level. - Dropped the season-tile reveal step (episodes always visible when expanded). Scoped under .vwsh-nebula; music wishlist untouched.
This commit is contained in:
parent
11cc9aa789
commit
7ef794fe74
2 changed files with 37 additions and 63 deletions
|
|
@ -2507,12 +2507,6 @@ body[data-side="video"] #soulsync-toggle { display: none; }
|
|||
background: linear-gradient(180deg, hsla(var(--orb-hue, 230), 55%, 12%, 0.55), rgba(12, 12, 16, 0.92)); }
|
||||
.vwsh-nebula .wl-orb-group.expanded > * { position: relative; z-index: 1; }
|
||||
|
||||
/* #2 season number stamped on the tile art (so seasons read as distinct) */
|
||||
.vwsh-nebula .wl-album-tile-art { position: relative; }
|
||||
.vwsh-nebula .vwsh-season-tag { position: absolute; top: 5px; left: 5px; z-index: 2; font-size: 12px; font-weight: 900;
|
||||
color: #fff; padding: 2px 7px; border-radius: 6px; letter-spacing: 0.02em; backdrop-filter: blur(4px);
|
||||
background: rgba(0, 0, 0, 0.58); border: 1px solid rgba(255, 255, 255, 0.18); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7); }
|
||||
|
||||
/* episode status dot (used in the episode-card meta line) */
|
||||
.vwsh-nebula .vwsh-ep-dot { display: inline-block; vertical-align: middle; margin-right: 6px;
|
||||
width: 7px; height: 7px; border-radius: 50%;
|
||||
|
|
@ -2545,14 +2539,6 @@ body[data-side="video"] #soulsync-toggle { display: none; }
|
|||
.vwsh-nebula .wl-orb.orb-md { width: 104px; height: 104px; }
|
||||
.vwsh-nebula .wl-orb.orb-lg { width: 124px; height: 124px; }
|
||||
|
||||
/* softer count badge (the big number read like a rating) */
|
||||
.vwsh-nebula .wl-album-tile-badge { font-size: 9px; font-weight: 800; letter-spacing: 0.02em; padding: 2px 6px;
|
||||
background: rgba(0, 0, 0, 0.55); color: rgba(255, 255, 255, 0.85); border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
backdrop-filter: blur(4px); }
|
||||
/* selected season tile glows in the show hue */
|
||||
.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; }
|
||||
|
|
@ -2579,19 +2565,30 @@ body[data-side="video"] #soulsync-toggle { display: none; }
|
|||
}
|
||||
.vwsh-nebula .vwsh-info-eyebrow { display: block; font-size: 10px; font-weight: 800; text-transform: uppercase;
|
||||
letter-spacing: 0.05em; color: hsl(var(--orb-hue, 230) 80% 76%); margin-bottom: 6px; }
|
||||
.vwsh-nebula .vwsh-info-viewshow { display: inline-block; margin-top: 12px; padding: 7px 14px; border-radius: 9px;
|
||||
font-size: 11.5px; font-weight: 800; cursor: pointer; color: #fff;
|
||||
border: 1px solid hsla(var(--orb-hue, 230), 70%, 55%, 0.5); background: hsla(var(--orb-hue, 230), 60%, 32%, 0.4);
|
||||
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease; }
|
||||
.vwsh-nebula .vwsh-info-viewshow:hover { background: hsla(var(--orb-hue, 230), 65%, 42%, 0.6);
|
||||
border-color: hsla(var(--orb-hue, 230), 70%, 64%, 0.8); transform: translateY(-1px); }
|
||||
/* selected episode card */
|
||||
.vwsh-nebula .vwsh-epc.vwsh-epc--sel { background: hsla(var(--orb-hue, 230), 48%, 22%, 0.5);
|
||||
border-color: hsla(var(--orb-hue, 230), 70%, 58%, 0.65); }
|
||||
|
||||
/* #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; }
|
||||
/* episodes grouped under a clickable season header (header → show page) */
|
||||
.vwsh-nebula .vwsh-seasons { display: flex; flex-direction: column; gap: 16px; }
|
||||
.vwsh-nebula .vwsh-szn-hd { display: flex; align-items: center; gap: 11px; padding: 7px 9px; margin-bottom: 9px; cursor: pointer;
|
||||
border-radius: 10px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
transition: background 0.15s ease, border-color 0.15s ease; }
|
||||
.vwsh-nebula .vwsh-szn-hd:hover { background: hsla(var(--orb-hue, 230), 45%, 24%, 0.45); border-color: hsla(var(--orb-hue, 230), 70%, 58%, 0.45); }
|
||||
.vwsh-nebula .vwsh-szn-thumb { flex-shrink: 0; width: 34px; height: 48px; border-radius: 5px; overflow: hidden; background: #16161d;
|
||||
display: flex; align-items: center; justify-content: center; }
|
||||
.vwsh-nebula .vwsh-szn-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.vwsh-nebula .vwsh-szn-ph { font-size: 16px; opacity: 0.4; }
|
||||
.vwsh-nebula .vwsh-szn-name { font-size: 14px; font-weight: 800; color: #fff; }
|
||||
.vwsh-nebula .vwsh-szn-count { font-size: 11.5px; font-weight: 600; color: rgba(255, 255, 255, 0.4); }
|
||||
.vwsh-nebula .vwsh-szn-go { margin-left: auto; font-size: 14px; font-weight: 800; color: rgba(255, 255, 255, 0.3); transition: color 0.15s ease, transform 0.15s ease; }
|
||||
.vwsh-nebula .vwsh-szn-hd:hover .vwsh-szn-go { color: hsl(var(--orb-hue, 230) 80% 75%); transform: translateX(3px); }
|
||||
.vwsh-nebula .vwsh-szn-rm { flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%; border: none; background: rgba(0, 0, 0, 0.35);
|
||||
color: rgba(255, 255, 255, 0.4); font-size: 12px; cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||||
opacity: 0; transition: all 0.15s ease; }
|
||||
.vwsh-nebula .vwsh-szn-hd:hover .vwsh-szn-rm { opacity: 1; }
|
||||
.vwsh-nebula .vwsh-szn-rm:hover { background: rgba(239, 68, 68, 0.85); color: #fff; }
|
||||
|
||||
.vwsh-nebula .vwsh-ep-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 10px;
|
||||
max-height: 420px; overflow-y: auto; padding: 2px; scrollbar-width: thin; }
|
||||
.vwsh-nebula .vwsh-epc { position: relative; display: flex; align-items: flex-start; gap: 11px; padding: 9px; cursor: pointer;
|
||||
|
|
|
|||
|
|
@ -77,21 +77,23 @@
|
|||
? '<img class="wl-orb-img" src="' + esc(sh.poster_url) + '" alt="" ' +
|
||||
'onerror="this.outerHTML=\'<div class="wl-orb-initials">' + esc(initials(sh.title)) + '</div>\'">'
|
||||
: '<div class="wl-orb-initials">' + esc(initials(sh.title)) + '</div>';
|
||||
// Season tiles are SELECTORS now — picking one renders its episodes full-
|
||||
// width below (so episode cards get room), instead of cramming inside.
|
||||
var tiles = (sh.seasons || []).map(function (se) {
|
||||
// Episodes are shown grouped under a clickable season header (header →
|
||||
// show page); each episode card SELECTS the episode (drives the info bar).
|
||||
var seasons = (sh.seasons || []).map(function (se) {
|
||||
var n = se.episodes.length;
|
||||
var posterUrl = se.poster_url || sh.poster_url || null;
|
||||
var inner = posterUrl ? '<img src="' + esc(posterUrl) + '" alt="">' : '<div class="wl-album-tile-fallback">📺</div>';
|
||||
return '<div class="wl-album-tile" data-vwsh-tile data-tmdb="' + esc(sh.tmdb_id) + '" data-vwsh-season="' + se.season_number + '">' +
|
||||
'<div class="wl-album-tile-art">' + inner + '<span class="vwsh-season-tag">S' + se.season_number + '</span></div>' +
|
||||
'<div class="wl-album-tile-info">' +
|
||||
'<div class="wl-album-tile-name">Season ' + se.season_number + '</div>' +
|
||||
'<div class="wl-album-tile-count">' + n + ' episode' + (n === 1 ? '' : 's') + '</div>' +
|
||||
var thumb = posterUrl ? '<img src="' + esc(posterUrl) + '" alt="">' : '<span class="vwsh-szn-ph">📺</span>';
|
||||
var cards = (se.episodes || []).map(function (e) { return epCard(sh, se, e); }).join('');
|
||||
return '<div class="vwsh-szn">' +
|
||||
'<div class="vwsh-szn-hd" data-vwsh-open-show data-vwsh-src="' + src + '" data-vwsh-id="' + esc(openId) + '" title="Open show page">' +
|
||||
'<span class="vwsh-szn-thumb">' + thumb + '</span>' +
|
||||
'<span class="vwsh-szn-name">Season ' + se.season_number + '</span>' +
|
||||
'<span class="vwsh-szn-count">' + n + ' ep</span>' +
|
||||
'<span class="vwsh-szn-go" aria-hidden="true">→</span>' +
|
||||
'<button class="vwsh-szn-rm" type="button" data-vwsh-rm="season" ' +
|
||||
'data-tmdb="' + esc(sh.tmdb_id) + '" data-s="' + se.season_number + '" title="Remove season">✕</button>' +
|
||||
'</div>' +
|
||||
'<span class="wl-album-tile-badge">' + n + ' ep</span>' +
|
||||
'<button class="wl-album-tile-remove" type="button" data-vwsh-rm="season" ' +
|
||||
'data-tmdb="' + esc(sh.tmdb_id) + '" data-s="' + se.season_number + '" title="Remove season">✕</button>' +
|
||||
'<div class="vwsh-ep-grid">' + cards + '</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
var eps = total + ' episode' + (total === 1 ? '' : 's');
|
||||
|
|
@ -111,8 +113,7 @@
|
|||
'<div class="wl-orb-meta">' + eps + (sh.done ? ' · ' + sh.done + ' done' : '') + '</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 class="vwsh-seasons">' + seasons + '</div></div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
|
|
@ -139,9 +140,7 @@
|
|||
// sel = a selected episode object (episode synopsis + guest cast), or null (show synopsis + show cast).
|
||||
function renderInfoBar(group, tmdb, sel) {
|
||||
var info = group && group.querySelector('[data-vwsh-info]'); if (!info) return;
|
||||
var sh = state.showData[tmdb] || {}, si = state.showInfo[tmdb] || {};
|
||||
var src = sh.library_id != null ? 'library' : 'tmdb';
|
||||
var openId = sh.library_id != null ? sh.library_id : tmdb;
|
||||
var si = state.showInfo[tmdb] || {};
|
||||
var eyebrow, overview, castArr;
|
||||
if (sel) {
|
||||
eyebrow = 'S' + sel.season_number + ' · E' + sel.episode_number;
|
||||
|
|
@ -152,9 +151,7 @@
|
|||
}
|
||||
var left = '<div class="vwsh-info-syn">' +
|
||||
(eyebrow ? '<span class="vwsh-info-eyebrow">' + esc(eyebrow) + '</span>' : '') +
|
||||
esc(overview) +
|
||||
'<button class="vwsh-info-viewshow" type="button" data-vwsh-open-show data-vwsh-src="' + src + '" data-vwsh-id="' + esc(openId) + '">View show →</button>' +
|
||||
'</div>';
|
||||
esc(overview) + '</div>';
|
||||
info.innerHTML = left + (castArr.length ? '<div class="vwsh-info-cast">' + castBubbles(castArr) + '</div>' : '');
|
||||
// lazily fetch the episode's guest stars, then re-render if still selected
|
||||
if (sel && sel._guests === undefined) {
|
||||
|
|
@ -209,14 +206,6 @@
|
|||
'data-tmdb="' + esc(sh.tmdb_id) + '" data-s="' + se.season_number + '" data-e="' + e.episode_number + '" title="Remove">✕</button>' +
|
||||
'</div>';
|
||||
}
|
||||
function renderEpisodeArea(group, tmdb, seasonNum) {
|
||||
var area = group && group.querySelector('[data-vwsh-ep-area]'); if (!area) return;
|
||||
var sh = state.showData[tmdb]; var se = null;
|
||||
if (sh) (sh.seasons || []).forEach(function (x) { if (x.season_number === seasonNum) se = x; });
|
||||
if (!se) { area.innerHTML = ''; return; }
|
||||
area.innerHTML = '<div class="vwsh-ep-grid">' +
|
||||
(se.episodes || []).map(function (e) { return epCard(sh, se, e); }).join('') + '</div>';
|
||||
}
|
||||
|
||||
function render(items) {
|
||||
var grid = $('[data-vwsh-grid]'); if (!grid) return;
|
||||
|
|
@ -371,18 +360,6 @@
|
|||
renderInfoBar(eg, etmdb, findEpisode(etmdb, parseInt(epc.getAttribute('data-s'), 10), parseInt(epc.getAttribute('data-e'), 10)));
|
||||
return;
|
||||
}
|
||||
var tile = e.target.closest('[data-vwsh-tile]');
|
||||
if (tile) { // season → render its episodes full-width below (single-select)
|
||||
var group = tile.closest('.wl-orb-group');
|
||||
var tmdb = parseInt(tile.getAttribute('data-tmdb'), 10);
|
||||
var wasSel = tile.classList.contains('vwsh-tile--sel');
|
||||
if (group) { var all = group.querySelectorAll('.wl-album-tile'); for (var i = 0; i < all.length; i++) all[i].classList.remove('vwsh-tile--sel'); }
|
||||
renderInfoBar(group, tmdb, null); // changing/closing a season resets the info bar to show-level
|
||||
if (wasSel) { var a = group && group.querySelector('[data-vwsh-ep-area]'); if (a) a.innerHTML = ''; return; }
|
||||
tile.classList.add('vwsh-tile--sel');
|
||||
renderEpisodeArea(group, tmdb, parseInt(tile.getAttribute('data-vwsh-season'), 10));
|
||||
return;
|
||||
}
|
||||
var orb = e.target.closest('[data-vwsh-orb]');
|
||||
if (orb) { // show → reveal seasons + lazily load the synopsis/cast info bar
|
||||
var g = orb.closest('.wl-orb-group');
|
||||
|
|
|
|||
Loading…
Reference in a new issue