Consistency sweep: fix switcher layout, gaps, buttons, empty states

- Home: mode switcher moved to its own row (no longer crammed next to
  title on mobile), hide-watched simplified to text-only toggle
- Home/History/Discovery: pagination buttons text-sm → text-xs, page
  counter text-sm → text-xs
- Liked/Downloads/SearchResults: top-level gap-8 → gap-6
- Liked: refresh button px-4 py-2 text-sm → px-3 py-1.5 text-xs
- Empty states: standardize to text-zinc-500 text-sm across Queue,
  ContinueWatching, History, Following, Discovery, Liked
- Following: "Latest uploads" tab label → "Feed"
- Home: remove -mt-3 hacks from mode description rows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mattias Tall
2026-05-26 17:21:02 +02:00
parent f422d754b9
commit 94f74215e2
9 changed files with 57 additions and 65 deletions

View File

@@ -25,7 +25,7 @@ export default function ContinueWatchingPage() {
</svg>
</div>
<div>
<p className="text-zinc-300 font-medium">Nothing in progress</p>
<p className="text-zinc-500 text-sm">Nothing in progress</p>
<p className="text-zinc-500 text-sm mt-1">
Videos you've started but not finished will appear here.
</p>

View File

@@ -284,7 +284,7 @@ export default function DiscoveryPage() {
</svg>
</div>
<div>
<p className="text-zinc-300 font-medium">Nothing here yet</p>
<p className="text-zinc-500 text-sm">Nothing here yet</p>
<p className="text-zinc-500 text-sm mt-1 max-w-xs">
Follow a few channels first, then hit "Find more" to discover similar ones.
</p>
@@ -308,15 +308,15 @@ export default function DiscoveryPage() {
<button
onClick={() => { setChannelPage(p => p - 1); scrollToTop(); }}
disabled={channelPage === 0}
className="px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
Prev
</button>
<span className="text-zinc-500 text-sm tabular-nums">Page {channelPage + 1}</span>
<span className="text-zinc-500 text-xs tabular-nums">Page {channelPage + 1}</span>
<button
onClick={() => { setChannelPage(p => p + 1); scrollToTop(); }}
disabled={!hasNextChannelPage}
className="px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
Next
</button>
@@ -338,15 +338,15 @@ export default function DiscoveryPage() {
<button
onClick={() => { setVideoPage(p => p - 1); scrollToTop(); }}
disabled={videoPage === 0}
className="px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
Prev
</button>
<span className="text-zinc-500 text-sm tabular-nums">Page {videoPage + 1}</span>
<span className="text-zinc-500 text-xs tabular-nums">Page {videoPage + 1}</span>
<button
onClick={() => { setVideoPage(p => p + 1); scrollToTop(); }}
disabled={!hasNextVideoPage}
className="px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
Next
</button>

View File

@@ -95,7 +95,7 @@ export default function DownloadsPage() {
const hasRemovable = history.length > 0 || trash.length > 0;
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between">
<h1 className="font-display font-bold text-2xl text-zinc-100">Downloads</h1>
{hasRemovable && (

View File

@@ -709,7 +709,7 @@ export default function Following() {
</svg>
</div>
<div>
<p className="text-zinc-300 font-medium">Not following anyone yet</p>
<p className="text-zinc-500 text-sm">Not following anyone yet</p>
<p className="text-zinc-500 text-sm mt-1">
Hit Follow on a channel while watching a video or searching.
</p>

View File

@@ -30,7 +30,7 @@ export default function History() {
</div>
) : videos.length === 0 ? (
<div className="flex flex-col items-center gap-3 py-20 text-center">
<p className="text-zinc-400 text-sm">No watch history yet. Start watching some videos!</p>
<p className="text-zinc-500 text-sm">No watch history yet. Start watching some videos!</p>
</div>
) : (
<>
@@ -43,15 +43,15 @@ export default function History() {
<button
onClick={() => { setPage(p => p - 1); scrollToTop(); }}
disabled={page === 0}
className="px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
Prev
</button>
<span className="text-zinc-500 text-sm tabular-nums">Page {page + 1}</span>
<span className="text-zinc-500 text-xs tabular-nums">Page {page + 1}</span>
<button
onClick={() => { setPage(p => p + 1); scrollToTop(); }}
disabled={!hasNext}
className="px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
Next
</button>

View File

@@ -107,20 +107,21 @@ export default function Home() {
</div>
) : hasFollowing ? (
<section className="flex flex-col gap-6">
<div className="flex items-center justify-between flex-wrap gap-3">
<h2 className="font-display font-semibold text-xl text-zinc-200">Home</h2>
<div className="flex items-center gap-2">
{/* Title row + secondary controls */}
<div className="flex items-center justify-between gap-2">
<h2 className="font-display font-semibold text-base sm:text-xl text-zinc-200">Home</h2>
<div className="flex items-center gap-1.5">
<button
onClick={toggleViewMode}
title={viewMode === "grid" ? "Switch to list view" : "Switch to grid view"}
className="flex items-center justify-center w-8 h-8 rounded-lg text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800 transition-colors border border-zinc-800"
className="flex items-center justify-center w-7 h-7 rounded-md text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800 transition-colors"
>
{viewMode === "grid" ? (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
) : (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zm10 0a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zm10 0a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
</svg>
)}
@@ -129,32 +130,25 @@ export default function Home() {
onClick={handleHideWatchedToggle}
title={hideWatched ? "Showing unwatched only" : "Showing all videos"}
className={[
"flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium transition-colors border",
hideWatched
? "bg-accent/10 text-accent border-accent/30"
: "text-zinc-500 border-zinc-800 hover:text-zinc-300 hover:border-zinc-700",
"flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium transition-colors",
hideWatched ? "text-accent" : "text-zinc-600 hover:text-zinc-400",
].join(" ")}
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{hideWatched ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
)}
</svg>
{hideWatched ? "Unwatched" : "All"}
</button>
<div className="flex items-center gap-0.5 bg-zinc-900 rounded-lg p-0.5">
</div>
</div>
{/* Mode switcher — own row, full width on mobile */}
<div className="flex items-center gap-0.5 bg-zinc-900 rounded-lg p-0.5 self-start">
{FEED_MODES.map(m => (
<button
key={m.value}
onClick={() => handleModeChange(m.value)}
title={m.hint}
className={[
"relative px-2 py-1 rounded-md text-xs font-medium transition-colors",
mode === m.value
? "bg-zinc-700 text-zinc-100"
: "text-zinc-500 hover:text-zinc-300",
"relative px-2.5 py-1 rounded-md text-xs font-medium transition-colors",
mode === m.value ? "bg-zinc-700 text-zinc-100" : "text-zinc-500 hover:text-zinc-300",
].join(" ")}
>
{m.label}
@@ -166,11 +160,9 @@ export default function Home() {
</button>
))}
</div>
</div>
</div>
{/* Duration filter */}
<div className="flex items-center gap-1.5 -mt-3">
<div className="flex items-center gap-1.5">
{[["short", "< 10 min"], ["medium", "1030 min"], ["long", "30+ min"]].map(([val, label]) => (
<button
key={val}
@@ -188,7 +180,7 @@ export default function Home() {
</div>
{mode === "inbox" && (
<div className="flex items-center justify-between -mt-3">
<div className="flex items-center justify-between">
<p className="text-xs text-zinc-600">Unwatched videos from followed channels since your last visit.</p>
<button
onClick={() => markSeenMut.mutate()}
@@ -200,10 +192,10 @@ export default function Home() {
</div>
)}
{mode === "chronological" && (
<p className="text-xs text-zinc-600 -mt-3">All videos from channels you follow, newest first.</p>
<p className="text-xs text-zinc-600">All videos from channels you follow, newest first.</p>
)}
{mode === "random" && (
<div className="flex items-center justify-between -mt-3">
<div className="flex items-center justify-between">
<p className="text-xs text-zinc-600">Random from your discovery pool no weighting, no ranking.</p>
<button
onClick={handleReshuffle}
@@ -238,15 +230,15 @@ export default function Home() {
<button
onClick={() => { setPage(p => p - 1); scrollToTop(); }}
disabled={page === 0}
className="px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
Prev
</button>
<span className="text-zinc-500 text-sm tabular-nums">Page {page + 1}</span>
<span className="text-zinc-500 text-xs tabular-nums">Page {page + 1}</span>
<button
onClick={() => { setPage(p => p + 1); scrollToTop(); }}
disabled={!hasNextPage}
className="px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
Next
</button>

View File

@@ -42,7 +42,7 @@ export default function LikedPage() {
}
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-6">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="font-display font-bold text-2xl text-zinc-100">Liked videos</h1>
@@ -54,7 +54,7 @@ export default function LikedPage() {
<button
onClick={() => refreshMut.mutate()}
disabled={refreshMut.isPending || refreshMut.isSuccess}
className="flex items-center gap-2 px-4 py-2 rounded-xl bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 transition-colors disabled:opacity-60"
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-zinc-800 text-zinc-300 text-xs font-medium hover:bg-zinc-700 transition-colors disabled:opacity-60"
>
{refreshMut.isPending ? (
<>
@@ -82,7 +82,7 @@ export default function LikedPage() {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
<p className="text-zinc-400 font-medium">No liked videos yet</p>
<p className="text-zinc-500 text-sm">No liked videos yet</p>
<p className="text-zinc-600 text-sm max-w-xs">
Hit the heart on any video. Liked videos teach the discovery engine what you enjoy.
</p>

View File

@@ -27,7 +27,7 @@ export default function QueuePage() {
</svg>
</div>
<div>
<p className="text-zinc-300 font-medium">Queue is empty</p>
<p className="text-zinc-500 text-sm">Queue is empty</p>
<p className="text-zinc-500 text-sm mt-1">
Hit the queue icon on any video to save it for later.
</p>

View File

@@ -68,7 +68,7 @@ export default function SearchResults() {
const channelsFirst = channels.length > 0 && (videos.length === 0 || source === "local");
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3 flex-wrap">
<h1 className="font-display font-semibold text-xl text-zinc-100">
Results for <span className="text-accent">"{q}"</span>