feat(app): add real images to all seed content + universal SVG text fallbacks

- Songs: 19/21 now have iTunes album art URLs (2 niche Bitcoin artists use fallback)
- Podcasts: 21/21 now have iTunes artwork URLs
- News: 21/21 now have Unsplash topic images
- Books: Fixed The Network State cover URL
- Places: 21/21 now have photos (Unsplash + Wikimedia)
- Added generateNewsFallback() and generateImageFallback() SVG generators
- Updated NewsCard, NewsGrid, ImageCard to use SVG text fallback instead of emoji
- Added error handling to PlaceCard and PlaceGrid for failed photo loads

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 20:11:15 +00:00
co-authored by Claude Opus 4.6
parent 982a0c6aa6
commit c88e837cc9
11 changed files with 156 additions and 153 deletions
@@ -106,6 +106,38 @@ export function generateSongCoverFallback(title: string, artist?: string): strin
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}
/** News article fallback — newspaper style with category badge */
export function generateNewsFallback(title: string, source?: string): string {
const hue = [...(title + (source ?? ''))].reduce((acc, c) => acc + c.charCodeAt(0), 0) % 360
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<rect width="200" height="200" fill="hsl(${hue}, 18%, 10%)"/>
<rect x="3" y="3" width="194" height="194" rx="12" fill="none" stroke="hsl(${hue}, 22%, 18%)" stroke-width="1"/>
<text x="100" y="72" text-anchor="middle" fill="hsl(${hue}, 20%, 22%)" font-family="Georgia,'Times New Roman',serif" font-size="10" font-weight="400" letter-spacing="3">NEWS</text>
<line x1="50" y1="82" x2="150" y2="82" stroke="hsl(${hue}, 22%, 17%)" stroke-width="0.8"/>
<line x1="40" y1="100" x2="160" y2="100" stroke="hsl(${hue}, 18%, 16%)" stroke-width="3"/>
<line x1="50" y1="112" x2="150" y2="112" stroke="hsl(${hue}, 18%, 14%)" stroke-width="2"/>
<line x1="55" y1="122" x2="145" y2="122" stroke="hsl(${hue}, 18%, 13%)" stroke-width="1.5"/>
<line x1="60" y1="132" x2="140" y2="132" stroke="hsl(${hue}, 18%, 13%)" stroke-width="1"/>
${source ? `<text x="100" y="165" text-anchor="middle" fill="hsl(${hue}, 25%, 40%)" font-family="system-ui,sans-serif" font-size="9" font-weight="400">${escapeXml(source.length > 22 ? source.slice(0, 20) + '…' : source)}</text>` : ''}
</svg>`
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}
/** Image fallback — picture frame style */
export function generateImageFallback(title: string): string {
const hue = [...title].reduce((acc, c) => acc + c.charCodeAt(0), 0) % 360
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<rect width="200" height="200" fill="hsl(${hue}, 15%, 10%)"/>
<rect x="3" y="3" width="194" height="194" rx="12" fill="none" stroke="hsl(${hue}, 20%, 18%)" stroke-width="1"/>
<text x="100" y="72" text-anchor="middle" fill="hsl(${hue}, 18%, 22%)" font-family="system-ui,sans-serif" font-size="10" font-weight="500" letter-spacing="3">IMAGE</text>
<rect x="60" y="85" width="80" height="60" rx="4" fill="none" stroke="hsl(${hue}, 25%, 25%)" stroke-width="1.5"/>
<circle cx="80" cy="102" r="6" fill="hsl(${hue}, 30%, 30%)"/>
<path d="M60 135 L85 115 L105 128 L120 118 L140 135 Z" fill="hsl(${hue}, 25%, 20%)"/>
<text x="100" y="170" text-anchor="middle" fill="hsl(${hue}, 30%, 45%)" font-family="system-ui,sans-serif" font-size="10" font-weight="500">${escapeXml(title.length > 20 ? title.slice(0, 18) + '…' : title)}</text>
</svg>`
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}
/** Film poster fallback — cinematic sans-serif */
export function generatePosterFallback(title: string, year?: number): string {
const hue = [...title].reduce((acc, c) => acc + c.charCodeAt(0), 0) % 360