UI-UX/src/lib/mock-data.ts
iye d5ed43acbd feat(ui): design overhaul, global login modal, design spec
- nav: center links (首页/排行榜/我的), right-side AuthMenu + RemainingVotesBadge; image logo with responsive sizing
- auth: replace /login route with global LoginModal triggered anywhere; "我的" intercepts unauth users with post-login redirect
- home: full-screen Hero, redesigned Top12 (12 pill cards, top-3 glow), scroll-snap mandatory between Hero/Top12/candidates
- home: candidates section with sticky filter that gains frosted-glass bg when stuck (matches nav)
- filter: simplified tags (全部/舞蹈/声乐/rap/全能型); ArtistCard uniform purple vote button
- ranking/me: remove Top12Bar; me header stacks 编辑资料/退出登录 vertically
- typography: font-logo set to Orbitron; ✦ glyph in CYBER ✦ STAR preserved
- layout: max-w-[1500px] unified across pages
- docs: add design-spec.md + design-spec.html with full visual spec (lucide SVG, zero emoji policy)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 18:59:30 +08:00

139 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Artist, ArtistTag } from "@/types/artist";
const STAGE_NAMES: Array<[string, string, string]> = [
["艺奈", "AURORA", "破晓极光"],
["路米", "LUMI", "暖光治愈"],
["星澪", "NEBULA", "星云吟唱"],
["凯", "KAI", "海岸少年"],
["回音", "ECHO", "声波女王"],
["薇尔", "VEIL", "薄雾低语"],
["艾莉雅", "ARIA", "咏叹之声"],
["怜", "REN", "莲华少女"],
["米拉", "MIRA", "镜面舞者"],
["诺娃", "NOVA", "超新星"],
["纪罗", "KIRO", "Rap 制造机"],
["瑞", "ZUI", "醉月夜"],
["阳", "SOL", "阳光少年"],
["凛", "LIN", "学院偶像"],
["律", "LYRA", "竖琴公主"],
["昕", "DAWN", "晨曦少女"],
["天", "SKY", "天空之翼"],
["语", "ARIE", "诗与远方"],
["翼", "WING", "飞翔之翼"],
["铃", "CHIME", "风铃声"],
["夜", "NYX", "暗夜女神"],
["晴", "SUNNY", "晴空万里"],
["月", "LUNA", "月光女神"],
["岚", "STORM", "暴风之子"],
["雷", "BOLT", "雷霆速度"],
["焰", "FLARE", "火焰之心"],
["雪", "FROST", "霜花少女"],
["林", "LEAF", "森林精灵"],
["渊", "ABYSS", "深渊之声"],
["瑶", "JADE", "翡翠少女"],
["晨", "AURIA", "金色晨光"],
["岩", "ROCK", "硬核摇滚"],
["翔", "SOAR", "翱翔天际"],
["茉", "MOLLY", "茉莉芬芳"],
["梓", "AZUR", "蓝调诗人"],
];
// 4 个新主标签均匀分布。每位艺人 1-2 个标签,便于筛选器命中。
const TAG_POOL: ArtistTag[][] = [
["vocal"],
["dance"],
["all-rounder"],
["rap"],
["vocal", "all-rounder"],
["dance", "all-rounder"],
["rap", "all-rounder"],
["vocal"],
["dance"],
["all-rounder"],
["rap"],
["vocal", "dance"],
];
const THEME_COLORS = [
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#f59e0b",
"#10b981",
"#ef4444",
"#a78bfa",
"#f472b6",
"#38bdf8",
"#fbbf24",
"#34d399",
"#fb7185",
];
/** 生成确定性 35 位艺人 mock 数据 */
function buildArtists(): Artist[] {
return STAGE_NAMES.map(([name, enName, slogan], idx) => {
const no = String(idx + 1).padStart(3, "0");
const rank = idx + 1;
// 票数采用反比例衰减(确定性 · 避免 SSR/CSR hydration 不一致)
// 主曲线125000/√rank · 用 idx 推导抖动量保持稳定分布
const jitter = ((idx * 1103) % 2999) - 1499;
const votes = Math.round(125000 / Math.sqrt(rank) + jitter);
return {
id: no,
no,
name,
enName,
slogan,
bio: `来自虚拟星域的偶像候选人 ${enName},从小热爱音乐与舞蹈。性格${
rank % 2 === 0 ? "温柔" : "活泼"
},擅长${
idx % 3 === 0 ? "抒情曲" : idx % 3 === 1 ? "舞台表演" : "Rap 创作"
}。曾获得多项虚拟偶像新人奖项,代表作品深受粉丝喜爱。立志成为 Top12 出道阵容的一员,用音乐传递梦想与力量。`,
portrait: "",
avatar: "",
gallery: ["", "", "", "", ""],
videoUrl: undefined,
videoPoster: "",
tags: TAG_POOL[idx % TAG_POOL.length]!,
birthday: `${String(((idx * 7) % 12) + 1).padStart(2, "0")}-${String(
((idx * 13) % 28) + 1
).padStart(2, "0")}`,
height: 158 + (idx % 12),
cv: idx < 12 ? `CV 配音 #${idx + 1}` : undefined,
themeColor: THEME_COLORS[idx % THEME_COLORS.length]!,
votes,
rank,
};
});
}
export const ARTISTS: Artist[] = buildArtists();
export const TOP_12 = ARTISTS.slice(0, 12);
export const CANDIDATES = ARTISTS.slice(12);
/** 按当前排序方式获取艺人列表 */
export type SortKey = "votes" | "no" | "recent";
export function sortArtists(list: Artist[], key: SortKey = "votes"): Artist[] {
const sorted = [...list];
if (key === "votes") sorted.sort((a, b) => b.votes - a.votes);
else if (key === "no") sorted.sort((a, b) => a.no.localeCompare(b.no));
return sorted;
}
/** 按 ID 获取艺人 */
export function getArtist(id: string): Artist | undefined {
return ARTISTS.find((a) => a.id === id);
}
/** 活动结束时间mock当前日期 + 12 天) */
export function getActivityEndTime(): Date {
const end = new Date();
end.setDate(end.getDate() + 12);
end.setHours(end.getHours() + 3);
end.setMinutes(end.getMinutes() + 24);
end.setSeconds(end.getSeconds() + 18);
return end;
}