diff --git a/src/views/detail.js b/src/views/detail.js index f7ff028..b773968 100644 --- a/src/views/detail.js +++ b/src/views/detail.js @@ -113,46 +113,49 @@ export async function loadArtistDetail(artist) { ]) : [{ status: 'rejected' }, { status: 'rejected' }]; - // ── Hero ── (await color before showing anything so it all renders at once) + // ── Hero ── non-blocking: set bg/pfp immediately, fade in on image load, tint after const img = imgRes.status === 'fulfilled' ? imgRes.value : null; const _firstAlbums = albumRes.status === 'fulfilled' ? (albumRes.value?.artist?.album || []) : []; const _bgCover = _firstAlbums[0] ? coverUrl(_firstAlbums[0].coverArt, 512) : null; const bgSrc = img || _bgCover; - // _extractColor loads img into canvas — warms the browser cache for the display uses below - const [rgb] = await Promise.all([ - img ? _extractColor(img) : Promise.resolve(null), - bgSrc && bgSrc !== img ? new Promise(r => { const i = new Image(); i.onload = i.onerror = r; i.src = bgSrc; }) : Promise.resolve(), - ]); - if(bgSrc) { hero.style.background = ''; const bgEl = document.getElementById('artist-hero-bg'); if(bgEl) bgEl.style.backgroundImage = `url('${bgSrc}')`; } + + const _revealHero = () => requestAnimationFrame(() => { + hero.style.transition = 'opacity 0.25s ease'; + hero.style.opacity = '1'; + }); + if(img) { const pfpWrap = document.getElementById('artist-hero-pfp-wrap'); if(pfpWrap) { const pfpImg = document.createElement('img'); pfpImg.className = 'artist-hero-pfp'; pfpImg.alt = ''; + pfpImg.onload = _revealHero; + pfpImg.onerror = _revealHero; pfpImg.src = img; pfpWrap.prepend(pfpImg); - const initial = document.getElementById('artist-hero-pfp-initial'); - if(initial) initial.style.display = 'none'; + document.getElementById('artist-hero-pfp-initial')?.style.setProperty('display', 'none'); } + // color tint — async, fades in via CSS so it never pops + _extractColor(img).then(rgb => { + if(!rgb) return; + const [r, g, b] = rgb; + hero.querySelector('.artist-hero-tint')?.remove(); + const tint = document.createElement('div'); + tint.className = 'artist-hero-tint'; + tint.style.cssText = `position:absolute;inset:0;background:linear-gradient(to right,rgba(${r},${g},${b},.55) 0%,rgba(${r},${g},${b},.25) 55%,rgba(${r},${g},${b},.6) 100%);pointer-events:none;opacity:0;transition:opacity 0.5s ease`; + if(overlay) hero.insertBefore(tint, overlay); + requestAnimationFrame(() => { tint.style.opacity = '1'; }); + }); + } else { + _revealHero(); } - if(rgb) { - const [r, g, b] = rgb; - const tint = document.createElement('div'); - tint.className = 'artist-hero-tint'; - tint.style.cssText = `position:absolute;inset:0;background:linear-gradient(to right,rgba(${r},${g},${b},.55) 0%,rgba(${r},${g},${b},.25) 55%,rgba(${r},${g},${b},.6) 100%);pointer-events:none`; - if(overlay) hero.insertBefore(tint, overlay); - } - requestAnimationFrame(() => { - hero.style.transition = 'opacity 0.3s ease'; - hero.style.opacity = '1'; - }); // ── Bio ── const info = infoRes.status === 'fulfilled' ? infoRes.value?.artistInfo2 : null;