All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m17s
- Navigation: fixed + transparent over Hero (home) / at page top (other routes); fades to glass-on-scroll. Glass uses surface tone matching site cards. - Filter bar sticky glass synced to nav recipe (no seam between layers). - HeroBanner: full-viewport video, center title removed, bottom dim overlay removed, eyebrow/countdown repositioned below the nav. - ArtistDetail: removed portrait shadow; added FloatingBackButton that uses router.back() with internal-history fallback to /. - Floating buttons (back + vote) translateY upward to avoid footer rather than disappearing, via useFooterPush. - Home: useScrollRestore preserves scroll position on return from detail pages; temporarily disables scroll-snap during restore. - PerformanceVideo: max-w capped by 85svh*16/9 so small viewports never crop. - ArtistFilters: hide horizontal scrollbar thumb in tag container. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { cn } from "@/lib/cn";
|
|
import type { ArtistTag } from "@/types/artist";
|
|
import type { SortKey } from "@/lib/mock-data";
|
|
|
|
export type TagFilter = ArtistTag | "all";
|
|
|
|
interface ArtistFiltersProps {
|
|
tagFilter: TagFilter;
|
|
onTagChange: (tag: TagFilter) => void;
|
|
sort: SortKey;
|
|
onSortChange: (sort: SortKey) => void;
|
|
}
|
|
|
|
const TAG_OPTIONS: { key: TagFilter; label: string }[] = [
|
|
{ key: "all", label: "全部" },
|
|
{ key: "rock", label: "摇滚" },
|
|
{ key: "pop", label: "流行" },
|
|
{ key: "chinese", label: "国风" },
|
|
{ key: "hiphop", label: "嘻哈说唱" },
|
|
{ key: "folk", label: "民谣治愈" },
|
|
{ key: "jazz", label: "爵士" },
|
|
];
|
|
|
|
const SORT_OPTIONS: { key: SortKey; label: string }[] = [
|
|
{ key: "votes", label: "实时排名" },
|
|
{ key: "no", label: "编号顺序" },
|
|
];
|
|
|
|
export default function ArtistFilters({
|
|
tagFilter,
|
|
onTagChange,
|
|
sort,
|
|
onSortChange,
|
|
}: ArtistFiltersProps) {
|
|
return (
|
|
<div className="flex items-center gap-3 py-3 border-b border-white/[0.06]">
|
|
{/* 左:标签筛选 · 隐藏横向滚动条 thumb,避免在毛玻璃栏里出现深色小矩形 */}
|
|
<div className="flex items-center gap-1 overflow-x-auto flex-1 min-w-0 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
|
{TAG_OPTIONS.map((opt) => (
|
|
<TagPill
|
|
key={opt.key}
|
|
active={tagFilter === opt.key}
|
|
onClick={() => onTagChange(opt.key)}
|
|
>
|
|
{opt.label}
|
|
</TagPill>
|
|
))}
|
|
</div>
|
|
|
|
{/* 右:排序切换(segmented control 胶囊) */}
|
|
<div className="flex-shrink-0 inline-flex rounded-full bg-white/[0.04] border border-white/10 p-0.5 text-xs">
|
|
{SORT_OPTIONS.map((opt) => {
|
|
const active = sort === opt.key;
|
|
return (
|
|
<button
|
|
key={opt.key}
|
|
type="button"
|
|
onClick={() => onSortChange(opt.key)}
|
|
aria-pressed={active}
|
|
className={cn(
|
|
"px-3 sm:px-3.5 h-7 rounded-full transition-colors whitespace-nowrap",
|
|
active
|
|
? "bg-purple-500/25 text-purple-100 shadow-[0_0_10px_rgba(139,92,246,0.35)]"
|
|
: "text-white/55 hover:text-white/85",
|
|
)}
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TagPill({
|
|
active,
|
|
onClick,
|
|
children,
|
|
}: {
|
|
active: boolean;
|
|
onClick: () => void;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={onClick}
|
|
className={cn(
|
|
"relative px-3.5 py-1.5 text-xs whitespace-nowrap transition-colors",
|
|
active ? "text-purple-300" : "text-white/55 hover:text-white/85",
|
|
)}
|
|
>
|
|
{children}
|
|
{active && (
|
|
<span
|
|
className="absolute -bottom-0.5 left-3 right-3 h-0.5 bg-purple-400 rounded-full"
|
|
style={{ boxShadow: "0 0 6px #a78bfa" }}
|
|
/>
|
|
)}
|
|
</button>
|
|
);
|
|
}
|