diff --git a/src/frontend/src/components/ContentListItem.tsx b/src/frontend/src/components/ContentListItem.tsx
new file mode 100644
index 0000000..e99e64d
--- /dev/null
+++ b/src/frontend/src/components/ContentListItem.tsx
@@ -0,0 +1,295 @@
+import { Bookmark, BookmarkPlus, Download, ExternalLink, Film, Music } from 'lucide-react';
+import { StatusBadge } from './StatusBadge';
+import { DownloadProgressBar } from './DownloadProgressBar';
+import { useDownloadProgress } from '../contexts/DownloadProgressContext';
+import type { ContentItem } from '@shared/types/index';
+
+// ── Helpers (shared pattern with ContentCard) ──
+
+function formatDuration(seconds: number | null): string {
+ if (seconds == null) return '';
+ const h = Math.floor(seconds / 3600);
+ const m = Math.floor((seconds % 3600) / 60);
+ const s = seconds % 60;
+ if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
+ return `${m}:${String(s).padStart(2, '0')}`;
+}
+
+function formatRelativeTime(isoString: string | null): string {
+ if (!isoString) return '';
+ const delta = Date.now() - Date.parse(isoString);
+ if (delta < 0) return 'just now';
+ const seconds = Math.floor(delta / 1000);
+ if (seconds < 60) return 'just now';
+ const minutes = Math.floor(seconds / 60);
+ if (minutes < 60) return `${minutes}m ago`;
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h ago`;
+ const days = Math.floor(hours / 24);
+ if (days < 30) return `${days}d ago`;
+ const months = Math.floor(days / 30);
+ if (months < 12) return `${months}mo ago`;
+ return `${Math.floor(months / 12)}y ago`;
+}
+
+// ── Component ──
+
+interface ContentListItemProps {
+ item: ContentItem;
+ selected: boolean;
+ onSelect: (id: number) => void;
+ onToggleMonitored: (id: number, monitored: boolean) => void;
+ onDownload: (id: number) => void;
+}
+
+export function ContentListItem({ item, selected, onSelect, onToggleMonitored, onDownload }: ContentListItemProps) {
+ const progress = useDownloadProgress(item.id);
+ const duration = formatDuration(item.duration);
+ const published = formatRelativeTime(item.publishedAt);
+
+ return (
+
onSelect(item.id)}
+ onMouseEnter={(e) => {
+ if (!selected) e.currentTarget.style.borderColor = 'var(--border-light)';
+ // Reveal checkbox on hover
+ const cb = e.currentTarget.querySelector('.list-checkbox') as HTMLElement | null;
+ if (cb) cb.style.opacity = '1';
+ }}
+ onMouseLeave={(e) => {
+ if (!selected) e.currentTarget.style.borderColor = 'var(--border)';
+ // Hide checkbox if not selected
+ const cb = e.currentTarget.querySelector('.list-checkbox') as HTMLElement | null;
+ if (cb && !selected) cb.style.opacity = '0';
+ }}
+ >
+ {/* Selection checkbox */}
+
+ {
+ e.stopPropagation();
+ onSelect(item.id);
+ }}
+ onClick={(e) => e.stopPropagation()}
+ aria-label={`Select ${item.title}`}
+ style={{
+ width: 16,
+ height: 16,
+ cursor: 'pointer',
+ accentColor: 'var(--accent)',
+ }}
+ />
+
+
+ {/* Thumbnail */}
+
+ {item.thumbnailUrl ? (
+

+ ) : (
+
+ {item.contentType === 'audio' ? : }
+
+ )}
+
+ {/* Duration badge on thumbnail */}
+ {duration && (
+
+ {duration}
+
+ )}
+
+ {/* Download progress overlay */}
+ {item.status === 'downloading' && progress && (
+
+
+
+ )}
+
+
+ {/* Info section */}
+
+
+ {/* Right section: status badge + action buttons */}
+
+
+
+
+
+
+ {item.status !== 'downloaded' && item.status !== 'downloading' && item.status !== 'queued' && (
+
+ )}
+
+
e.stopPropagation()}
+ title="Open on YouTube"
+ aria-label={`Open ${item.title} on YouTube`}
+ style={{
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: 28,
+ height: 28,
+ borderRadius: 'var(--radius-sm)',
+ color: 'var(--text-muted)',
+ transition: 'color var(--transition-fast)',
+ }}
+ >
+
+
+
+
+
+ );
+}