Minimal flat redesign: white accent, remove card backgrounds

Replace yellow accent (#f5a623) with white (#ffffff) across the entire
app. Flatten VideoCard grid variant by removing zinc-900 card background
so content sits directly on the page. Simplify active states, badges,
progress bars, and hover effects throughout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 21:50:44 +02:00
parent 366a2ff183
commit 52279752e4
10 changed files with 34 additions and 35 deletions

View File

@@ -80,7 +80,7 @@ export default function ChannelCard({ channel }) {
className={`text-xs font-medium px-4 py-2 rounded-lg transition-colors ${ className={`text-xs font-medium px-4 py-2 rounded-lg transition-colors ${
isFollowed || followMut.isSuccess isFollowed || followMut.isSuccess
? "bg-zinc-700 text-zinc-300 hover:bg-zinc-600" ? "bg-zinc-700 text-zinc-300 hover:bg-zinc-600"
: "bg-accent text-black hover:bg-yellow-300" : "bg-accent text-black hover:bg-zinc-100"
}`} }`}
> >
{isFollowed || followMut.isSuccess ? "Following" : "Follow"} {isFollowed || followMut.isSuccess ? "Following" : "Follow"}

View File

@@ -41,7 +41,7 @@ function BottomNav({ newCount }) {
end={tab.end} end={tab.end}
className={({ isActive }) => className={({ isActive }) =>
`relative flex-1 flex flex-col items-center justify-center gap-0.5 transition-colors outline-none ${ `relative flex-1 flex flex-col items-center justify-center gap-0.5 transition-colors outline-none ${
isActive ? "text-accent" : "text-zinc-500" isActive ? "text-zinc-100" : "text-zinc-500"
}` }`
} }
> >
@@ -49,13 +49,13 @@ function BottomNav({ newCount }) {
<> <>
<div className="relative"> <div className="relative">
{isActive && ( {isActive && (
<span className="absolute -inset-2 rounded-xl bg-accent/10" /> <span className="absolute -inset-2 rounded-xl bg-white/10" />
)} )}
<svg className="w-[18px] h-[18px] relative" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-[18px] h-[18px] relative" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{tab.icon} {tab.icon}
</svg> </svg>
{tab.badge > 0 && ( {tab.badge > 0 && (
<span className="absolute -top-1 -right-1.5 min-w-[13px] h-3 bg-accent text-black text-[8px] font-bold rounded-full flex items-center justify-center px-0.5 leading-none"> <span className="absolute -top-1 -right-1.5 min-w-[13px] h-3 bg-zinc-200 text-zinc-900 text-[8px] font-bold rounded-full flex items-center justify-center px-0.5 leading-none">
{tab.badge > 99 ? "99+" : tab.badge} {tab.badge > 99 ? "99+" : tab.badge}
</span> </span>
)} )}
@@ -98,7 +98,7 @@ function DownloadIndicator() {
className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700 transition-colors text-xs text-zinc-300 shrink-0" className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700 transition-colors text-xs text-zinc-300 shrink-0"
title={`${active.length} download${active.length > 1 ? "s" : ""} in progress`} title={`${active.length} download${active.length > 1 ? "s" : ""} in progress`}
> >
<svg className="w-3 h-3 animate-spin text-accent shrink-0" fill="none" viewBox="0 0 24 24"> <svg className="w-3 h-3 animate-spin text-zinc-400 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-20" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" /> <circle className="opacity-20" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
<path className="opacity-80" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" /> <path className="opacity-80" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg> </svg>
@@ -125,7 +125,7 @@ function NavItem({ to, children, badge }) {
> >
{children} {children}
{badge > 0 && ( {badge > 0 && (
<span className="absolute -top-1 -right-1 min-w-[16px] h-4 bg-accent text-black text-[10px] font-bold rounded-full flex items-center justify-center px-1 leading-none"> <span className="absolute -top-1 -right-1 min-w-[16px] h-4 bg-zinc-200 text-zinc-900 text-[10px] font-bold rounded-full flex items-center justify-center px-1 leading-none">
{badge > 99 ? "99+" : badge} {badge > 99 ? "99+" : badge}
</span> </span>
)} )}
@@ -147,7 +147,7 @@ function DropItem({ to, children, badge }) {
> >
<span>{children}</span> <span>{children}</span>
{badge > 0 && ( {badge > 0 && (
<span className="min-w-[18px] h-[18px] bg-accent text-black text-[10px] font-bold rounded-full flex items-center justify-center px-1 leading-none shrink-0"> <span className="min-w-[18px] h-[18px] bg-zinc-200 text-zinc-900 text-[10px] font-bold rounded-full flex items-center justify-center px-1 leading-none shrink-0">
{badge > 99 ? "99+" : badge} {badge > 99 ? "99+" : badge}
</span> </span>
)} )}
@@ -214,7 +214,7 @@ export default function Layout() {
{/* Logo */} {/* Logo */}
<button <button
onClick={() => navigate("/")} onClick={() => navigate("/")}
className="font-display font-bold text-base sm:text-lg text-accent shrink-0" className="font-display font-bold text-base sm:text-lg text-zinc-100 shrink-0 tracking-tight"
> >
YT YT
</button> </button>

View File

@@ -42,8 +42,8 @@ function IconBtn({ onClick, title, active, pending, children }) {
onClick={(e) => { e.stopPropagation(); onClick(e); }} onClick={(e) => { e.stopPropagation(); onClick(e); }}
title={title} title={title}
className={clsx( className={clsx(
"flex items-center justify-center w-7 h-7 rounded-full transition-all duration-150", "flex items-center justify-center w-7 h-7 rounded-md transition-all duration-150",
active ? "text-accent" : "text-zinc-600 hover:text-zinc-200", active ? "text-zinc-100" : "text-zinc-600 hover:text-zinc-300",
pending && "opacity-60 cursor-default", pending && "opacity-60 cursor-default",
)} )}
> >
@@ -130,13 +130,13 @@ function ThumbnailBlock({ video, isWatched, duration, calmMode, onDismiss, class
)} )}
{video.download_resolution && ( {video.download_resolution && (
<span className="absolute bottom-1.5 left-1.5 text-[10px] font-medium px-1.5 py-0.5 rounded font-mono text-accent bg-black/75"> <span className="absolute bottom-1.5 left-1.5 text-[10px] font-medium px-1.5 py-0.5 rounded font-mono text-white/80 bg-black/75">
{video.download_resolution} {video.download_resolution}
</span> </span>
)} )}
{isWatched && ( {isWatched && (
<span className="absolute top-2 left-2 w-2 h-2 rounded-full bg-accent shadow-[0_0_6px_rgba(var(--color-accent-rgb),0.8)]" /> <span className="absolute top-2 left-2 w-2 h-2 rounded-full bg-white/60" />
)} )}
<div className="absolute inset-0 bg-black/25 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center"> <div className="absolute inset-0 bg-black/25 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
@@ -148,9 +148,9 @@ function ThumbnailBlock({ video, isWatched, duration, calmMode, onDismiss, class
</div> </div>
{video.watch_progress_seconds > 0 && video.duration_seconds > 0 && ( {video.watch_progress_seconds > 0 && video.duration_seconds > 0 && (
<div className="absolute bottom-0 inset-x-0 h-[3px] bg-white/10"> <div className="absolute bottom-0 inset-x-0 h-[2px] bg-white/10">
<div <div
className="h-full bg-accent" className="h-full bg-white/70"
style={{ width: `${Math.min(video.watch_progress_seconds / video.duration_seconds, 1) * 100}%` }} style={{ width: `${Math.min(video.watch_progress_seconds / video.duration_seconds, 1) * 100}%` }}
/> />
</div> </div>
@@ -259,7 +259,7 @@ export default function VideoCard({ video, size = "md", onDismiss, variant = "gr
return ( return (
<div <div
onClick={() => navigate(`/watch/${video.youtube_video_id}`)} onClick={() => navigate(`/watch/${video.youtube_video_id}`)}
className="group flex gap-3 sm:gap-4 px-2 sm:px-3 py-2 sm:py-3 rounded-xl cursor-pointer hover:bg-zinc-800/50 transition-colors duration-150" className="group flex gap-3 sm:gap-4 px-2 sm:px-3 py-2 sm:py-3 rounded-lg cursor-pointer hover:bg-zinc-900/70 transition-colors duration-150"
> >
{/* Thumbnail — compact on mobile, wide on desktop */} {/* Thumbnail — compact on mobile, wide on desktop */}
<ThumbnailBlock <ThumbnailBlock
@@ -332,8 +332,7 @@ export default function VideoCard({ video, size = "md", onDismiss, variant = "gr
<div <div
onClick={() => navigate(`/watch/${video.youtube_video_id}`)} onClick={() => navigate(`/watch/${video.youtube_video_id}`)}
className={clsx( className={clsx(
"group relative flex flex-col cursor-pointer rounded-2xl", "group relative flex flex-col cursor-pointer",
"bg-zinc-900 hover:bg-zinc-800/80 transition-colors duration-150",
size === "sm" && "text-xs", size === "sm" && "text-xs",
)} )}
> >
@@ -343,10 +342,10 @@ export default function VideoCard({ video, size = "md", onDismiss, variant = "gr
duration={duration} duration={duration}
calmMode={calmMode} calmMode={calmMode}
onDismiss={() => dismissMut.mutate()} onDismiss={() => dismissMut.mutate()}
className="aspect-video rounded-t-2xl overflow-hidden" className="aspect-video rounded-lg overflow-hidden"
/> />
<div className="flex gap-2 sm:gap-2.5 p-2 sm:p-3 flex-1"> <div className="flex gap-2 sm:gap-2.5 pt-2 sm:pt-2.5 pb-1 flex-1">
{/* Channel avatar */} {/* Channel avatar */}
<div <div
className="shrink-0 mt-0.5" className="shrink-0 mt-0.5"
@@ -356,10 +355,10 @@ export default function VideoCard({ video, size = "md", onDismiss, variant = "gr
<img <img
src={avatarUrl} src={avatarUrl}
alt="" alt=""
className="w-7 h-7 sm:w-8 sm:h-8 rounded-full object-cover hover:ring-2 hover:ring-accent/50 transition-all" className="w-7 h-7 sm:w-8 sm:h-8 rounded-full object-cover hover:opacity-80 transition-opacity"
/> />
) : ( ) : (
<div className="w-7 h-7 sm:w-8 sm:h-8 rounded-full bg-zinc-700 flex items-center justify-center text-[11px] font-bold text-zinc-400 hover:ring-2 hover:ring-accent/50 transition-all"> <div className="w-7 h-7 sm:w-8 sm:h-8 rounded-full bg-zinc-800 flex items-center justify-center text-[11px] font-bold text-zinc-400 hover:opacity-80 transition-opacity">
{avatarLetter} {avatarLetter}
</div> </div>
)} )}

View File

@@ -112,7 +112,7 @@ export default function ChannelPage() {
{isFollowed ? "Following" : "Follow"} {isFollowed ? "Following" : "Follow"}
</button> </button>
<button onClick={() => dlMut.mutate()} disabled={dlMut.isPending} <button onClick={() => dlMut.mutate()} disabled={dlMut.isPending}
className="text-sm font-medium px-4 py-1.5 rounded-lg bg-accent text-black hover:bg-yellow-300 transition-colors disabled:opacity-60"> className="text-sm font-medium px-4 py-1.5 rounded-lg bg-accent text-black hover:bg-zinc-100 transition-colors disabled:opacity-60">
{dlMut.isPending ? "Queuing…" : "Download all"} {dlMut.isPending ? "Queuing…" : "Download all"}
</button> </button>
<button onClick={() => indexMut.mutate()} disabled={indexMut.isPending || indexMut.isSuccess} <button onClick={() => indexMut.mutate()} disabled={indexMut.isPending || indexMut.isSuccess}
@@ -130,7 +130,7 @@ export default function ChannelPage() {
{isFollowed ? "Following ✓" : "Follow"} {isFollowed ? "Following ✓" : "Follow"}
</button> </button>
<button onClick={() => dlMut.mutate()} disabled={dlMut.isPending} <button onClick={() => dlMut.mutate()} disabled={dlMut.isPending}
className="flex-1 text-sm font-medium py-2 rounded-lg bg-accent text-black hover:bg-yellow-300 transition-colors disabled:opacity-60"> className="flex-1 text-sm font-medium py-2 rounded-lg bg-accent text-black hover:bg-zinc-100 transition-colors disabled:opacity-60">
{dlMut.isPending ? "Queuing…" : "Download all"} {dlMut.isPending ? "Queuing…" : "Download all"}
</button> </button>
<button onClick={() => indexMut.mutate()} disabled={indexMut.isPending || indexMut.isSuccess} <button onClick={() => indexMut.mutate()} disabled={indexMut.isPending || indexMut.isSuccess}

View File

@@ -160,7 +160,7 @@ function ChannelCard({ item }) {
<button <button
onClick={() => followMut.mutate()} onClick={() => followMut.mutate()}
disabled={busy} disabled={busy}
className="w-full py-1.5 rounded-lg bg-accent text-black text-xs font-semibold hover:bg-yellow-300 transition-colors disabled:opacity-50" className="w-full py-1.5 rounded-lg bg-accent text-black text-xs font-semibold hover:bg-zinc-100 transition-colors disabled:opacity-50"
> >
Follow Follow
</button> </button>
@@ -292,7 +292,7 @@ export default function DiscoveryPage() {
<button <button
onClick={() => refreshMut.mutate()} onClick={() => refreshMut.mutate()}
disabled={refreshMut.isPending} disabled={refreshMut.isPending}
className="mt-2 px-6 py-2.5 rounded-xl bg-accent text-black font-semibold text-sm hover:bg-yellow-300 transition-colors disabled:opacity-60" className="mt-2 px-6 py-2.5 rounded-xl bg-accent text-black font-semibold text-sm hover:bg-zinc-100 transition-colors disabled:opacity-60"
> >
{refreshMut.isPending ? "Searching…" : "Find channels"} {refreshMut.isPending ? "Searching…" : "Find channels"}
</button> </button>

View File

@@ -765,7 +765,7 @@ export default function Following() {
<button <button
onClick={() => dlAllMut.mutate()} onClick={() => dlAllMut.mutate()}
disabled={dlAllMut.isPending} disabled={dlAllMut.isPending}
className="flex items-center gap-2 px-4 py-2 rounded-xl bg-accent text-black text-sm font-semibold hover:bg-yellow-300 transition-colors disabled:opacity-60" className="flex items-center gap-2 px-4 py-2 rounded-xl bg-accent text-black text-sm font-semibold hover:bg-zinc-100 transition-colors disabled:opacity-60"
> >
{dlAllMut.isPending ? <><Spinner /> Queuing</> : "Download all new"} {dlAllMut.isPending ? <><Spinner /> Queuing</> : "Download all new"}
</button> </button>

View File

@@ -153,7 +153,7 @@ export default function Home() {
> >
{m.label} {m.label}
{m.value === "inbox" && inboxCount > 0 && ( {m.value === "inbox" && inboxCount > 0 && (
<span className="absolute -top-1 -right-1 min-w-[13px] h-3 bg-accent text-black text-[8px] font-bold rounded-full flex items-center justify-center px-0.5 leading-none"> <span className="absolute -top-1 -right-1 min-w-[13px] h-3 bg-zinc-200 text-zinc-900 text-[8px] font-bold rounded-full flex items-center justify-center px-0.5 leading-none">
{inboxCount > 99 ? "99+" : inboxCount} {inboxCount > 99 ? "99+" : inboxCount}
</span> </span>
)} )}
@@ -252,7 +252,7 @@ export default function Home() {
<button <button
onClick={() => surpriseMut.mutate()} onClick={() => surpriseMut.mutate()}
disabled={surpriseMut.isPending} disabled={surpriseMut.isPending}
className="bg-accent text-black font-display font-bold text-lg px-8 py-4 rounded-2xl hover:scale-105 active:scale-95 transition-all disabled:opacity-60 shadow-lg shadow-accent/20" className="bg-accent text-black font-display font-bold text-lg px-8 py-4 rounded-2xl hover:scale-105 active:scale-95 transition-all disabled:opacity-60 shadow-lg"
> >
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<span className="text-2xl"></span> <span className="text-2xl"></span>

View File

@@ -413,7 +413,7 @@ function SubscriptionImportSection() {
</div> </div>
<button <button
onClick={() => setShowPaste((v) => !v)} onClick={() => setShowPaste((v) => !v)}
className="shrink-0 px-4 py-2 rounded-lg bg-accent text-black text-sm font-medium hover:bg-yellow-300 transition-colors" className="shrink-0 px-4 py-2 rounded-lg bg-accent text-black text-sm font-medium hover:bg-zinc-100 transition-colors"
> >
{showPaste ? "Cancel" : "Paste list"} {showPaste ? "Cancel" : "Paste list"}
</button> </button>
@@ -430,7 +430,7 @@ function SubscriptionImportSection() {
<button <button
onClick={handlePaste} onClick={handlePaste}
disabled={loading || !pasteText.trim()} disabled={loading || !pasteText.trim()}
className="self-end px-4 py-2 rounded-lg bg-accent text-black text-sm font-medium hover:bg-yellow-300 transition-colors disabled:opacity-50" className="self-end px-4 py-2 rounded-lg bg-accent text-black text-sm font-medium hover:bg-zinc-100 transition-colors disabled:opacity-50"
> >
{loading ? "Importing…" : `Import ${(pasteText.match(/@[\w.-]+(?=•)/g) || []).length} channels`} {loading ? "Importing…" : `Import ${(pasteText.match(/@[\w.-]+(?=•)/g) || []).length} channels`}
</button> </button>

View File

@@ -230,7 +230,7 @@ function Placeholder({ video, dlStatus, onPlay, onDownloadAndPlay, isDownloading
) : onDownloadAndPlay ? ( ) : onDownloadAndPlay ? (
<button <button
onClick={onDownloadAndPlay} onClick={onDownloadAndPlay}
className="flex items-center gap-2 px-6 py-3 rounded-full bg-accent text-black font-bold text-sm hover:bg-yellow-300 transition-colors" className="flex items-center gap-2 px-6 py-3 rounded-full bg-accent text-black font-bold text-sm hover:bg-zinc-100 transition-colors"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
@@ -1046,7 +1046,7 @@ export default function Watch() {
<button <button
onClick={() => addSubsMut.mutate()} onClick={() => addSubsMut.mutate()}
disabled={addSubsMut.isPending} disabled={addSubsMut.isPending}
className="flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium bg-accent text-black hover:bg-yellow-300 transition-colors disabled:opacity-50" className="flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium bg-accent text-black hover:bg-zinc-100 transition-colors disabled:opacity-50"
> >
{addSubsMut.isPending && <span className="w-3 h-3 border-2 border-black/40 border-t-transparent rounded-full animate-spin inline-block" />} {addSubsMut.isPending && <span className="w-3 h-3 border-2 border-black/40 border-t-transparent rounded-full animate-spin inline-block" />}
{addSubsMut.isPending ? "Fetching…" : "Add subtitles"} {addSubsMut.isPending ? "Fetching…" : "Add subtitles"}

View File

@@ -10,9 +10,9 @@ export default {
}, },
colors: { colors: {
accent: { accent: {
DEFAULT: "#f5a623", DEFAULT: "#ffffff",
light: "#fbbf45", light: "#f4f4f5",
dark: "#d4891a", dark: "#d4d4d8",
}, },
}, },
aspectRatio: { aspectRatio: {