"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import { Users } from "lucide-react"; import HeroBanner from "@/components/HeroBanner"; import Top12Bar from "@/components/Top12Bar"; import ArtistCard from "@/components/cards/ArtistCard"; import ArtistFilters, { type TagFilter } from "@/components/ArtistFilters"; import VoteModal from "@/components/VoteModal"; import { sortArtists, type SortKey } from "@/lib/mock-data"; import { useVoteStore } from "@/lib/store"; import { useVoteAction } from "@/hooks/useVoteAction"; import { useScrollRestore } from "@/hooks/useScrollRestore"; import { useUIStore } from "@/lib/ui-store"; import { cn } from "@/lib/cn"; import { tosUrl } from "@/lib/tos"; export default function Home() { const artists = useVoteStore((s) => s.artists); const { target, remaining, totalQuota, openVote, closeVote, confirmVote } = useVoteAction(); const [tagFilter, setTagFilter] = useState("all"); const [sortKey, setSortKey] = useState("votes"); const [filterStuck, setFilterStuck] = useState(false); const filterSentinelRef = useRef(null); const setStoreFilterStuck = useUIStore((s) => s.setFilterStuck); // 首页滚动位置 per-tab 记忆:从艺人详情点 ← 返回时恢复到上次浏览位置 useScrollRestore("home"); const visibleArtists = useMemo(() => { let list = [...artists]; if (tagFilter !== "all") { list = list.filter((a) => a.tags.includes(tagFilter)); } return sortArtists(list, sortKey); }, [artists, tagFilter, sortKey]); // 仅在首页启用 scroll-snap:用户接近 Hero/Top12/候选区时自然吸附。 // 用 proximity 而不是 mandatory —— mandatory 会把"滚到底部"强制吸回到最后一个 // snap 点的 start(候选区顶部),表现为回弹;proximity 只在靠近时吸,远离不干预。 useEffect(() => { const root = document.documentElement; const prev = root.style.scrollSnapType; root.style.scrollSnapType = "y proximity"; return () => { root.style.scrollSnapType = prev; }; }, []); // 检测筛选条是否吸顶:直接读哨兵相对视口的 top,越过 nav 下沿(80px)即 stuck。 // 不用 IntersectionObserver — 它对零面积/scroll-snap 场景不稳定,scroll 监听更直接。 useEffect(() => { const sentinel = filterSentinelRef.current; if (!sentinel) return; const update = () => { const top = sentinel.getBoundingClientRect().top; setFilterStuck(top <= 80); }; update(); window.addEventListener("scroll", update, { passive: true }); window.addEventListener("resize", update); return () => { window.removeEventListener("scroll", update); window.removeEventListener("resize", update); }; }, []); // 把 filterStuck 同步到全局 UI store —— 让导航栏感知,在吸顶时关掉自己的玻璃, // 让筛选条延伸出的"共享玻璃带"成为唯一的 backdrop-filter,消除接缝 useEffect(() => { setStoreFilterStuck(filterStuck); return () => setStoreFilterStuck(false); }, [filterStuck, setStoreFilterStuck]); return ( <> {/* Hero · 全屏沉浸式视频 · 作为第一个 snap 点 -mt-20 把 Hero 拉到 main 顶部 padding 之上,让视频铺到导航后面(与毛玻璃导航重叠) */}
{/* Top12 出道位 · 作为第二个 snap 点:滚动结束后自然落到这里,标题贴近顶部 */}
{/* 候选人阵容 · 作为第三个 snap 点 */}
{/* 大标题 · 版心内 */}

{artists.length} 位候选人

当前显示{" "} {visibleArtists.length} {" "} 位

{/* 哨兵:用于检测筛选条是否吸顶 */}
{/* 筛选条 · 外层铺满,内层版心承载文案。 吸顶时,absolute 子层把玻璃从 -top-20 一路扩到容器底部 —— 这是一个单一元素的 backdrop-filter,横跨 nav 区域 + filter 区域, 消除两块独立玻璃在 y=80 接缝处的视觉割裂。导航栏同步关掉自己的玻璃。 */}
{/* 共享玻璃带:absolute,吸顶时延伸到 nav 顶部,opacity 平滑过渡 */}
{/* 候选人网格 · 版心内 */}
{visibleArtists.length === 0 ? ( ) : (
{visibleArtists.map((a) => ( ))}
)}
); } function EmptyState() { return (

No Match

该标签下暂无艺人

); }