@ -1329,118 +1329,71 @@ async function downloadMissingYourAlbums() {
}
async function loadDiscoverReleaseRadar ( ) {
try {
const playlistContainer = document . getElementById ( 'release-radar-playlist' ) ;
if ( ! playlistContainer ) return ;
playlistContainer . innerHTML = '<div class="discover-loading"><div class="loading-spinner"></div><p>Loading release radar...</p></div>' ;
const response = await fetch ( '/api/discover/release-radar' ) ;
if ( ! response . ok ) {
throw new Error ( 'Failed to fetch release radar' ) ;
}
const data = await response . json ( ) ;
if ( ! data . success || ! data . tracks || data . tracks . length === 0 ) {
playlistContainer . innerHTML = '<div class="discover-empty"><p>No new releases available</p></div>' ;
return ;
}
// Store tracks for download/sync functionality
discoverReleaseRadarTracks = data . tracks ;
function _renderCompactTrackRow ( track , index ) {
const coverUrl = track . album _cover _url || '/static/placeholder-album.png' ;
const durationMin = Math . floor ( track . duration _ms / 60000 ) ;
const durationSec = Math . floor ( ( track . duration _ms % 60000 ) / 1000 ) ;
const duration = ` ${ durationMin } : ${ durationSec . toString ( ) . padStart ( 2 , '0' ) } ` ;
return `
< div class = "discover-playlist-track-compact" data - track - index = "${index}" >
< div class = "track-compact-number" > $ { index + 1 } < / d i v >
< div class = "track-compact-image" >
< img src = "${coverUrl}" alt = "${track.album_name}" loading = "lazy" >
< / d i v >
< div class = "track-compact-info" >
< div class = "track-compact-name" > $ { track . track _name } < / d i v >
< div class = "track-compact-artist" > $ { track . artist _name } < / d i v >
< / d i v >
< div class = "track-compact-album" > $ { track . album _name } < / d i v >
< div class = "track-compact-duration" > $ { duration } < / d i v >
< / d i v >
` ;
}
// Build compact playlist HTML
let html = '<div class="discover-playlist-tracks-compact">' ;
data . tracks . forEach ( ( track , index ) => {
const coverUrl = track . album _cover _url || '/static/placeholder-album.png' ;
const durationMin = Math . floor ( track . duration _ms / 60000 ) ;
const durationSec = Math . floor ( ( track . duration _ms % 60000 ) / 1000 ) ;
const duration = ` ${ durationMin } : ${ durationSec . toString ( ) . padStart ( 2 , '0' ) } ` ;
let _releaseRadarCtrl = null ;
html += `
< div class = "discover-playlist-track-compact" data - track - index = "${index}" >
< div class = "track-compact-number" > $ { index + 1 } < / d i v >
< div class = "track-compact-image" >
< img src = "${coverUrl}" alt = "${track.album_name}" loading = "lazy" >
< / d i v >
< div class = "track-compact-info" >
< div class = "track-compact-name" > $ { track . track _name } < / d i v >
< div class = "track-compact-artist" > $ { track . artist _name } < / d i v >
< / d i v >
< div class = "track-compact-album" > $ { track . album _name } < / d i v >
< div class = "track-compact-duration" > $ { duration } < / d i v >
< / d i v >
` ;
async function loadDiscoverReleaseRadar ( ) {
if ( ! _releaseRadarCtrl ) {
_releaseRadarCtrl = createDiscoverSectionController ( {
id : 'release-radar' ,
contentEl : '#release-radar-playlist' ,
fetchUrl : '/api/discover/release-radar' ,
extractItems : ( data ) => data . tracks || [ ] ,
renderItems : ( items ) => {
discoverReleaseRadarTracks = items ;
const rows = items . map ( ( t , i ) => _renderCompactTrackRow ( t , i ) ) . join ( '' ) ;
return ` <div class="discover-playlist-tracks-compact"> ${ rows } </div> ` ;
} ,
loadingMessage : 'Loading release radar...' ,
emptyMessage : 'No new releases available' ,
errorMessage : 'Failed to load release radar' ,
verboseErrors : true ,
} ) ;
html += '</div>' ;
playlistContainer . innerHTML = html ;
} catch ( error ) {
console . error ( 'Error loading release radar:' , error ) ;
const playlistContainer = document . getElementById ( 'release-radar-playlist' ) ;
if ( playlistContainer ) {
playlistContainer . innerHTML = '<div class="discover-empty"><p>Failed to load release radar</p></div>' ;
}
}
return _releaseRadarCtrl . load ( ) ;
}
async function loadDiscoverWeekly ( ) {
try {
const playlistContainer = document . getElementById ( 'discovery-weekly-playlist' ) ;
if ( ! playlistContainer ) return ;
playlistContainer . innerHTML = '<div class="discover-loading"><div class="loading-spinner"></div><p>Curating your discovery playlist...</p></div>' ;
const response = await fetch ( '/api/discover/weekly' ) ;
if ( ! response . ok ) {
throw new Error ( 'Failed to fetch discovery weekly' ) ;
}
const data = await response . json ( ) ;
if ( ! data . success || ! data . tracks || data . tracks . length === 0 ) {
playlistContainer . innerHTML = '<div class="discover-empty"><p>No tracks available yet</p></div>' ;
return ;
}
// Store tracks for download/sync functionality
discoverWeeklyTracks = data . tracks ;
// Build compact playlist HTML
let html = '<div class="discover-playlist-tracks-compact">' ;
data . tracks . forEach ( ( track , index ) => {
const coverUrl = track . album _cover _url || '/static/placeholder-album.png' ;
const durationMin = Math . floor ( track . duration _ms / 60000 ) ;
const durationSec = Math . floor ( ( track . duration _ms % 60000 ) / 1000 ) ;
const duration = ` ${ durationMin } : ${ durationSec . toString ( ) . padStart ( 2 , '0' ) } ` ;
let _weeklyCtrl = null ;
html += `
< div class = "discover-playlist-track-compact" data - track - index = "${index}" >
< div class = "track-compact-number" > $ { index + 1 } < / d i v >
< div class = "track-compact-image" >
< img src = "${coverUrl}" alt = "${track.album_name}" loading = "lazy" >
< / d i v >
< div class = "track-compact-info" >
< div class = "track-compact-name" > $ { track . track _name } < / d i v >
< div class = "track-compact-artist" > $ { track . artist _name } < / d i v >
< / d i v >
< div class = "track-compact-album" > $ { track . album _name } < / d i v >
< div class = "track-compact-duration" > $ { duration } < / d i v >
< / d i v >
` ;
async function loadDiscoverWeekly ( ) {
if ( ! _weeklyCtrl ) {
_weeklyCtrl = createDiscoverSectionController ( {
id : 'discovery-weekly' ,
contentEl : '#discovery-weekly-playlist' ,
fetchUrl : '/api/discover/weekly' ,
extractItems : ( data ) => data . tracks || [ ] ,
renderItems : ( items ) => {
discoverWeeklyTracks = items ;
const rows = items . map ( ( t , i ) => _renderCompactTrackRow ( t , i ) ) . join ( '' ) ;
return ` <div class="discover-playlist-tracks-compact"> ${ rows } </div> ` ;
} ,
loadingMessage : 'Curating your discovery playlist...' ,
emptyMessage : 'No tracks available yet' ,
errorMessage : 'Failed to load discovery weekly' ,
verboseErrors : true ,
} ) ;
html += '</div>' ;
playlistContainer . innerHTML = html ;
} catch ( error ) {
console . error ( 'Error loading discovery weekly:' , error ) ;
const playlistContainer = document . getElementById ( 'discovery-weekly-playlist' ) ;
if ( playlistContainer ) {
playlistContainer . innerHTML = '<div class="discover-empty"><p>Failed to load discovery weekly</p></div>' ;
}
}
return _weeklyCtrl . load ( ) ;
}
// ===============================
@ -1450,51 +1403,40 @@ async function loadDiscoverWeekly() {
let selectedDecade = null ;
let decadeTracks = [ ] ;
async function loadDecadeBrowser ( ) {
try {
const carousel = document . getElementById ( 'decade-browser-carousel' ) ;
if ( ! carousel ) return ;
// Fetch available decades from backend
const response = await fetch ( '/api/discover/decades/available' ) ;
if ( ! response . ok ) {
throw new Error ( 'Failed to fetch available decades' ) ;
}
function _renderDecadeCard ( decade ) {
const icon = getDecadeIcon ( decade . year ) ;
const label = ` ${ decade . year } s ` ;
return `
< div class = "discover-card decade-card-modern" onclick = "openDecadePlaylist(${decade.year})" >
< div class = "discover-card-image decade-card-image" >
< div class = "decade-icon-large" > $ { icon } < / d i v >
< / d i v >
< div class = "discover-card-info" >
< h4 class = "discover-card-title" > $ { label } < / h 4 >
< p class = "discover-card-subtitle" > $ { decade . track _count } tracks < / p >
< p class = "discover-card-meta" > Classics < / p >
< / d i v >
< / d i v >
` ;
}
const data = await response . json ( ) ;
if ( ! data . success || ! data . decades || data . decades . length === 0 ) {
carousel . innerHTML = '<div class="discover-empty"><p>No decade content available yet. Run a watchlist scan to populate your discovery pool!</p></div>' ;
return ;
}
let _decadeBrowserCtrl = null ;
// Build decade cards matching Recent Releases style
let html = '' ;
data . decades . forEach ( decade => {
const icon = getDecadeIcon ( decade . year ) ;
const label = ` ${ decade . year } s ` ;
html += `
< div class = "discover-card decade-card-modern" onclick = "openDecadePlaylist(${decade.year})" >
< div class = "discover-card-image decade-card-image" >
< div class = "decade-icon-large" > $ { icon } < / d i v >
< / d i v >
< div class = "discover-card-info" >
< h4 class = "discover-card-title" > $ { label } < / h 4 >
< p class = "discover-card-subtitle" > $ { decade . track _count } tracks < / p >
< p class = "discover-card-meta" > Classics < / p >
< / d i v >
< / d i v >
` ;
async function loadDecadeBrowser ( ) {
if ( ! _decadeBrowserCtrl ) {
_decadeBrowserCtrl = createDiscoverSectionController ( {
id : 'decade-browser' ,
contentEl : '#decade-browser-carousel' ,
fetchUrl : '/api/discover/decades/available' ,
extractItems : ( data ) => data . decades || [ ] ,
renderItems : ( items ) => items . map ( d => _renderDecadeCard ( d ) ) . join ( '' ) ,
loadingMessage : 'Loading decades...' ,
emptyMessage : 'No decade content available yet. Run a watchlist scan to populate your discovery pool!' ,
errorMessage : 'Failed to load decades' ,
verboseErrors : true ,
} ) ;
carousel . innerHTML = html ;
} catch ( error ) {
console . error ( 'Error loading decade browser:' , error ) ;
const carousel = document . getElementById ( 'decade-browser-carousel' ) ;
if ( carousel ) {
carousel . innerHTML = '<div class="discover-empty"><p>Failed to load decades</p></div>' ;
}
}
return _decadeBrowserCtrl . load ( ) ;
}
function getDecadeIcon ( year ) {
@ -1552,51 +1494,40 @@ async function openDecadePlaylist(decade) {
let selectedGenre = null ;
let genreTracks = [ ] ;
async function loadGenreBrowser ( ) {
try {
const carousel = document . getElementById ( 'genre-browser-carousel' ) ;
if ( ! carousel ) return ;
// Fetch available genres from backend
const response = await fetch ( '/api/discover/genres/available' ) ;
if ( ! response . ok ) {
throw new Error ( 'Failed to fetch available genres' ) ;
}
function _renderGenreCard ( genre ) {
const icon = getGenreIcon ( genre . name ) ;
const displayName = capitalizeGenre ( genre . name ) ;
return `
< div class = "discover-card genre-card-modern" onclick = "openGenrePlaylist('${escapeForInlineJs(genre.name)}')" >
< div class = "discover-card-image genre-card-image" >
< div class = "genre-icon-large" > $ { icon } < / d i v >
< / d i v >
< div class = "discover-card-info" >
< h4 class = "discover-card-title" > $ { displayName } < / h 4 >
< p class = "discover-card-subtitle" > $ { genre . track _count } tracks < / p >
< p class = "discover-card-meta" > Curated < / p >
< / d i v >
< / d i v >
` ;
}
const data = await response . json ( ) ;
if ( ! data . success || ! data . genres || data . genres . length === 0 ) {
carousel . innerHTML = '<div class="discover-empty"><p>No genre content available yet. Run a watchlist scan to populate your discovery pool!</p></div>' ;
return ;
}
let _genreBrowserCtrl = null ;
// Build genre cards matching Recent Releases style
let html = '' ;
data . genres . forEach ( genre => {
const icon = getGenreIcon ( genre . name ) ;
const displayName = capitalizeGenre ( genre . name ) ;
html += `
< div class = "discover-card genre-card-modern" onclick = "openGenrePlaylist('${escapeForInlineJs(genre.name)}')" >
< div class = "discover-card-image genre-card-image" >
< div class = "genre-icon-large" > $ { icon } < / d i v >
< / d i v >
< div class = "discover-card-info" >
< h4 class = "discover-card-title" > $ { displayName } < / h 4 >
< p class = "discover-card-subtitle" > $ { genre . track _count } tracks < / p >
< p class = "discover-card-meta" > Curated < / p >
< / d i v >
< / d i v >
` ;
async function loadGenreBrowser ( ) {
if ( ! _genreBrowserCtrl ) {
_genreBrowserCtrl = createDiscoverSectionController ( {
id : 'genre-browser' ,
contentEl : '#genre-browser-carousel' ,
fetchUrl : '/api/discover/genres/available' ,
extractItems : ( data ) => data . genres || [ ] ,
renderItems : ( items ) => items . map ( g => _renderGenreCard ( g ) ) . join ( '' ) ,
loadingMessage : 'Loading genres...' ,
emptyMessage : 'No genre content available yet. Run a watchlist scan to populate your discovery pool!' ,
errorMessage : 'Failed to load genres' ,
verboseErrors : true ,
} ) ;
carousel . innerHTML = html ;
} catch ( error ) {
console . error ( 'Error loading genre browser:' , error ) ;
const carousel = document . getElementById ( 'genre-browser-carousel' ) ;
if ( carousel ) {
carousel . innerHTML = '<div class="discover-empty"><p>Failed to load genres</p></div>' ;
}
}
return _genreBrowserCtrl . load ( ) ;
}
function getGenreIcon ( genreName ) {
@ -3657,80 +3588,51 @@ async function loadSeasonalAlbums(seasonData) {
}
}
async function loadSeasonalPlaylist ( seasonData ) {
try {
const playlistContainer = document . getElementById ( 'seasonal-playlist' ) ;
if ( ! playlistContainer ) return ;
// Show seasonal playlist section
const seasonalPlaylistSection = document . getElementById ( 'seasonal-playlist-section' ) ;
if ( seasonalPlaylistSection ) {
seasonalPlaylistSection . style . display = 'block' ;
}
// Update header
const playlistTitle = document . getElementById ( 'seasonal-playlist-title' ) ;
const playlistSubtitle = document . getElementById ( 'seasonal-playlist-subtitle' ) ;
if ( playlistTitle ) {
playlistTitle . textContent = ` ${ seasonData . icon } ${ seasonData . name } Mix ` ;
}
if ( playlistSubtitle ) {
playlistSubtitle . textContent = ` Curated playlist for ${ seasonData . name . toLowerCase ( ) } ` ;
}
playlistContainer . innerHTML = '<div class="discover-loading"><div class="loading-spinner"></div><p>Loading playlist...</p></div>' ;
// Fetch playlist tracks
const response = await fetch ( ` /api/discover/seasonal/ ${ currentSeasonKey } /playlist ` ) ;
if ( ! response . ok ) {
throw new Error ( 'Failed to fetch seasonal playlist' ) ;
}
let _seasonalPlaylistCtrl = null ;
let _seasonalPlaylistCtrlKey = null ;
const data = await response . json ( ) ;
async function loadSeasonalPlaylist ( seasonData ) {
const playlistContainer = document . getElementById ( 'seasonal-playlist' ) ;
if ( ! playlistContainer ) return ;
if ( ! data . success || ! data . tracks || data . tracks . length === 0 ) {
playlistContainer . innerHTML = '<div class="discover-empty"><p>No tracks available yet</p></div>' ;
return ;
}
// Show seasonal playlist section
const seasonalPlaylistSection = document . getElementById ( 'seasonal-playlist-section' ) ;
if ( seasonalPlaylistSection ) {
seasonalPlaylistSection . style . display = 'block' ;
}
// Store tracks for download/sync functionality
discoverSeasonalTracks = data . tracks ;
// Update header
const playlistTitle = document . getElementById ( 'seasonal-playlist-title' ) ;
const playlistSubtitle = document . getElementById ( 'seasonal-playlist-subtitle' ) ;
// Build compact playlist HTML
let html = '<div class="discover-playlist-tracks-compact">' ;
data . tracks . forEach ( ( track , index ) => {
const coverUrl = track . album _cover _url || '/static/placeholder-album.png' ;
const durationMin = Math . floor ( track . duration _ms / 60000 ) ;
const durationSec = Math . floor ( ( track . duration _ms % 60000 ) / 1000 ) ;
const duration = ` ${ durationMin } : ${ durationSec . toString ( ) . padStart ( 2 , '0' ) } ` ;
if ( playlistTitle ) {
playlistTitle . textContent = ` ${ seasonData . icon } ${ seasonData . name } Mix ` ;
}
if ( playlistSubtitle ) {
playlistSubtitle . textContent = ` Curated playlist for ${ seasonData . name . toLowerCase ( ) } ` ;
}
html += `
< div class = "discover-playlist-track-compact" data - track - index = "${index}" >
< div class = "track-compact-number" > $ { index + 1 } < / d i v >
< div class = "track-compact-image" >
< img src = "${coverUrl}" alt = "${track.album_name}" loading = "lazy" >
< / d i v >
< div class = "track-compact-info" >
< div class = "track-compact-name" > $ { track . track _name } < / d i v >
< div class = "track-compact-artist" > $ { track . artist _name } < / d i v >
< / d i v >
< div class = "track-compact-album" > $ { track . album _name } < / d i v >
< div class = "track-compact-duration" > $ { duration } < / d i v >
< / d i v >
` ;
// Re-create the controller when the season key changes so the
// fetchUrl always points at the active season's endpoint.
if ( ! _seasonalPlaylistCtrl || _seasonalPlaylistCtrlKey !== currentSeasonKey ) {
_seasonalPlaylistCtrl = createDiscoverSectionController ( {
id : 'seasonal-playlist' ,
contentEl : '#seasonal-playlist' ,
fetchUrl : ` /api/discover/seasonal/ ${ currentSeasonKey } /playlist ` ,
extractItems : ( data ) => data . tracks || [ ] ,
renderItems : ( items ) => {
discoverSeasonalTracks = items ;
const rows = items . map ( ( t , i ) => _renderCompactTrackRow ( t , i ) ) . join ( '' ) ;
return ` <div class="discover-playlist-tracks-compact"> ${ rows } </div> ` ;
} ,
loadingMessage : 'Loading playlist...' ,
emptyMessage : 'No tracks available yet' ,
errorMessage : 'Failed to load playlist' ,
verboseErrors : true ,
} ) ;
html += '</div>' ;
playlistContainer . innerHTML = html ;
} catch ( error ) {
console . error ( 'Error loading seasonal playlist:' , error ) ;
const playlistContainer = document . getElementById ( 'seasonal-playlist' ) ;
if ( playlistContainer ) {
playlistContainer . innerHTML = '<div class="discover-empty"><p>Failed to load playlist</p></div>' ;
}
_seasonalPlaylistCtrlKey = currentSeasonKey ;
}
return _seasonalPlaylistCtrl . load ( ) ;
}
function hideSeasonalSections ( ) {
@ -4225,59 +4127,63 @@ async function unblockDiscoveryArtist(id, name) {
// Backwards compat — called during page init but now a no-op (modal handles it)
// ── Your Artists (Liked Artists Pool) ──
async function loadYourArtists ( ) {
const section = document . getElementById ( 'your-artists-section' ) ;
const carousel = document . getElementById ( 'your-artists-carousel' ) ;
const subtitle = document . getElementById ( 'your-artists-subtitle' ) ;
if ( ! section || ! carousel ) return ;
try {
const resp = await fetch ( '/api/discover/your-artists' ) ;
if ( ! resp . ok ) return ;
const data = await resp . json ( ) ;
if ( ! data . artists || data . artists . length === 0 ) {
if ( data . stale ) {
// First load — show section with loading state, poll until ready
section . style . display = '' ;
if ( subtitle ) subtitle . textContent = 'Discovering your artists across connected services...' ;
carousel . innerHTML = `
< div class = "ya-loading" >
< div class = "watch-all-loading-spinner" > < / d i v >
< span > Fetching and matching artists from your services ... < / s p a n >
< / d i v >
` ;
_pollYourArtists ( ) ;
} else {
section . style . display = 'none' ;
}
return ;
}
// Show section
section . style . display = '' ;
// Update subtitle with source info
const sources = new Set ( ) ;
data . artists . forEach ( a => ( a . source _services || [ ] ) . forEach ( s => sources . add ( s ) ) ) ;
const sourceNames = { spotify : 'Spotify' , lastfm : 'Last.fm' , tidal : 'Tidal' , deezer : 'Deezer' } ;
const sourceList = [ ... sources ] . map ( s => sourceNames [ s ] || s ) . join ( ' and ' ) ;
if ( subtitle ) subtitle . textContent = ` Artists you follow on ${ sourceList || 'your music services' } ` ;
let _yourArtistsCtrl = null ;
if ( data . stale ) {
if ( subtitle ) subtitle . textContent += ' (updating...)' ;
_pollYourArtists ( ) ;
}
async function loadYourArtists ( ) {
if ( ! _yourArtistsCtrl ) {
_yourArtistsCtrl = createDiscoverSectionController ( {
id : 'your-artists' ,
sectionEl : '#your-artists-section' ,
contentEl : '#your-artists-carousel' ,
fetchUrl : '/api/discover/your-artists' ,
extractItems : ( data ) => data . artists || [ ] ,
// Only treat as "truly empty" when there's no data AND the
// upstream isn't still discovering. When stale + empty, the
// renderer shows a custom in-progress message and a poller
// is started in onRendered.
isEmpty : ( items , data ) => items . length === 0 && ! data . stale ,
hideWhenEmpty : true ,
renderItems : ( items , data ) => {
const subtitle = document . getElementById ( 'your-artists-subtitle' ) ;
// Stale + empty — show custom "still fetching" message
if ( items . length === 0 && data . stale ) {
if ( subtitle ) subtitle . textContent = 'Discovering your artists across connected services...' ;
return `
< div class = "ya-loading" >
< div class = "watch-all-loading-spinner" > < / d i v >
< span > Fetching and matching artists from your services ... < / s p a n >
< / d i v >
` ;
}
// Store for modal access and render carousel cards
window . _yaArtists = { } ;
window . _yaActiveSource = data . active _source || 'spotify' ;
data . artists . forEach ( a => { window . _yaArtists [ a . id ] = a ; } ) ;
carousel . innerHTML = data . artists . map ( a => _renderYourArtistCard ( a ) ) . join ( '' ) ;
// Update subtitle with source info
const sources = new Set ( ) ;
items . forEach ( a => ( a . source _services || [ ] ) . forEach ( s => sources . add ( s ) ) ) ;
const sourceNames = { spotify : 'Spotify' , lastfm : 'Last.fm' , tidal : 'Tidal' , deezer : 'Deezer' } ;
const sourceList = [ ... sources ] . map ( s => sourceNames [ s ] || s ) . join ( ' and ' ) ;
if ( subtitle ) {
subtitle . textContent = ` Artists you follow on ${ sourceList || 'your music services' } ` ;
if ( data . stale ) subtitle . textContent += ' (updating...)' ;
}
} catch ( err ) {
console . error ( 'Error loading Your Artists:' , err ) ;
// Store for modal access and render carousel cards
window . _yaArtists = { } ;
window . _yaActiveSource = data . active _source || 'spotify' ;
items . forEach ( a => { window . _yaArtists [ a . id ] = a ; } ) ;
return items . map ( a => _renderYourArtistCard ( a ) ) . join ( '' ) ;
} ,
onRendered : ( { data } ) => {
// Continue polling while upstream is still discovering.
if ( data . stale ) _pollYourArtists ( ) ;
} ,
loadingMessage : 'Loading your artists...' ,
emptyMessage : 'No followed artists found' ,
errorMessage : 'Failed to load your artists' ,
verboseErrors : true ,
} ) ;
}
return _yourArtistsCtrl . load ( ) ;
}
function _pollYourArtists ( ) {
@ -6685,60 +6591,78 @@ async function loadFamiliarFavorites() {
// BECAUSE YOU LISTEN TO
// ===============================
async function loadBecauseYouListenTo ( ) {
try {
const resp = await fetch ( '/api/discover/because-you-listen-to' ) ;
if ( ! resp . ok ) return ;
const data = await resp . json ( ) ;
if ( ! data . success || ! data . sections || data . sections . length === 0 ) return ;
// Find or create the BYLT container
let byltContainer = document . getElementById ( 'discover-bylt-sections' ) ;
if ( ! byltContainer ) {
// Insert after the release radar section
const releaseRadar = document . getElementById ( 'discover-release-radar' ) ;
if ( ! releaseRadar ) return ;
const parent = releaseRadar . closest ( '.discover-section' ) ;
if ( ! parent ) return ;
byltContainer = document . createElement ( 'div' ) ;
byltContainer . id = 'discover-bylt-sections' ;
parent . parentNode . insertBefore ( byltContainer , parent . nextSibling ) ;
}
byltContainer . innerHTML = data . sections . map ( ( section , idx ) => `
< div class = "discover-section bylt-section" >
< div class = "discover-section-header" >
< div class = "bylt-header" >
$ { section . artist _image ? ` <img class="bylt-artist-img" src=" ${ section . artist _image } " alt="" onerror="this.style.display='none'"> ` : '' }
< div >
< div class = "discover-section-subtitle" > Because you listen to < / d i v >
< h3 class = "discover-section-title" > $ { _esc ( section . artist _name ) } < / h 3 >
< / d i v >
function _renderByltSection ( section , idx ) {
return `
< div class = "discover-section bylt-section" >
< div class = "discover-section-header" >
< div class = "bylt-header" >
$ { section . artist _image ? ` <img class="bylt-artist-img" src=" ${ section . artist _image } " alt="" onerror="this.style.display='none'"> ` : '' }
< div >
< div class = "discover-section-subtitle" > Because you listen to < / d i v >
< h3 class = "discover-section-title" > $ { _esc ( section . artist _name ) } < / h 3 >
< / d i v >
< / d i v >
< div class = "discover-carousel" id = "bylt-carousel-${idx}" > < / d i v >
< / d i v >
` ).join('');
< div class = "discover-carousel" id = "bylt-carousel-${idx}" > < / d i v >
< / d i v >
` ;
}
// Render track cards in each carousel
data . sections . forEach ( ( section , idx ) => {
const carousel = document . getElementById ( ` bylt-carousel- ${ idx } ` ) ;
if ( ! carousel ) return ;
carousel . innerHTML = section . tracks . map ( t => `
< div class = "discover-card" >
< div class = "discover-card-image" >
$ { t . image _url ? ` <img src=" ${ t . image _url } " alt="" loading="lazy" onerror="this.src='/static/placeholder.png'"> ` : '<div class="discover-card-placeholder">🎵</div>' }
< / d i v >
< div class = "discover-card-title" > $ { _esc ( t . name ) } < / d i v >
< div class = "discover-card-artist" > $ { _esc ( t . artist ) } < / d i v >
< / d i v >
` ).join('');
} ) ;
function _renderByltTrackCard ( t ) {
return `
< div class = "discover-card" >
< div class = "discover-card-image" >
$ { t . image _url ? ` <img src=" ${ t . image _url } " alt="" loading="lazy" onerror="this.src='/static/placeholder.png'"> ` : '<div class="discover-card-placeholder">🎵</div>' }
< / d i v >
< div class = "discover-card-title" > $ { _esc ( t . name ) } < / d i v >
< div class = "discover-card-artist" > $ { _esc ( t . artist ) } < / d i v >
< / d i v >
` ;
}
} catch ( error ) {
console . debug ( 'Error loading Because You Listen To:' , error ) ;
let _byltCtrl = null ;
async function loadBecauseYouListenTo ( ) {
// Ensure the BYLT container exists in the DOM. It's dynamically
// inserted after the release radar section because the markup
// doesn't ship a placeholder for it. Bail if anchor section
// isn't present.
let byltContainer = document . getElementById ( 'discover-bylt-sections' ) ;
if ( ! byltContainer ) {
const releaseRadar = document . getElementById ( 'discover-release-radar' ) ;
if ( ! releaseRadar ) return ;
const parent = releaseRadar . closest ( '.discover-section' ) ;
if ( ! parent ) return ;
byltContainer = document . createElement ( 'div' ) ;
byltContainer . id = 'discover-bylt-sections' ;
parent . parentNode . insertBefore ( byltContainer , parent . nextSibling ) ;
}
if ( ! _byltCtrl ) {
_byltCtrl = createDiscoverSectionController ( {
id : 'because-you-listen-to' ,
contentEl : '#discover-bylt-sections' ,
fetchUrl : '/api/discover/because-you-listen-to' ,
extractItems : ( data ) => data . sections || [ ] ,
// No per-section empty/loading copy — when there's nothing
// to show we leave the container blank rather than render a
// placeholder, matching the original no-op behavior.
renderEmptyState : false ,
loadingMessage : '' ,
renderItems : ( items ) => items . map ( ( s , i ) => _renderByltSection ( s , i ) ) . join ( '' ) ,
onRendered : ( { items } ) => {
// Inject track cards into each section's carousel after
// the section wrappers exist in the DOM.
items . forEach ( ( section , idx ) => {
const carousel = document . getElementById ( ` bylt-carousel- ${ idx } ` ) ;
if ( ! carousel ) return ;
carousel . innerHTML = section . tracks . map ( t => _renderByltTrackCard ( t ) ) . join ( '' ) ;
} ) ;
} ,
} ) ;
}
return _byltCtrl . load ( ) ;
}
// ===============================