refactor(ui): mobile friendly landing

This commit is contained in:
2026-02-21 16:50:20 +01:00
parent f4f653614d
commit ed1608b8e3
2 changed files with 37 additions and 29 deletions

View File

@@ -48,7 +48,7 @@ export default async function Home() {
}
return (
<div className="p-4 md:p-6">
<div className="p-3 md:p-6">
<StreamGrid liveStreams={liveStreams} offlineStreams={offlineStreams} />
</div>
);

View File

@@ -25,7 +25,7 @@ export default function StreamGrid({ liveStreams, offlineStreams }: StreamGridPr
const [featured, ...rest] = sorted;
return (
<div className="space-y-10">
<div className="space-y-8 md:space-y-10">
{!featured && (
<div className="flex flex-col items-center gap-4 py-10 text-center">
<ConfusedDino className="h-24 w-24 opacity-70" />
@@ -45,7 +45,7 @@ export default function StreamGrid({ liveStreams, offlineStreams }: StreamGridPr
{featured && (
<section>
<SectionHeading label="Featured" />
<Link href={`/${featured.username}`} className="group block max-w-2xl">
<Link href={`/${featured.username}`} className="group block w-full md:max-w-2xl">
<div className="overflow-hidden rounded-xl border border-border bg-card shadow-sm transition-shadow duration-200 group-hover:shadow-md">
<div className="relative aspect-video overflow-hidden bg-muted">
<img
@@ -54,28 +54,32 @@ export default function StreamGrid({ liveStreams, offlineStreams }: StreamGridPr
className="h-full w-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/10 to-transparent" />
<div className="absolute bottom-3 left-3 flex items-center gap-2">
<div className="absolute bottom-2 left-2 flex items-center gap-1.5 md:bottom-3 md:left-3 md:gap-2">
<LiveBadge />
{featured.category && (
<span className="rounded-full bg-black/60 px-2.5 py-0.5 text-xs font-medium text-white backdrop-blur-sm">
<span className="rounded-full bg-black/60 px-2 py-0.5 text-[10px] font-medium text-white backdrop-blur-sm md:px-2.5 md:text-xs">
{featured.category}
</span>
)}
</div>
<div className="absolute bottom-3 right-3">
<div className="absolute bottom-2 right-2 md:bottom-3 md:right-3">
<ViewerCount count={featured.viewers} />
</div>
</div>
<div className="flex items-start gap-4 p-4">
<Avatar className="h-10 w-10 shrink-0 ring-2 ring-primary/30">
<div className="flex items-start gap-3 p-3 md:gap-4 md:p-4">
<Avatar className="h-9 w-9 shrink-0 ring-2 ring-primary/30 md:h-10 md:w-10">
<AvatarImage src={featured.channel.pfpUrl} alt={featured.channel.name} />
<AvatarFallback className="text-sm font-semibold">
{featured.channel.name.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate font-semibold leading-snug">{featured.title}</p>
<p className="mt-0.5 text-sm text-muted-foreground">{featured.channel.name}</p>
<p className="truncate text-sm font-semibold leading-snug md:text-base">
{featured.title}
</p>
<p className="mt-0.5 text-xs text-muted-foreground md:text-sm">
{featured.channel.name}
</p>
</div>
</div>
</div>
@@ -86,7 +90,7 @@ export default function StreamGrid({ liveStreams, offlineStreams }: StreamGridPr
{rest.length > 0 && (
<section>
<SectionHeading label="Live now" count={rest.length} />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<div className="grid grid-cols-2 gap-3 md:gap-4 lg:grid-cols-3 xl:grid-cols-4">
{rest.map((stream) => (
<StreamCard key={stream.id} stream={stream} />
))}
@@ -97,20 +101,20 @@ export default function StreamGrid({ liveStreams, offlineStreams }: StreamGridPr
{offlineStreams.length > 0 && (
<section>
<SectionHeading label="Offline channels" count={offlineStreams.length} />
<div className="px-8">
<div className="relative">
<Carousel opts={{ align: 'start', dragFree: true }}>
<CarouselContent>
{offlineStreams.map((stream) => (
<CarouselItem
key={stream.id}
className="basis-1/2 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 xl:basis-1/6"
className="basis-1/3 sm:basis-1/4 md:basis-1/5 lg:basis-1/6 xl:basis-1/8"
>
<OfflineCard stream={stream} />
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
<CarouselPrevious className="hidden md:flex" />
<CarouselNext className="hidden md:flex" />
</Carousel>
</div>
</section>
@@ -130,27 +134,29 @@ function StreamCard({ stream }: { stream: StreamWithChannel }) {
className="h-full w-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
<div className="absolute bottom-2 left-2">
<div className="absolute bottom-1.5 left-1.5 md:bottom-2 md:left-2">
<LiveBadge small />
</div>
<div className="absolute bottom-2 right-2">
<div className="absolute bottom-1.5 right-1.5 md:bottom-2 md:right-2">
<ViewerCount count={stream.viewers} small />
</div>
</div>
<div className="flex items-start gap-3 p-3">
<Avatar className="h-8 w-8 shrink-0 ring-1 ring-primary/20">
<div className="flex items-start gap-2 p-2 md:gap-3 md:p-3">
<Avatar className="h-7 w-7 shrink-0 ring-1 ring-primary/20 md:h-8 md:w-8">
<AvatarImage src={stream.channel.pfpUrl} alt={stream.channel.name} />
<AvatarFallback className="text-xs">
<AvatarFallback className="text-[10px]">
{stream.channel.name.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium leading-snug">{stream.title}</p>
<p className="truncate text-xs text-muted-foreground">{stream.channel.name}</p>
<p className="truncate text-xs font-medium leading-snug md:text-sm">{stream.title}</p>
<p className="truncate text-[10px] text-muted-foreground md:text-xs">
{stream.channel.name}
</p>
{stream.category && (
<Badge
variant="secondary"
className="mt-1.5 rounded-full px-2 py-0 text-[10px] font-medium"
className="mt-1 rounded-full px-1.5 py-0 text-[9px] font-medium md:mt-1.5 md:px-2 md:text-[10px]"
>
{stream.category}
</Badge>
@@ -165,17 +171,19 @@ function StreamCard({ stream }: { stream: StreamWithChannel }) {
function OfflineCard({ stream }: { stream: StreamWithChannel }) {
return (
<Link href={`/${stream.username}`} className="group block">
<div className="flex flex-col items-center gap-2 rounded-lg p-3 transition-colors duration-150 hover:bg-muted/50">
<div className="flex flex-col items-center gap-1.5 rounded-lg p-2 transition-colors duration-150 hover:bg-muted/50 md:gap-2 md:p-3">
<div className="relative">
<Avatar className="h-16 w-16 ring-2 ring-border transition-colors duration-150 group-hover:ring-border/60">
<Avatar className="h-12 w-12 ring-2 ring-border transition-colors duration-150 group-hover:ring-border/60 md:h-16 md:w-16">
<AvatarImage src={stream.channel.pfpUrl} alt={stream.channel.name} />
<AvatarFallback className="text-lg font-semibold">
<AvatarFallback className="text-base font-semibold md:text-lg">
{stream.channel.name.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<span className="absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 rounded-full border-2 border-background bg-muted-foreground/40" />
<span className="absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full border-2 border-background bg-muted-foreground/40 md:h-3.5 md:w-3.5" />
</div>
<p className="w-full truncate text-center text-xs font-medium">{stream.channel.name}</p>
<p className="w-full truncate text-center text-[10px] font-medium md:text-xs">
{stream.channel.name}
</p>
</div>
</Link>
);
@@ -206,7 +214,7 @@ function ViewerCount({ count, small }: { count: number; small?: boolean }) {
function SectionHeading({ label, count }: { label: string; count?: number }) {
return (
<div className="mb-3 flex items-center gap-2">
<h2 className="pb-0 text-base font-semibold tracking-tight">{label}</h2>
<h2 className="pb-0 text-sm font-semibold tracking-tight md:text-base">{label}</h2>
{count !== undefined && (
<span className="rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary">
{count}