From b436e39f40b002d52eb20363cfe2f426cd484d27 Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 7 Jun 2026 22:28:59 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20hero=20non-blocking=20=E2=80=94=20reveal?= =?UTF-8?q?s=20on=20pfp=20load,=20tint=20fades=20in=20via=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/views/detail.js | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) 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;