UX overhaul: motion animations, command palette, warm cinema theme

Add premium "Late Night Screening Room" experience with warm indigo/amber
palette, framer-motion spring animations, Cmd+K command palette with TMDB
search, keyboard shortcuts (G H, G S, ?, W, M, 1-5), sonner toasts with
optimistic updates, skeleton loading states, stats dashboard, search
autocomplete with filter tabs, cinematic backdrop with film grain, season
progress bars, and staggered card reveal animations throughout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 15:30:35 -05:00
co-authored by Claude Opus 4.6
parent 095b64ab42
commit b6aaad243f
23 changed files with 2017 additions and 339 deletions
+80 -47
View File
@@ -7,12 +7,31 @@ import {
IconPlus,
IconX,
} from "@tabler/icons-react";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useRef, useState } from "react";
const statuses = [
{ value: "watchlist", label: "Watchlist", icon: IconBookmark },
{ value: "in_progress", label: "Watching", icon: IconPlayerPlay },
{ value: "completed", label: "Completed", icon: IconCheck },
{
value: "watchlist",
label: "Watchlist",
icon: IconBookmark,
colorClass:
"border-status-watchlist/30 bg-status-watchlist/10 text-status-watchlist hover:bg-status-watchlist/15",
},
{
value: "in_progress",
label: "Watching",
icon: IconPlayerPlay,
colorClass:
"border-status-watching/30 bg-status-watching/10 text-status-watching hover:bg-status-watching/15",
},
{
value: "completed",
label: "Completed",
icon: IconCheck,
colorClass:
"border-status-completed/30 bg-status-completed/10 text-status-completed hover:bg-status-completed/15",
},
] as const;
interface StatusButtonProps {
@@ -42,9 +61,9 @@ export function StatusButton({ currentStatus, onChange }: StatusButtonProps) {
<button
type="button"
onClick={() => setOpen(!open)}
className={`inline-flex h-9 items-center gap-2 rounded-lg border px-4 text-sm font-medium transition-all ${
className={`inline-flex h-9 items-center gap-2 rounded-lg border px-4 text-sm font-medium transition-all active:scale-[0.97] ${
current
? "border-primary/30 bg-primary/10 text-primary hover:bg-primary/15"
? current.colorClass
: "border-border/50 hover:border-primary/30 hover:bg-primary/5"
}`}
>
@@ -52,48 +71,62 @@ export function StatusButton({ currentStatus, onChange }: StatusButtonProps) {
{current ? current.label : "Add to List"}
</button>
{open && (
<div className="absolute left-0 top-full z-20 mt-1.5 w-44 overflow-hidden rounded-lg border border-border/50 bg-popover/95 p-1 shadow-xl shadow-black/30 backdrop-blur-xl">
{statuses.map((s) => {
const Icon = s.icon;
return (
<button
key={s.value}
type="button"
onClick={() => {
onChange(s.value === currentStatus ? null : s.value);
setOpen(false);
}}
className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors hover:bg-accent ${
s.value === currentStatus ? "text-primary" : "text-foreground"
}`}
>
<Icon size={15} />
{s.label}
{s.value === currentStatus && (
<IconCheck size={13} className="ml-auto" />
)}
</button>
);
})}
{currentStatus && (
<>
<div className="my-1 border-t border-border/50" />
<button
type="button"
onClick={() => {
onChange(null);
setOpen(false);
}}
className="flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm text-destructive transition-colors hover:bg-accent"
>
<IconX size={15} />
Remove
</button>
</>
)}
</div>
)}
<AnimatePresence>
{open && (
<motion.div
initial={{ opacity: 0, scale: 0.95, y: -4 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: -4 }}
transition={{
type: "spring" as const,
stiffness: 500,
damping: 30,
}}
className="absolute left-0 top-full z-20 mt-1.5 w-44 overflow-hidden rounded-xl border border-border/50 bg-popover/95 p-1 shadow-xl shadow-black/30 backdrop-blur-xl"
>
{statuses.map((s) => {
const Icon = s.icon;
return (
<button
key={s.value}
type="button"
onClick={() => {
onChange(s.value === currentStatus ? null : s.value);
setOpen(false);
}}
className={`flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors hover:bg-accent ${
s.value === currentStatus
? "text-primary"
: "text-foreground"
}`}
>
<Icon size={15} />
{s.label}
{s.value === currentStatus && (
<IconCheck size={13} className="ml-auto" />
)}
</button>
);
})}
{currentStatus && (
<>
<div className="my-1 border-t border-border/50" />
<button
type="button"
onClick={() => {
onChange(null);
setOpen(false);
}}
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-destructive transition-colors hover:bg-accent"
>
<IconX size={15} />
Remove
</button>
</>
)}
</motion.div>
)}
</AnimatePresence>
</div>
);
}