polish(channel): compact header — stats inline, slimmer banner

- Move the channel stats (subscribers / videos / views / joined) onto one meta line beside the
  handle under the name, dropping the separate stats row.
- Cap the banner at 150px tall so it no longer dominates the page; the wide YouTube-style crop
  is kept. Net: the video grid starts much higher.
This commit is contained in:
npeter83 2026-06-30 04:43:37 +02:00
parent c59b53a51b
commit 50a7774588

View file

@ -123,6 +123,14 @@ export default function ChannelPage({
const joined = ch?.published_at
? new Date(ch.published_at).toLocaleDateString(i18n.language, { year: "numeric", month: "short" })
: null;
// Handle + stats on one compact meta line under the name (saves the separate stats row).
const metaParts = [
ch?.handle ? `@${ch.handle.replace(/^@/, "")}` : null,
ch?.subscriber_count != null ? t("channel.subscribers", { formatted: formatViews(ch.subscriber_count) }) : null,
ch?.video_count != null ? t("channel.videoCount", { count: ch.video_count }) : null,
ch?.total_view_count != null ? t("channel.totalViews", { formatted: formatViews(ch.total_view_count) }) : null,
joined ? t("channel.joined", { date: joined }) : null,
].filter(Boolean) as string[];
const tabClass = (active: boolean) =>
active
? "pb-2 text-sm font-medium border-b-2 border-fg text-fg"
@ -136,7 +144,10 @@ export default function ChannelPage({
// Match YouTube's banner: the stored bannerExternalUrl is the full 16:9 template at a
// low default size, so request a crisp wide version (=w1707) and object-cover the
// desktop "safe area" — the centre 2560×423 (~6:1) band — at full width.
<div className="w-full overflow-hidden bg-surface" style={{ aspectRatio: "2560 / 423" }}>
<div
className="w-full overflow-hidden bg-surface"
style={{ aspectRatio: "2560 / 423", maxHeight: "150px" }}
>
<img
src={`${ch.banner_url}=w1707`}
alt=""
@ -173,9 +184,14 @@ export default function ChannelPage({
</span>
)}
</div>
{ch?.handle && (
<div className="text-sm text-muted truncate">@{ch.handle.replace(/^@/, "")}</div>
)}
<div className="text-sm text-muted mt-0.5 flex flex-wrap gap-x-1.5 gap-y-0.5">
{metaParts.map((part, i) => (
<span key={i}>
{i > 0 && <span className="mr-1.5">·</span>}
{part}
</span>
))}
</div>
</div>
<div className="flex items-center gap-2 shrink-0 pb-1">
<a
@ -212,17 +228,6 @@ export default function ChannelPage({
</div>
</div>
{/* Stats */}
<div className="flex flex-wrap items-center gap-x-2.5 gap-y-1 text-sm text-muted mt-3">
{ch?.subscriber_count != null && (
<span>{t("channel.subscribers", { formatted: formatViews(ch.subscriber_count) })}</span>
)}
{ch?.video_count != null && <span>· {t("channel.videoCount", { count: ch.video_count })}</span>}
{ch?.total_view_count != null && (
<span>· {t("channel.totalViews", { formatted: formatViews(ch.total_view_count) })}</span>
)}
{joined && <span>· {t("channel.joined", { date: joined })}</span>}
</div>
{exploring && (
<div className="flex items-center gap-2 text-xs text-muted mt-2">
<Loader2 className="w-3.5 h-3.5 animate-spin" />
@ -231,7 +236,7 @@ export default function ChannelPage({
)}
{/* Tabs */}
<div className="flex items-center gap-4 mt-4 border-b border-border">
<div className="flex items-center gap-4 mt-3 border-b border-border">
<button onClick={() => setTab("videos")} className={tabClass(tab === "videos")}>
{t("channel.tabVideos")}
</button>