// Podcast Selection Page — Layer 1 const COLOR_PALETTE = ['#6366f1', '#22d3ee', '#f59e0b', '#22c55e', '#ec4899']; const deriveColor = (id) => { if (!id) return COLOR_PALETTE[0]; let hash = 2166136261; for (let i = 0; i < id.length; i++) { hash ^= id.charCodeAt(i); hash = Math.imul(hash, 16777619); } return COLOR_PALETTE[Math.abs(hash) % COLOR_PALETTE.length]; }; const PodcastSelect = ({ lang, onSelect }) => { const t = lang === 'zh'; const [search, setSearch] = React.useState(''); const [hovered, setHovered] = React.useState(null); const [shows, setShows] = React.useState(null); const [error, setError] = React.useState(null); React.useEffect(() => { let cancelled = false; setShows(null); setError(null); (async () => { try { const res = await fetch(`${API_BASE}/shows`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); if (!cancelled) setShows(data); } catch (err) { if (!cancelled) setError(err.message); } })(); return () => { cancelled = true; }; }, []); const filtered = (shows || []).filter(s => { const q = search.toLowerCase(); return ( (s.title || '') + (s.description || '') + (s.rss_url || '') ).toLowerCase().includes(q); }); const totalTranscribed = (shows || []).reduce((a, s) => a + (s.transcribed_count || 0), 0); return (
{/* Header */}

{t ? '節目庫' : 'Show Library'}

{t ? '選擇 Podcast 節目' : 'Select a Podcast Show'}

{shows ? (t ? `共 ${shows.length} 個節目,${totalTranscribed} 集已轉錄完成` : `${shows.length} shows, ${totalTranscribed} episodes transcribed`) : (t ? '載入中...' : 'Loading...')}

setSearch(e.target.value)} placeholder={t ? '搜尋節目名稱...' : 'Search shows...'} icon="search" />
{/* Content */}
{error && (

{t ? `載入節目失敗:${error}` : `Failed to load shows: ${error}`}

)} {!error && shows === null && (
{t ? '載入中...' : 'Loading...'}
)} {!error && shows && shows.length === 0 && (

{t ? '尚未新增節目,請透過 POST /shows 加入第一個 RSS feed' : 'No shows yet — add one via POST /shows with an RSS URL'}

)} {!error && shows && shows.length > 0 && (
{filtered.map(show => ( setHovered(show.id)} onMouseLeave={() => setHovered(null)} onClick={() => onSelect(show)} /> ))}
)} {!error && shows && shows.length > 0 && filtered.length === 0 && (

{t ? '找不到符合的節目' : 'No matching shows found'}

)}
); }; const ShowCard = ({ show, lang, hovered, onMouseEnter, onMouseLeave, onClick }) => { const t = lang === 'zh'; const episodeCount = show.episode_count || 0; const transcribedCount = show.transcribed_count || 0; const pct = episodeCount > 0 ? Math.round((transcribedCount / episodeCount) * 100) : 0; const color = deriveColor(show.id); return (
{/* Cover + title */}
{show.image_url ? ( ) : ( )}
{show.title}
{show.language && (
{show.language}
)}
{/* Description */} {show.description && (

{show.description}

)} {/* Stats */}
{episodeCount} {t ? '集' : 'eps'}
{transcribedCount} {t ? '已轉錄' : 'transcribed'}
{/* Progress bar */}
{t ? '轉錄進度' : 'Transcription'} {pct}%
{/* CTA */}
{show.rss_url}
{t ? '進入節目' : 'Open Show'}
); }; Object.assign(window, { PodcastSelect, deriveColor });