diff --git a/app/(pages)/titles/[id]/page.tsx b/app/(pages)/titles/[id]/page.tsx index d8ba672..33e6ec9 100644 --- a/app/(pages)/titles/[id]/page.tsx +++ b/app/(pages)/titles/[id]/page.tsx @@ -10,6 +10,7 @@ import { titles } from "@/lib/db/schema"; import { getTitleWithChildren, importTitle } from "@/lib/services/metadata"; import { getUserTitleInfo } from "@/lib/services/tracking"; import { tmdbImageUrl } from "@/lib/tmdb/image"; +import { getTitleThemeStyle } from "@/lib/utils/title-theme"; import { TitleActions } from "./_components/title-actions"; import { TitleAvailability } from "./_components/title-availability"; import { TitleHero } from "./_components/title-hero"; @@ -72,8 +73,10 @@ export default async function TitleDetailPage({ const { title, seasons, availability } = result; + const themeStyle = getTitleThemeStyle(title.colorPalette); + return ( -
+
{}); + // Lazy color extraction — await so the palette is available for theming + let palette = parseColorPalette(title.colorPalette); + if (!palette && title.posterPath) { + palette = + (await extractAndStoreColors(title.id, title.posterPath).catch( + () => null, + )) ?? null; } const resolvedTitle: ResolvedTitle = { @@ -586,7 +590,7 @@ export async function getTitleWithChildren(id: string): Promise<{ voteAverage: title.voteAverage, voteCount: title.voteCount, status: title.status, - colorPalette: parseColorPalette(title.colorPalette), + colorPalette: palette, }; return { title: resolvedTitle, seasons: titleSeasons, availability }; diff --git a/lib/utils/title-theme.ts b/lib/utils/title-theme.ts new file mode 100644 index 0000000..f2382e7 --- /dev/null +++ b/lib/utils/title-theme.ts @@ -0,0 +1,30 @@ +import type { ColorPalette } from "@/lib/types/title"; + +function hexToRelativeLuminance(hex: string): number { + const r = Number.parseInt(hex.slice(1, 3), 16) / 255; + const g = Number.parseInt(hex.slice(3, 5), 16) / 255; + const b = Number.parseInt(hex.slice(5, 7), 16) / 255; + const toLinear = (c: number) => + c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; + return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b); +} + +export function getTitleThemeStyle( + palette: ColorPalette | null, +): React.CSSProperties { + const color = palette?.vibrant; + if (!color) return {}; + + const luminance = hexToRelativeLuminance(color); + const foreground = + luminance > 0.3 + ? "oklch(0.13 0.006 55)" // dark (current --primary-foreground) + : "oklch(0.93 0.015 80)"; // light (current --foreground) + + return { + "--primary": color, + "--ring": color, + "--primary-foreground": foreground, + "--status-watching": color, + } as React.CSSProperties; +}