Fix channel page mobile layout

On mobile: move action buttons below the banner into their own row
(flex-1 Follow + Download, compact Re-index) instead of cramming
three full-size buttons inside the banner overlay alongside the avatar
and name. Desktop keeps the original inline layout. Also reduced
banner height, avatar size, and description to 2-line clamp on mobile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mattias Tall
2026-05-26 17:09:17 +02:00
parent ca5196d9f1
commit ebd8ddee6e

View File

@@ -78,80 +78,76 @@ export default function ChannelPage() {
const isFollowed = channel.status === "followed"; const isFollowed = channel.status === "followed";
return ( return (
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-6">
{/* Channel header — banner with overlay, or plain if no banner */} {/* Banner */}
<div className={`-mx-4 -mt-6 relative ${channel.banner_url ? "" : "bg-zinc-900"} rounded-b-2xl overflow-hidden`}> <div className={`-mx-4 -mt-6 relative ${channel.banner_url ? "" : "bg-zinc-900"} rounded-b-2xl overflow-hidden`}>
{channel.banner_url && ( {channel.banner_url && (
<img src={channel.banner_url} alt="" className="w-full h-36 sm:h-52 object-cover" /> <img src={channel.banner_url} alt="" className="w-full h-28 sm:h-48 object-cover" />
)} )}
{/* Gradient overlay */} <div className={channel.banner_url ? "absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent" : ""} />
<div className={`${channel.banner_url ? "absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent" : ""}`} />
{/* Info row sits at the bottom of the banner */} {/* Avatar + name — always in the banner overlay */}
<div className={`${channel.banner_url ? "absolute bottom-0 inset-x-0" : ""} px-4 pb-4 pt-3 flex items-end gap-4`}> <div className={`${channel.banner_url ? "absolute bottom-0 inset-x-0" : ""} px-4 pb-4 pt-3 flex items-end gap-3`}>
{/* Avatar */}
{channel.thumbnail_url ? ( {channel.thumbnail_url ? (
<img <img src={channel.thumbnail_url} alt={channel.name}
src={channel.thumbnail_url} className="w-14 h-14 sm:w-20 sm:h-20 rounded-full object-cover shrink-0 ring-2 ring-black/40" />
alt={channel.name}
className="w-16 h-16 sm:w-20 sm:h-20 rounded-full object-cover shrink-0 ring-2 ring-black/40"
/>
) : ( ) : (
<div className="w-16 h-16 sm:w-20 sm:h-20 rounded-full bg-zinc-800 flex items-center justify-center text-2xl sm:text-3xl font-display font-bold text-zinc-400 shrink-0"> <div className="w-14 h-14 sm:w-20 sm:h-20 rounded-full bg-zinc-800 flex items-center justify-center text-2xl font-display font-bold text-zinc-400 shrink-0">
{channel.name?.[0]?.toUpperCase()} {channel.name?.[0]?.toUpperCase()}
</div> </div>
)} )}
{/* Name + meta */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h1 className="font-display font-bold text-xl sm:text-2xl text-white drop-shadow">{channel.name}</h1> <h1 className="font-display font-bold text-lg sm:text-2xl text-white drop-shadow leading-tight">{channel.name}</h1>
<p className="text-xs sm:text-sm text-zinc-300 mt-0.5 drop-shadow"> <p className="text-xs text-zinc-400 mt-0.5 drop-shadow">
{[ {[
formatSubs(channel.subscriber_count) && `${formatSubs(channel.subscriber_count)} subscribers`, formatSubs(channel.subscriber_count) && `${formatSubs(channel.subscriber_count)} subs`,
`${channel.video_count} videos indexed`, channel.video_count && `${channel.video_count} videos`,
].filter(Boolean).join(" · ")} ].filter(Boolean).join(" · ")}
</p> </p>
</div> </div>
{/* Desktop action buttons inline */}
{/* Action buttons */} <div className="hidden sm:flex items-center gap-2 shrink-0">
<div className="flex items-center gap-2 shrink-0 flex-wrap justify-end"> <button onClick={() => followMut.mutate()} disabled={followMut.isPending}
{dlResult != null && ( className={`text-sm font-medium px-4 py-1.5 rounded-lg transition-colors ${isFollowed ? "bg-zinc-700/80 text-zinc-300 hover:bg-zinc-600" : "bg-zinc-800/80 text-zinc-300 hover:bg-zinc-700"}`}>
<span className="text-sm text-accent font-mono"> {isFollowed ? "Following" : "Follow"}
{dlResult === 0 ? "Already up to date" : `${dlResult} queued`} </button>
</span> <button onClick={() => dlMut.mutate()} disabled={dlMut.isPending}
)} className="text-sm font-medium px-4 py-1.5 rounded-lg bg-accent text-black hover:bg-yellow-300 transition-colors disabled:opacity-60">
<button {dlMut.isPending ? "Queuing…" : "Download all"}
onClick={() => dlMut.mutate()} </button>
disabled={dlMut.isPending} <button onClick={() => indexMut.mutate()} disabled={indexMut.isPending || indexMut.isSuccess}
className="text-sm font-medium px-4 py-2 rounded-lg bg-accent text-black hover:bg-yellow-300 transition-colors disabled:opacity-60 flex items-center gap-2" className="text-sm font-medium px-4 py-1.5 rounded-lg bg-zinc-800/80 text-zinc-300 hover:bg-zinc-700 transition-colors disabled:opacity-60">
> {indexMut.isPending ? "Indexing…" : indexMut.isSuccess ? "Done ✓" : "Re-index"}
{dlMut.isPending ? "Queuing…" : "Download all"} </button>
</button> </div>
<button
onClick={() => indexMut.mutate()}
disabled={indexMut.isPending || indexMut.isSuccess}
className="text-sm font-medium px-4 py-2 rounded-lg bg-zinc-800/80 text-zinc-300 hover:bg-zinc-700 transition-colors disabled:opacity-60"
>
{indexMut.isPending ? "Indexing…" : indexMut.isSuccess ? "Done ✓" : "Re-index"}
</button>
<button
onClick={() => followMut.mutate()}
disabled={followMut.isPending}
className={`text-sm font-medium px-4 py-2 rounded-lg transition-colors ${
isFollowed
? "bg-zinc-700/80 text-zinc-300 hover:bg-zinc-600"
: "bg-zinc-800/80 text-zinc-300 hover:bg-zinc-700"
}`}
>
{isFollowed ? "Following" : "Follow"}
</button>
</div>
</div> </div>
</div> </div>
{/* Description below banner */} {/* Mobile action row — below banner */}
<div className="sm:hidden flex items-center gap-2 -mt-2">
<button onClick={() => followMut.mutate()} disabled={followMut.isPending}
className={`flex-1 text-sm font-medium py-2 rounded-lg transition-colors ${isFollowed ? "bg-zinc-800 text-zinc-300 hover:bg-zinc-700" : "bg-zinc-800 text-zinc-300 hover:bg-zinc-700"}`}>
{isFollowed ? "Following ✓" : "Follow"}
</button>
<button onClick={() => dlMut.mutate()} disabled={dlMut.isPending}
className="flex-1 text-sm font-medium py-2 rounded-lg bg-accent text-black hover:bg-yellow-300 transition-colors disabled:opacity-60">
{dlMut.isPending ? "Queuing…" : "Download all"}
</button>
<button onClick={() => indexMut.mutate()} disabled={indexMut.isPending || indexMut.isSuccess}
className="text-sm font-medium px-3 py-2 rounded-lg bg-zinc-800 text-zinc-500 hover:bg-zinc-700 transition-colors disabled:opacity-60">
{indexMut.isPending ? "…" : indexMut.isSuccess ? "✓" : "Re-index"}
</button>
</div>
{dlResult != null && (
<p className="text-xs text-accent font-mono -mt-2">
{dlResult === 0 ? "Already up to date" : `${dlResult} downloads queued`}
</p>
)}
{/* Description */}
{channel.description && ( {channel.description && (
<p className="text-sm text-zinc-400 line-clamp-3 -mt-4">{channel.description}</p> <p className="text-xs text-zinc-500 line-clamp-2 -mt-2">{channel.description}</p>
)} )}
{/* Video grid */} {/* Video grid */}