Improve title detail: episode stills, descriptions, provider tooltips, stronger gradient

- Episode rows now show still images (thumbnail, hidden on mobile)
- Episode descriptions shown as 2-line clamp below title on desktop
- Watched episodes dimmed with opacity for clear visual differentiation
- Provider logos get CSS hover tooltips showing the service name
- Backdrop gradient strengthened with extra overlay layer for text
  readability on bright/colorful backdrop images
- Added stillPath to Episode interface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 11:36:15 -05:00
co-authored by Claude Opus 4.6
parent 30d074e2ca
commit 989b5e4cd0
+36 -14
View File
@@ -32,6 +32,7 @@ interface Episode {
episodeNumber: number;
name: string | null;
overview: string | null;
stillPath: string | null;
airDate: string | null;
runtimeMinutes: number | null;
}
@@ -462,10 +463,11 @@ export default function TitleDetailPage() {
className="object-cover"
priority
/>
{/* Three-layer gradient */}
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/60 to-background/20" />
<div className="absolute inset-0 bg-gradient-to-r from-background/80 to-transparent" />
<div className="absolute inset-0 bg-gradient-to-b from-background/40 via-transparent to-transparent" />
{/* Multi-layer gradient for text readability on any backdrop */}
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/70 to-background/30" />
<div className="absolute inset-0 bg-gradient-to-r from-background/90 via-background/40 to-transparent" />
<div className="absolute inset-0 bg-gradient-to-b from-background/50 via-transparent to-transparent" />
<div className="absolute inset-0 bg-background/15" />
{/* Film grain overlay */}
<div
className="pointer-events-none absolute inset-0 opacity-[0.03]"
@@ -689,10 +691,13 @@ export default function TitleDetailPage() {
const isWatched = title.episodeWatches?.includes(
ep.id,
);
const stillUrl = ep.stillPath
? `https://image.tmdb.org/t/p/w300${ep.stillPath}`
: null;
return (
<div
key={ep.id}
className="flex items-center gap-3 border-b border-border/30 px-4 py-3 last:border-b-0"
className={`flex gap-3 border-b border-border/30 px-4 py-3 last:border-b-0 transition-colors ${isWatched ? "opacity-60" : ""}`}
>
<button
type="button"
@@ -705,7 +710,7 @@ export default function TitleDetailPage() {
)
}
disabled={watchingEp === ep.id}
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-2 transition-all ${
className={`mt-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-2 transition-all ${
isWatched
? "border-primary bg-primary text-primary-foreground"
: "border-muted-foreground/40 bg-muted-foreground/5 hover:border-primary/70 hover:bg-primary/10"
@@ -725,8 +730,19 @@ export default function TitleDetailPage() {
</motion.div>
)}
</button>
{stillUrl && (
<div className="hidden h-14 w-24 shrink-0 overflow-hidden rounded-md bg-muted sm:block">
<Image
src={stillUrl}
alt={ep.name ?? ""}
width={300}
height={169}
className="h-full w-full object-cover"
/>
</div>
)}
<div className="min-w-0 flex-1">
<p className="truncate text-sm">
<p className="text-sm">
<span className="font-mono text-xs text-muted-foreground">
E{String(ep.episodeNumber).padStart(2, "0")}
</span>{" "}
@@ -734,13 +750,17 @@ export default function TitleDetailPage() {
{ep.name ?? "Untitled"}
</span>
</p>
{ep.airDate && (
<p className="text-xs text-muted-foreground">
{ep.airDate}
{ep.airDate ?? ""}
{ep.airDate && ep.runtimeMinutes ? " · " : ""}
{ep.runtimeMinutes
? ` · ${ep.runtimeMinutes}m`
? `${ep.runtimeMinutes}m`
: ""}
</p>
{ep.overview && (
<p className="mt-1 hidden line-clamp-2 text-xs leading-relaxed text-muted-foreground/70 sm:block">
{ep.overview}
</p>
)}
</div>
</div>
@@ -796,10 +816,8 @@ function ProviderBadge({
const logoUrl = logoPath ? `https://image.tmdb.org/t/p/w92${logoPath}` : null;
return (
<div
className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-lg border border-border/30 bg-card transition-transform hover:scale-105"
title={name}
>
<div className="group relative">
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-lg border border-border/30 bg-card transition-transform hover:scale-105">
{logoUrl ? (
<Image
src={logoUrl}
@@ -814,5 +832,9 @@ function ProviderBadge({
</span>
)}
</div>
<div className="pointer-events-none absolute -top-8 left-1/2 z-20 -translate-x-1/2 whitespace-nowrap rounded-md bg-popover px-2 py-1 text-[10px] font-medium text-popover-foreground opacity-0 shadow-md transition-opacity group-hover:opacity-100">
{name}
</div>
</div>
);
}