Hero falls back to the next image when the lead's won't load
Some sources hotlink-protect their images (e.g. Guardian's i.guim.co.uk → 401), so a perfectly-enriched lead could still render an imageless hero. The browser is the only true judge of loadability, so on a hero image error, promote the next brief item that has an image into the hero slot; the failed lead becomes a text tile. Resets to the lead on each fresh brief. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
let { article, onaction, onreplace, ontag, hero = false } = $props();
|
||||
let { article, onaction, onreplace, ontag, onimageerror, hero = false } = $props();
|
||||
|
||||
const humanize = (s) => (s || '').replace(/-/g, ' ');
|
||||
// Grouping tags are the doorways; cap at 3 so cards stay calm. Fall back to
|
||||
@@ -49,7 +49,7 @@
|
||||
{#if showImage}
|
||||
<a class="media" href={safeHref} target="_blank" rel="noopener">
|
||||
<img src={article.image_url} alt="" loading="lazy" referrerpolicy="no-referrer"
|
||||
onerror={() => (failed = true)} />
|
||||
onerror={() => { failed = true; onimageerror?.(); }} />
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
let families = $state([]);
|
||||
let selected = $state('today'); // 'today' | a mood key | a topic key | 'tag:<slug>'
|
||||
let brief = $state(null);
|
||||
let heroIdx = $state(0); // which brief item fills the hero (advances if its image won't load)
|
||||
let feed = $state([]);
|
||||
let userPrefs = $state(P.blank());
|
||||
let showBoundaries = $state(false);
|
||||
@@ -87,6 +88,18 @@
|
||||
);
|
||||
let activeTab = $derived(showYou ? 'you' : selected === 'today' ? 'today' : 'browse');
|
||||
|
||||
// The hero is the only image slot. Some sources hotlink-protect their images
|
||||
// (e.g. Guardian → 401), so if the lead's image won't load, promote the next
|
||||
// brief item that has one. The failed lead just becomes a text tile.
|
||||
let heroArticle = $derived(brief?.items?.[heroIdx] ?? null);
|
||||
let restArticles = $derived((brief?.items ?? []).filter((_, i) => i !== heroIdx));
|
||||
function heroImageFailed() {
|
||||
const items = brief?.items ?? [];
|
||||
for (let i = heroIdx + 1; i < items.length; i++) {
|
||||
if (items[i]?.image_url) { heroIdx = i; return; }
|
||||
}
|
||||
}
|
||||
|
||||
// The filter for the current view: a mood's preset, a topic include, or none.
|
||||
function viewFilter(key = selected) {
|
||||
if (key === 'today') return {};
|
||||
@@ -111,6 +124,7 @@
|
||||
brief = fetched;
|
||||
P.saveJSON(BRIEF_VIEW_KEY, { generated_at: fetched.generated_at, items: fetched.items });
|
||||
}
|
||||
heroIdx = 0; // fresh brief — start the hero at the lead again
|
||||
remember(brief.items);
|
||||
}
|
||||
|
||||
@@ -158,7 +172,7 @@
|
||||
async function replaceArticle(article) {
|
||||
const list = selected === 'today' ? brief?.items : feed;
|
||||
if (!list) return;
|
||||
const isHero = selected === 'today' && list[0]?.id === article.id;
|
||||
const isHero = selected === 'today' && heroArticle?.id === article.id;
|
||||
const exclude = Array.from(seenIds).join(',');
|
||||
const q = mergedParam();
|
||||
const url = `/api/replacement?exclude=${exclude}&avoid_paywall=true${isHero ? '&gentle=true' : ''}${q ? '&' + q : ''}`;
|
||||
@@ -281,10 +295,10 @@
|
||||
{#if selected === 'today'}
|
||||
{#if brief?.items?.length}
|
||||
<section class="rise">
|
||||
<ArticleCard article={brief.items[0]} hero onaction={applyAction} onreplace={replaceArticle} ontag={(t) => select('tag:' + t)} />
|
||||
{#if brief.items.length > 1}
|
||||
<ArticleCard article={heroArticle} hero onaction={applyAction} onreplace={replaceArticle} ontag={(t) => select('tag:' + t)} onimageerror={heroImageFailed} />
|
||||
{#if restArticles.length}
|
||||
<div class="grid rest">
|
||||
{#each brief.items.slice(1) as a (a.id)}
|
||||
{#each restArticles as a (a.id)}
|
||||
<ArticleCard article={a} onaction={applyAction} onreplace={replaceArticle} ontag={(t) => select('tag:' + t)} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user