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}
+
+ {initial}
);
}
@@ -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. */}
+
{article.feed_title || 'Journal'}
diff --git a/web/src/components/AuthShell.tsx b/web/src/components/AuthShell.tsx
index 3e7a2c8..1489b42 100644
--- a/web/src/components/AuthShell.tsx
+++ b/web/src/components/AuthShell.tsx
@@ -38,7 +38,7 @@ export function AuthShell({ eyebrow, title, subtitle, children }: AuthShellProps
transition={{ duration: 0.7, delay: 0.1 }}
className="relative z-10 space-y-6"
>
-
+
Le calme dans le chaos de l'information.
@@ -46,7 +46,7 @@ export function AuthShell({ eyebrow, title, subtitle, children }: AuthShellProps
-
+
FlowReader · Édition 2026
diff --git a/web/src/components/TopBar.tsx b/web/src/components/TopBar.tsx
index 1753acd..f328674 100644
--- a/web/src/components/TopBar.tsx
+++ b/web/src/components/TopBar.tsx
@@ -211,7 +211,7 @@ export function TopBar({ onSelectFeed, selectedFeedId, onEnterFocus }: TopBarPro
return (
<>
-
+
FlowReader
diff --git a/web/src/components/focus/FocusCard.tsx b/web/src/components/focus/FocusCard.tsx
index 19b1bd5..4c9a3a5 100644
--- a/web/src/components/focus/FocusCard.tsx
+++ b/web/src/components/focus/FocusCard.tsx
@@ -24,19 +24,19 @@ export function FocusCard({ article, isTop = false }: FocusCardProps) {
onError={(e) => (e.currentTarget.style.display = 'none')}
/>
) : (
-
-
{article.feed_title}
+
+ {article.feed_title}
)}
-
+
{readingTimeLabel(article.reading_time)}
{/* Body */}
-
-
+
+
{article.feed_title || 'Journal'}
@@ -54,7 +54,7 @@ export function FocusCard({ article, isTop = false }: FocusCardProps) {
)}
{isTop && (
-
+
← Garder · Lu → · Entrée pour lire
)}
diff --git a/web/src/components/focus/FocusCardStack.tsx b/web/src/components/focus/FocusCardStack.tsx
index b242d2b..03d154c 100644
--- a/web/src/components/focus/FocusCardStack.tsx
+++ b/web/src/components/focus/FocusCardStack.tsx
@@ -144,7 +144,7 @@ export function FocusCardStack({ articles, onEmpty, onExit }: FocusCardStackProp
{announce}
-
+
{articles.length - currentIndex} restant{articles.length - currentIndex > 1 ? 's' : ''}
diff --git a/web/src/components/reader/Reader.tsx b/web/src/components/reader/Reader.tsx
index 4102212..66cd3d2 100644
--- a/web/src/components/reader/Reader.tsx
+++ b/web/src/components/reader/Reader.tsx
@@ -255,7 +255,7 @@ export function Reader({ article: listArticle, next, hasPrev, onClose, onNext, o
{listArticle.feed_title || 'Article'}
- , {readingTimeLabel(listArticle.reading_time)} de lecture
+ · {readingTimeLabel(listArticle.reading_time)} de lecture