diff --git a/src/views/detail.js b/src/views/detail.js index f804ace..096d60e 100644 --- a/src/views/detail.js +++ b/src/views/detail.js @@ -119,42 +119,56 @@ export async function loadArtistDetail(artist) { const _bgCover = _firstAlbums[0] ? coverUrl(_firstAlbums[0].coverArt, 512) : null; const bgSrc = img || _bgCover; + // Gate reveal on pfp + bg + color all ready — nothing shows until everything is set + let _pfpReady = false, _bgReady = false, _colorReady = false; + let _heroRgb = null; + + const _revealHero = () => { + if(!_pfpReady || !_bgReady || !_colorReady) return; + if(_heroRgb) { + const [r, g, b] = _heroRgb; + 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`; + if(overlay) hero.insertBefore(tint, overlay); + } + requestAnimationFrame(() => { + hero.style.transition = 'opacity 0.25s ease'; + hero.style.opacity = '1'; + }); + }; + + // Preload bg — only set background-image after it's in cache if(bgSrc) { hero.style.background = ''; const bgEl = document.getElementById('artist-hero-bg'); - if(bgEl) bgEl.style.backgroundImage = `url('${bgSrc}')`; + const _bgImg = new Image(); + _bgImg.onload = _bgImg.onerror = () => { + if(bgEl) bgEl.style.backgroundImage = `url('${bgSrc}')`; + _bgReady = true; _revealHero(); + }; + _bgImg.src = bgSrc; + } else { + _bgReady = true; } - 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.onload = pfpImg.onerror = () => { _pfpReady = true; _revealHero(); }; pfpImg.src = img; pfpWrap.prepend(pfpImg); document.getElementById('artist-hero-pfp-initial')?.style.setProperty('display', 'none'); + } else { + _pfpReady = true; } - // 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'; }); - }); + _extractColor(img).then(rgb => { _heroRgb = rgb; _colorReady = true; _revealHero(); }); } else { - _revealHero(); + _pfpReady = true; _colorReady = true; _revealHero(); } // ── Bio ──