diff --git a/scripts/cardBuilder.css b/scripts/cardBuilder.css index 3cc898c..72e36e7 100644 --- a/scripts/cardBuilder.css +++ b/scripts/cardBuilder.css @@ -779,10 +779,6 @@ .spotlight-play-button { width: 48px; height: 48px; - border-radius: 50%; - background: rgba(255, 255, 255, 0.9); - color: #000; - border: none; cursor: pointer; display: flex; align-items: center; @@ -803,9 +799,6 @@ width: 48px; height: 48px; border-radius: 50%; - background: rgba(255, 255, 255, 0.2); - color: white; - border: 2px solid rgba(255, 255, 255, 0.5); cursor: pointer; display: flex; align-items: center; @@ -814,25 +807,12 @@ transition: all 0.2s; } -.spotlight-watchlist-button:hover { - background: rgba(255, 255, 255, 0.3); -} - -.spotlight-watchlist-button.watchlisted { - border-color: #4caf50; -} - -.spotlight-watchlist-button.watchlisted .material-icons { +.spotlight-watchlist-button[data-active="true"] .material-icons { color: #4caf50; } -.spotlight-watchlist-button:not(.watchlisted):hover { - border-color: rgba(255, 255, 255, 0.7); -} - .spotlight-watchlist-button .material-icons { font-size: 28px; - color: white; } .spotlight-info-button { @@ -842,15 +822,16 @@ justify-content: center; transition: all 0.2s; position: relative; + overflow: visible; } .spotlight-overview-tooltip { - position: absolute; + position: absolute !important; text-align: left; bottom: 100%; left: 50%; - transform: translateX(-50%); - margin-bottom: 12px; + transform: translateX(-50%) !important; + margin-bottom: 12px !important; padding: 16px; background: rgba(0, 0, 0, 0.95); color: white; @@ -864,17 +845,19 @@ pointer-events: none; transition: opacity 0.2s ease, visibility 0s linear 0.2s; transition-delay: 0s; - z-index: 1000; + z-index: 1000 !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); word-wrap: break-word; + max-height: unset !important; } /* Show only while hovering the info icon glyph — not the button/tooltip footprint */ -.spotlight-info-button:has(.material-icons:hover) .spotlight-overview-tooltip { +.spotlight-info-button:is(.emby-button):hover .spotlight-overview-tooltip { opacity: 1; visibility: visible; pointer-events: none; transition-delay: 0.5s, 0.5s; + display: block; } /* Hover overview only on Normal desktop spotlights */ @@ -1703,7 +1686,7 @@ transform: translateX(-50%) !important; width: fit-content !important; max-width: min(85%, 700px); - text-align: center !important; + text-align: left !important; font-style: italic; font-size: 1.05em; margin: 0 !important; @@ -1725,6 +1708,8 @@ -webkit-line-clamp: 2; -webkit-box-orient: vertical; cursor: default; + height: unset; + line-height: 1.5; } /* Normal and large: tagline above dots (dots at bottom: 20px) */ @@ -1885,6 +1870,9 @@ margin: 0px !important; padding: 0px !important; } +.spotlight-section .mainDetailButtons > :is(button.emby-button) { + padding: 0.5em !important; +} .spotlight-section .mainDetailButtons > .btnPlay:is(.emby-button) { padding: 0.25em !important; } @@ -1934,7 +1922,7 @@ } .spotlight-metadata-row .emby-tab-button { - color: #ececec; + color: #c0c0c0; } :where(.spotlight-section-title.headerTabs.sectionTabs), diff --git a/scripts/cardBuilder.js b/scripts/cardBuilder.js index be8d201..e6ecf71 100644 --- a/scripts/cardBuilder.js +++ b/scripts/cardBuilder.js @@ -16,6 +16,7 @@ const state = { defaultCardBackgroundNumber: 1, useBlurhash: true, + watchlist: false, }; /** In-memory scroller metrics (avoid CSS var style writes for JS-only caches). */ @@ -2043,6 +2044,7 @@ } function updateCardWatchlistButtons(itemId, userData) { + if (!state.watchlist) return; if (!itemId || !userData || !('Likes' in userData)) return; const isLiked = userData.Likes === true; @@ -3867,7 +3869,7 @@ // Watchlist button (active state from UserData.Likes; click bound by watchlist.js) const watchlistSupportedTypes = ['Movie', 'Series', 'Season', 'Episode', 'BoxSet', 'Playlist', 'Video']; - if (watchlistSupportedTypes.includes(itemType)) { + if (state.watchlist && watchlistSupportedTypes.includes(itemType)) { const isLiked = item.UserData?.Likes === true; const watchlistButton = document.createElement('button'); watchlistButton.type = 'button'; @@ -3934,7 +3936,7 @@ buttonContainer.appendChild(moreButton); - const NON_PLAYABLE_TYPES = ['CollectionFolder', 'Folder', 'UserView', 'Virtual', 'Studio']; + const NON_PLAYABLE_TYPES = ['CollectionFolder', 'Folder', 'UserView', 'Virtual', 'Studio', 'Genre', 'Person']; if (!NON_PLAYABLE_TYPES.includes(item.Type) && item.LocationType !== 'Virtual') { cardOverlayContainer.appendChild(playButton); } @@ -4110,7 +4112,7 @@ if (itemType === 'Series' && item.Status === 'Continuing') { secondaryTextContent = item.ProductionYear + ' - Present'; - } else if (itemType === 'Series' && item.EndDate) { + } else if (itemType === 'Series' && item.EndDate && item.ProductionYear && item.EndDate.substring(0, 4) !== String(item.ProductionYear)) { secondaryTextContent = item.ProductionYear + ' - ' + item.EndDate.substring(0, 4); } else if (item.Type === 'Program') { secondaryTextContent = new Date(item.StartDate).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: true }); @@ -4894,67 +4896,32 @@ } }); - // Watchlist button (material icon span button with bookmark icon) - const watchlistButton = document.createElement('button'); - watchlistButton.className = 'emby-button button-flat btnWatchlist'; + // Watchlist button (same Likes / watchlist-button pattern as cards; clicks via watchlist.js) + const spotlightWatchlistSupportedTypes = ['Movie', 'Series', 'Season', 'Episode', 'BoxSet', 'Playlist', 'Video']; + let watchlistButton = null; + if (state.watchlist && spotlightWatchlistSupportedTypes.includes(itemType)) { + const isLiked = item.UserData?.Likes === true; + watchlistButton = document.createElement('button'); + watchlistButton.type = 'button'; + watchlistButton.className = 'watchlist-button spotlight-watchlist-button emby-button button-flat paper-icon-button-light'; + watchlistButton.setAttribute('data-action', 'none'); + watchlistButton.setAttribute('data-id', item.Id); + watchlistButton.setAttribute('data-active', isLiked ? 'true' : 'false'); + watchlistButton.title = isLiked ? 'Remove from Watchlist' : 'Add to Watchlist'; - // Check watchlist status - try to get from cache or API - let isInWatchlist = false; - const sectionName = - itemType === 'Movie' - ? 'movies' - : itemType === 'Series' - ? 'series' - : itemType === 'Season' - ? 'seasons' - : itemType === 'Episode' - ? 'episodes' - : null; + const watchlistIcon = document.createElement('span'); + watchlistIcon.className = 'material-icons watchlist'; + watchlistIcon.setAttribute('aria-hidden', 'true'); + watchlistButton.appendChild(watchlistIcon); - // Check watchlist cache if available - if (sectionName && typeof window.watchlistCache !== 'undefined' && window.watchlistCache[sectionName]?.data) { - isInWatchlist = window.watchlistCache[sectionName].data.some((watchlistItem) => watchlistItem.Id === item.Id); - } - - const watchlistIcon = document.createElement('span'); - watchlistIcon.className = 'material-icons'; - watchlistIcon.textContent = 'bookmark'; - watchlistIcon.title = isInWatchlist ? 'Remove from Watchlist' : 'Add to Watchlist'; - watchlistButton.appendChild(watchlistIcon); - if (isInWatchlist) { - watchlistButton.classList.add('watchlisted'); - } - watchlistButton.addEventListener('click', async (e) => { - e.stopPropagation(); - const userId = ApiClient.getCurrentUserId(); - const newStatus = !isInWatchlist; - - try { - // Update watchlist status using proper API - await ApiClient.updateUserItemRating(userId, item.Id, newStatus ? 'true' : 'false'); - - // Update local state - isInWatchlist = newStatus; - - // Update button class - if (isInWatchlist) { - watchlistButton.classList.add('watchlisted'); - } else { - watchlistButton.classList.remove('watchlisted'); - } - - // Update watchlist cache if available - if (sectionName && typeof window.updateWatchlistCacheOnToggle === 'function') { - await window.updateWatchlistCacheOnToggle(item.Id, itemType, isInWatchlist); - } - } catch (err) { - console.error('Failed to update watchlist status:', err); + if (typeof window.KefinWatchlistLikedIds?.bindWatchlistButton === 'function') { + window.KefinWatchlistLikedIds.bindWatchlistButton(watchlistButton, item.Id, itemType); } - }); + } // Info button (shows overview on hover) const infoButton = document.createElement('button'); - infoButton.className = 'emby-button button-flat spotlight-info-button'; + infoButton.className = 'emby-button button-flat spotlight-info-button paper-icon-button-light'; const infoIcon = document.createElement('span'); infoIcon.className = 'material-icons'; infoIcon.textContent = 'info'; @@ -4970,7 +4937,9 @@ } buttonsContainer.appendChild(playButton); - buttonsContainer.appendChild(watchlistButton); + if (watchlistButton) { + buttonsContainer.appendChild(watchlistButton); + } buttonsContainer.appendChild(infoButton); // Rating with star icon @@ -9173,6 +9142,10 @@ } async function initialize() { + // Match loader default: watchlist enabled unless scripts.watchlist === false + const scripts = window.KefinTweaksConfig?.scripts || {}; + state.watchlist = scripts.watchlist !== false; + if (window.userHelper?.waitForLogin) { await window.userHelper.waitForLogin(); }