Channel page: overlay avatar/name/buttons on banner

Name, subscriber count, and action buttons sit at the bottom of the
banner with a gradient overlay. Falls back to a plain dark header when
no banner is available. Description moves below the header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mattias Tall
2026-05-26 10:58:31 +02:00
parent 426e85c2c9
commit 219a388d72

View File

@@ -79,39 +79,42 @@ export default function ChannelPage() {
return (
<div className="flex flex-col gap-8">
{/* Banner */}
{channel.banner_url && (
<div className="-mx-4 -mt-6 mb-2">
<img src={channel.banner_url} alt="" className="w-full h-28 sm:h-44 object-cover" />
</div>
)}
{/* Channel header */}
<div className="flex items-start gap-5">
{channel.thumbnail_url ? (
<img
src={channel.thumbnail_url}
alt={channel.name}
className="w-20 h-20 rounded-full object-cover shrink-0"
/>
) : (
<div className="w-20 h-20 rounded-full bg-zinc-800 flex items-center justify-center text-3xl font-display font-bold text-zinc-400 shrink-0">
{channel.name?.[0]?.toUpperCase()}
</div>
{/* Channel header — banner with overlay, or plain if no banner */}
<div className={`-mx-4 -mt-6 relative ${channel.banner_url ? "" : "bg-zinc-900"} rounded-b-2xl overflow-hidden`}>
{channel.banner_url && (
<img src={channel.banner_url} alt="" className="w-full h-36 sm:h-52 object-cover" />
)}
<div className="flex-1 min-w-0">
<h1 className="font-display font-bold text-2xl text-zinc-100">{channel.name}</h1>
<p className="text-sm text-zinc-500 mt-1">
{[
formatSubs(channel.subscriber_count) && `${formatSubs(channel.subscriber_count)} subscribers`,
`${channel.video_count} videos indexed`,
].filter(Boolean).join(" · ")}
</p>
{channel.description && (
<p className="text-sm text-zinc-400 mt-2 line-clamp-2">{channel.description}</p>
{/* Gradient overlay */}
<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 */}
<div className={`${channel.banner_url ? "absolute bottom-0 inset-x-0" : ""} px-4 pb-4 pt-3 flex items-end gap-4`}>
{/* Avatar */}
{channel.thumbnail_url ? (
<img
src={channel.thumbnail_url}
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">
{channel.name?.[0]?.toUpperCase()}
</div>
)}
</div>
<div className="flex items-center gap-2 shrink-0 flex-wrap justify-end">
{/* Name + meta */}
<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>
<p className="text-xs sm:text-sm text-zinc-300 mt-0.5 drop-shadow">
{[
formatSubs(channel.subscriber_count) && `${formatSubs(channel.subscriber_count)} subscribers`,
`${channel.video_count} videos indexed`,
].filter(Boolean).join(" · ")}
</p>
</div>
{/* Action buttons */}
<div className="flex items-center gap-2 shrink-0 flex-wrap justify-end">
{dlResult != null && (
<span className="text-sm text-accent font-mono">
{dlResult === 0 ? "Already up to date" : `${dlResult} queued`}
@@ -127,7 +130,7 @@ export default function ChannelPage() {
<button
onClick={() => indexMut.mutate()}
disabled={indexMut.isPending || indexMut.isSuccess}
className="text-sm font-medium px-4 py-2 rounded-lg bg-zinc-800 text-zinc-300 hover:bg-zinc-700 transition-colors disabled:opacity-60"
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>
@@ -136,15 +139,21 @@ export default function ChannelPage() {
disabled={followMut.isPending}
className={`text-sm font-medium px-4 py-2 rounded-lg transition-colors ${
isFollowed
? "bg-zinc-700 text-zinc-300 hover:bg-zinc-600"
: "bg-zinc-800 text-zinc-300 hover:bg-zinc-700"
? "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>
{/* Description below banner */}
{channel.description && (
<p className="text-sm text-zinc-400 line-clamp-3 -mt-4">{channel.description}</p>
)}
{/* Video grid */}
{loadingVideos ? (
<div className="flex items-center justify-center py-8">