diff --git a/webui/static/video/video-get-modal.js b/webui/static/video/video-get-modal.js
index 50b4ea13..738bc96b 100644
--- a/webui/static/video/video-get-modal.js
+++ b/webui/static/video/video-get-modal.js
@@ -156,27 +156,30 @@
});
}, true);
- // Pick the right overlay control for any card: person → eye; movie → get;
- // show → eye when airing (or status unknown = still followable), get when
- // confirmed ended. One helper so every surface stays consistent.
+ // The control group for any card, so every surface stays consistent:
+ // person → eye (watchlist)
+ // movie → get (download/wishlist)
+ // airing show → eye + get (follow new episodes AND grab the back catalog)
+ // ended show → get
+ // Returns a positioned wrapper holding 0–2 buttons.
function cardButton(o) {
if (!o || !o.kind) return '';
+ var parts = [];
if (o.kind === 'person') {
- return window.VideoWatchlist
- ? VideoWatchlist.btn({ kind: 'person', tmdbId: o.tmdbId, title: o.title, poster: o.poster }) : '';
- }
- if (o.kind === 'show') {
+ if (window.VideoWatchlist) parts.push(VideoWatchlist.btn({ kind: 'person', tmdbId: o.tmdbId, title: o.title, poster: o.poster }));
+ } else if (o.kind === 'show') {
var airing = !o.status ? true : isAiring(o.status);
if (airing && o.tmdbId && window.VideoWatchlist) {
- return VideoWatchlist.btn({ kind: 'show', tmdbId: o.tmdbId, title: o.title,
- poster: o.poster, libraryId: o.libraryId });
+ parts.push(VideoWatchlist.btn({ kind: 'show', tmdbId: o.tmdbId, title: o.title,
+ poster: o.poster, libraryId: o.libraryId }));
}
- return btn({ kind: 'show', source: o.source || 'tmdb', openId: o.libraryId || o.tmdbId, title: o.title });
+ // every show can be acquired (missing/back-catalog episodes)
+ parts.push(btn({ kind: 'show', source: o.source || 'tmdb', openId: o.libraryId || o.tmdbId, title: o.title }));
+ } else if (o.kind === 'movie') {
+ parts.push(btn({ kind: 'movie', source: o.source || 'tmdb', openId: o.libraryId || o.tmdbId, title: o.title }));
}
- if (o.kind === 'movie') {
- return btn({ kind: 'movie', source: o.source || 'tmdb', openId: o.libraryId || o.tmdbId, title: o.title });
- }
- return '';
+ parts = parts.filter(Boolean);
+ return parts.length ? '' + parts.join('') + '' : '';
}
window.VideoGet = { btn: btn, isAiring: isAiring, open: openModal, cardButton: cardButton };
diff --git a/webui/static/video/video-library.js b/webui/static/video/video-library.js
index 196ff4a2..01729a07 100644
--- a/webui/static/video/video-library.js
+++ b/webui/static/video/video-library.js
@@ -65,23 +65,13 @@
fallback + '\'">'
: '
';
- // Contextual overlay control, injected inside the positioned poster box:
- // airing show -> watchlist eye (monitor for new episodes)
- // movie / ended show -> "get" download symbol (opens the detail modal)
- var ctrl = '';
- if (kind === 'movie') {
- ctrl = window.VideoGet ? VideoGet.btn({ kind: 'movie', source: 'library', openId: it.id, title: it.title }) : '';
- } else if (kind === 'show') {
- var airing = !window.VideoGet || VideoGet.isAiring(it.status);
- if (airing && it.tmdb_id && window.VideoWatchlist) {
- ctrl = VideoWatchlist.btn({
- kind: 'show', tmdbId: it.tmdb_id, title: it.title,
- poster: it.has_poster ? ('/api/video/poster/show/' + it.id) : '', libraryId: it.id
- });
- } else if (window.VideoGet) {
- ctrl = VideoGet.btn({ kind: 'show', source: 'library', openId: it.id, title: it.title });
- }
- }
+ // Overlay control group (eye/get), injected inside the positioned poster
+ // box: airing show -> eye + get; ended show / movie -> get; (people elsewhere).
+ var ctrl = window.VideoGet ? VideoGet.cardButton({
+ kind: kind, tmdbId: it.tmdb_id, libraryId: it.id, title: it.title,
+ poster: it.has_poster ? ('/api/video/poster/' + kind + '/' + it.id) : '',
+ status: it.status, source: 'library'
+ }) : '';
if (ctrl) img = img.replace(/<\/div>$/, ctrl + '');
var badge = '';
diff --git a/webui/static/video/video-side.css b/webui/static/video/video-side.css
index 92802ad9..21e46dc1 100644
--- a/webui/static/video/video-side.css
+++ b/webui/static/video/video-side.css
@@ -1973,3 +1973,8 @@ body[data-side="video"] #soulsync-toggle { display: none; }
additive (no flow change); buttons are absolute top-right within them. */
.vsr-card, .vd-sim-card, .vd-cast-card { position: relative; }
.vd-cast-card:hover .vget-btn { opacity: 1; transform: none; }
+
+/* Card control GROUP (eye + get can co-exist on an airing show). The wrapper is
+ positioned top-right; the buttons inside flow statically in a little row. */
+.vcard-ctrls { position: absolute; top: 8px; right: 8px; z-index: 4; display: flex; gap: 6px; }
+.vcard-ctrls .vwl-btn, .vcard-ctrls .vget-btn { position: static; top: auto; right: auto; }
diff --git a/webui/static/video/video-watchlist.js b/webui/static/video/video-watchlist.js
index 4994078e..df2ace66 100644
--- a/webui/static/video/video-watchlist.js
+++ b/webui/static/video/video-watchlist.js
@@ -46,8 +46,11 @@
? '
'
: '' + ph + '
';
- var btn = wlBtn({ kind: kind, tmdbId: it.tmdb_id, title: it.title,
- poster: it.poster_url, libraryId: it.library_id });
+ var btn = window.VideoGet
+ ? VideoGet.cardButton({ kind: kind, tmdbId: it.tmdb_id, libraryId: it.library_id,
+ title: it.title, poster: it.poster_url, status: it.status,
+ source: it.library_id ? 'library' : 'tmdb' })
+ : wlBtn({ kind: kind, tmdbId: it.tmdb_id, title: it.title, poster: it.poster_url, libraryId: it.library_id });
var pill = kind === 'show' ? statusPill(it.status) : '';
var meta = (kind === 'show' && it.episode_count)
? '' + (it.owned_count || 0) + '/' + it.episode_count + ' eps' : '';