window.AdCard = function AdCard({ ad, selected, onToggle, onExpand, minimal }) { const p = PLATFORMS[ad.platform] || PLATFORMS.foreplay; const hasTranscript = ad.transcript && ad.transcript.length > 0; const hasVideo = !!ad.creativeUrl; // AdSpy / BrandSearch transcripts are generated server-side via Whisper / // ElevenLabs after the ad is fetched. Until the transcript hydration call // returns we badge the card so users know something is in flight \u2014 // otherwise the missing transcript looks like a permanent gap. const transcriptStatus = ad.transcriptStatus || null; const transcriptPending = hasVideo && !hasTranscript && (ad.platform === 'adspy' || ad.platform === 'brandsearch') && transcriptStatus !== 'fallback' && transcriptStatus !== 'unavailable' && transcriptStatus !== 'empty'; const showVideoThumb = hasVideo && !ad.thumbnailUrl; const score = ad.score; const scoreText = (typeof score === 'number') ? (score > 10 ? Math.round(score) : score.toFixed(1)) : null; const metricChips = RESILIA_ADAPT.metricChips(ad); const isCurrentRun = !!(ad.currentSearchRun || ad.currentAutoresearchRun); const openExpand = (e) => { e.stopPropagation(); onExpand(ad); }; return (
{showVideoThumb && (
{!minimal && (
{ad.brand} {hasTranscript && ( )} {!hasTranscript && transcriptPending && ( Transcribing… (~5–20s) )}
{ad.directionLabel && (
{ad.directionLabel}
)} {ad.searchReturnLabel && (
{ad.searchReturnLabel}
)}
{ad.copy}
{scoreText !== null && ( {scoreText} {ad.fallbackSource ? ' · fallback' : ''} )} {metricChips.map(({ label, value, title, className }) => ( {label} {value} ))} {ad.daysRunning ? {ad.daysRunning}d running : null}
)} {minimal && (
{ad.brand}
{scoreText !== null && ( {scoreText} )}
)}
); };