diff --git a/web/src/components/ArticleCard.tsx b/web/src/components/ArticleCard.tsx index 2c3ffa9..ac1adfa 100644 --- a/web/src/components/ArticleCard.tsx +++ b/web/src/components/ArticleCard.tsx @@ -15,9 +15,10 @@ interface ArticleCardProps { /** Initial-letter cover used when a feed provides no image (no third-party fallback). */ function CoverFallback({ title, feed }: { title: string; feed?: string }) { + const initial = (feed || title).trim().charAt(0).toUpperCase(); return ( -
- {feed || title} + ); } @@ -25,6 +26,7 @@ function CoverFallback({ title, feed }: { title: string; feed?: string }) { function ArticleCardImpl({ article, index, isCurrent, onOpen, onToggleRead, onToggleFavorite }: ArticleCardProps) { const [offset, setOffset] = useState(0); const [imgFailed, setImgFailed] = useState(false); + const [imgLoaded, setImgLoaded] = useState(false); // Touch-only swipe: right = read/unread, left = favorite. Mouse drags (text // selection) never trigger it. @@ -80,12 +82,15 @@ function ArticleCardImpl({ article, index, isCurrent, onOpen, onToggleRead, onTo referrerPolicy="no-referrer" width={640} height={360} - className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-[1.03]" + className={`w-full h-full object-cover transition-[transform,opacity] duration-700 group-hover:scale-[1.03] ${imgLoaded ? 'opacity-100' : 'opacity-0'}`} + onLoad={() => setImgLoaded(true)} onError={() => setImgFailed(true)} /> ) : ( )} + {/* Light bottom shade: gives the cover depth and lifts the feed chip. */} +