feat(ui): liquid-glass design system, settings polish, hints, notif fixes

- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
  performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
  sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
  horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
  notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
  hints across the settings and channel-manager surfaces; persisted per account.
This commit is contained in:
npeter83 2026-06-11 21:08:35 +02:00
parent bf54b4b5a5
commit f8d2a11226
13 changed files with 388 additions and 102 deletions

View file

@ -12,6 +12,7 @@ import {
} from "lucide-react";
import { api, type ManagedChannel, type Tag } from "../lib/api";
import { notify } from "../lib/notifications";
import Tooltip from "./Tooltip";
export default function Channels({
onViewChannel,
@ -76,11 +77,23 @@ export default function Channels({
{/* Per-user sync status */}
{s && (
<div className="flex flex-wrap items-center gap-x-5 gap-y-1 text-sm text-muted mb-4">
<Stat label="Channels" value={s.channels_total} />
<Stat label="Recent synced" value={`${s.channels_recent_synced}/${s.channels_total}`} />
<Stat label="Full history" value={`${s.channels_deep_done}/${s.channels_total}`} />
<Stat label="My videos" value={s.my_videos.toLocaleString()} />
<Stat label="Quota left" value={s.quota_remaining_today.toLocaleString()} />
<Stat label="Channels" value={s.channels_total} hint="Channels you're subscribed to." />
<Stat
label="Recent synced"
value={`${s.channels_recent_synced}/${s.channels_total}`}
hint="Channels whose recent uploads are in the local DB and show in your feed."
/>
<Stat
label="Full history"
value={`${s.channels_deep_done}/${s.channels_total}`}
hint="Channels whose entire back-catalog is fetched. The rest backfill gradually."
/>
<Stat label="My videos" value={s.my_videos.toLocaleString()} hint="Total videos available across your channels." />
<Stat
label="Quota left"
value={s.quota_remaining_today.toLocaleString()}
hint="Shared YouTube API budget left today (resets midnight US Pacific)."
/>
</div>
)}
@ -166,23 +179,35 @@ export default function Channels({
);
}
function Stat({ label, value }: { label: string; value: string | number }) {
function Stat({
label,
value,
hint,
}: {
label: string;
value: string | number;
hint?: string;
}) {
return (
<span>
<span className="text-fg font-semibold">{value}</span> {label}
</span>
<Tooltip hint={hint ?? ""}>
<span className={hint ? "cursor-help" : ""}>
<span className="text-fg font-semibold">{value}</span> {label}
</span>
</Tooltip>
);
}
function SyncBadge({ ok, label }: { ok: boolean; label: string }) {
function SyncBadge({ ok, label, hint }: { ok: boolean; label: string; hint?: string }) {
return (
<span
className={`text-[10px] px-1.5 py-0.5 rounded-full border ${
ok ? "border-accent/40 text-accent" : "border-border text-muted"
}`}
>
{label}
</span>
<Tooltip hint={hint ?? ""}>
<span
className={`text-[10px] px-1.5 py-0.5 rounded-full border ${
ok ? "border-accent/40 text-accent" : "border-border text-muted"
}`}
>
{label}
</span>
</Tooltip>
);
}
@ -203,7 +228,7 @@ function ChannelRow({
}) {
return (
<div
className={`flex items-center gap-3 p-2.5 rounded-xl border border-border bg-card/30 ${
className={`glass-card glass-hover flex items-center gap-3 p-2.5 rounded-xl transition ${
c.hidden ? "opacity-60" : ""
}`}
>
@ -232,8 +257,16 @@ function ChannelRow({
{c.subscriber_count != null && <span>· {c.subscriber_count.toLocaleString()} subs</span>}
</div>
<div className="flex flex-wrap items-center gap-1 mt-1">
<SyncBadge ok={c.recent_synced} label="recent" />
<SyncBadge ok={c.backfill_done} label="full" />
<SyncBadge
ok={c.recent_synced}
label="recent"
hint={c.recent_synced ? "Recent uploads synced." : "Recent uploads not fetched yet."}
/>
<SyncBadge
ok={c.backfill_done}
label="full"
hint={c.backfill_done ? "Full history fetched." : "Only recent videos so far; full history pending."}
/>
{userTags.map((t) => {
const on = c.tag_ids.includes(t.id);
return (