Redesign title cards with proper padding and icon-based metadata

Replace text-based MOVIE/TV badges with Tabler icons (IconMovie, IconDeviceTv),
add proper padding to the metadata section below posters, use IconStarFilled for
ratings, and add subtle image hover zoom. Update skeleton and search autocomplete
to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 12:03:11 -05:00
co-authored by Claude Opus 4.6
parent 17c6b23e23
commit 906e6f76ea
3 changed files with 43 additions and 22 deletions
+20 -6
View File
@@ -1,6 +1,11 @@
"use client";
import { IconSearch } from "@tabler/icons-react";
import {
IconDeviceTv,
IconMovie,
IconSearch,
IconStarFilled,
} from "@tabler/icons-react";
import { Command as CommandPrimitive } from "cmdk";
import Image from "next/image";
import { useRouter } from "next/navigation";
@@ -163,17 +168,26 @@ export function SearchAutocomplete({
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<p className="truncate text-sm font-medium">{r.title}</p>
<span className="shrink-0 rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-primary">
{r.type}
</span>
{r.type === "movie" ? (
<IconMovie
size={14}
className="shrink-0 text-primary/60"
/>
) : (
<IconDeviceTv
size={14}
className="shrink-0 text-primary/60"
/>
)}
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
{r.releaseDate && (
<span>{r.releaseDate.slice(0, 4)}</span>
)}
{r.voteAverage > 0 && (
<span className="text-primary">
{r.voteAverage.toFixed(1)}
<span className="flex items-center gap-0.5 text-primary/80">
<IconStarFilled size={11} />
{r.voteAverage.toFixed(1)}
</span>
)}
</div>
+6 -4
View File
@@ -2,10 +2,12 @@ import { Skeleton } from "@/components/ui/skeleton";
export function TitleCardSkeleton() {
return (
<div className="space-y-2">
<Skeleton className="aspect-[2/3] w-full rounded-xl" />
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-1/2" />
<div className="overflow-hidden rounded-xl bg-card ring-1 ring-white/[0.06]">
<Skeleton className="aspect-[2/3] w-full rounded-none" />
<div className="px-3 pb-3 pt-2.5 space-y-2">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-1/2" />
</div>
</div>
);
}
+17 -12
View File
@@ -1,5 +1,6 @@
"use client";
import { IconDeviceTv, IconMovie, IconStarFilled } from "@tabler/icons-react";
import { motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
@@ -27,21 +28,22 @@ export function TitleCard({
onImport,
}: TitleCardProps) {
const year = releaseDate?.slice(0, 4);
const TypeIcon = type === "movie" ? IconMovie : IconDeviceTv;
const content = (
<motion.div
className="group relative overflow-hidden rounded-xl ring-1 ring-foreground/5 transition-shadow hover:ring-primary/20 hover:shadow-lg hover:shadow-black/25"
className="group relative overflow-hidden rounded-xl bg-card ring-1 ring-white/[0.06] transition-shadow hover:ring-primary/25 hover:shadow-lg hover:shadow-primary/5"
whileHover={{ scale: 1.02 }}
transition={{ type: "spring" as const, stiffness: 400, damping: 25 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
>
<div className="aspect-[2/3] overflow-hidden rounded-xl bg-card">
<div className="aspect-[2/3] overflow-hidden bg-card">
{posterPath ? (
<Image
src={posterPath}
alt={title}
width={300}
height={450}
className="h-full w-full object-cover"
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
/>
) : (
<div className="relative flex h-full items-center justify-center overflow-hidden bg-gradient-to-br from-card via-secondary to-muted">
@@ -59,18 +61,21 @@ export function TitleCard({
</div>
</div>
)}
{/* Hover gradient overlay with metadata */}
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 transition-opacity duration-200 group-hover:opacity-100" />
{/* Hover gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 transition-opacity duration-200 group-hover:opacity-100" />
</div>
<div className="mt-2 space-y-0.5">
{/* Metadata */}
<div className="px-3 pb-3 pt-2.5">
<p className="line-clamp-1 text-sm font-medium leading-snug">{title}</p>
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<span className="rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-primary">
{type}
</span>
<div className="mt-1.5 flex items-center gap-2 text-xs text-muted-foreground">
<TypeIcon size={14} className="shrink-0 text-primary/60" />
{year && <span>{year}</span>}
{voteAverage != null && voteAverage > 0 && (
<span className="text-primary"> {voteAverage.toFixed(1)}</span>
<span className="ml-auto flex items-center gap-0.5 text-primary/80">
<IconStarFilled size={11} />
{voteAverage.toFixed(1)}
</span>
)}
</div>
</div>