From effbae5fa3d1d1f08ff0e9b1576535468c954fbd Mon Sep 17 00:00:00 2001 From: Kai Chen Date: Fri, 10 Jul 2026 00:52:53 -0700 Subject: [PATCH] feat: grayscale photos that colorize on hover Photos and cover art render black & white to match the monochrome theme, regaining color on hover (0.2s filter transition): - Home avatar colorizes on direct hover - Track album art (Listening card, last month's tops, playlist tracks) colorizes on row hover, replacing the old partial desaturation - Music for Life playlist covers colorize when their card/link is hovered Touch devices simply show the black & white version. Co-authored-by: Claude --- app/components/avatar-card.tsx | 2 +- app/components/listening-track-row.tsx | 6 ++---- app/music-for-life/[id]/page.tsx | 2 +- app/music-for-life/page.tsx | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/components/avatar-card.tsx b/app/components/avatar-card.tsx index 1f67a76..5b2c0e4 100644 --- a/app/components/avatar-card.tsx +++ b/app/components/avatar-card.tsx @@ -11,7 +11,7 @@ export default function AvatarCard({ src, alt, className = "" }: AvatarCardProps fill priority sizes="(min-width: 768px) 425px, 320px" - className="object-cover object-top" + className="object-cover object-top grayscale hover:grayscale-0 transition-[filter] duration-200" /> ); diff --git a/app/components/listening-track-row.tsx b/app/components/listening-track-row.tsx index 2c1f029..7430856 100644 --- a/app/components/listening-track-row.tsx +++ b/app/components/listening-track-row.tsx @@ -47,10 +47,8 @@ export default function ListeningTrackRow({ alt={`${title} album art`} width={artSize} height={artSize} - className={`${artClass} rounded-[3px] object-cover transition-[filter,box-shadow] duration-200 ${HOVER_RING} ${ - live - ? "listening-art-live" - : "grayscale-[18%] contrast-[0.98] opacity-[0.88] saturate-[0.92] dark:grayscale-[12%]" + className={`${artClass} rounded-[3px] object-cover grayscale group-hover/track:grayscale-0 transition-[filter,box-shadow] duration-200 ${HOVER_RING} ${ + live ? "listening-art-live" : "" }`} /> ) : ( diff --git a/app/music-for-life/[id]/page.tsx b/app/music-for-life/[id]/page.tsx index f24c1dc..30afa80 100644 --- a/app/music-for-life/[id]/page.tsx +++ b/app/music-for-life/[id]/page.tsx @@ -64,7 +64,7 @@ export default async function PlaylistDetailPage({ params }: PageProps) { alt={`${playlist.name} playlist cover`} width={112} height={112} - className="size-20 md:size-28 rounded-[4px] object-cover" + className="size-20 md:size-28 rounded-[4px] object-cover grayscale group-hover:grayscale-0 transition-[filter] duration-200" /> ) : (
diff --git a/app/music-for-life/page.tsx b/app/music-for-life/page.tsx index 310cd34..46e6b29 100644 --- a/app/music-for-life/page.tsx +++ b/app/music-for-life/page.tsx @@ -75,7 +75,7 @@ export default async function Playlists() { alt={`${playlist.name} playlist cover`} width={112} height={112} - className="size-20 md:size-28 rounded-[4px] object-cover" + className="size-20 md:size-28 rounded-[4px] object-cover grayscale group-hover:grayscale-0 transition-[filter] duration-200" /> ) : (