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

@@ -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,48 +130,39 @@ 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">
{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",
].join(" ")}
>
{m.label}
{m.value === "inbox" && inboxCount > 0 && (
<span className="absolute -top-1 -right-1 min-w-[13px] h-3 bg-accent text-black text-[8px] font-bold rounded-full flex items-center justify-center px-0.5 leading-none">
{inboxCount > 99 ? "99+" : inboxCount}
</span>
)}
</button>
))}
</div>
</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.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}
{m.value === "inbox" && inboxCount > 0 && (
<span className="absolute -top-1 -right-1 min-w-[13px] h-3 bg-accent text-black text-[8px] font-bold rounded-full flex items-center justify-center px-0.5 leading-none">
{inboxCount > 99 ? "99+" : inboxCount}
</span>
)}
</button>
))}
</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>