Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d8451baa3 | |||
| aba9eee0c6 | |||
| 85717d557d | |||
| 034bb7ff42 | |||
| 1236df31b8 | |||
| 74a7b0ea16 | |||
| 49be38ff77 |
BIN
public/logo.png
BIN
public/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 1.4 MiB |
@ -35,7 +35,7 @@ export default function HeroBanner({
|
|||||||
if (!v || !videoSrc) return;
|
if (!v || !videoSrc) return;
|
||||||
v.muted = isMuted;
|
v.muted = isMuted;
|
||||||
v.play().catch(() => {});
|
v.play().catch(() => {});
|
||||||
// 仅在 videoSrc 变化时执行 · 不依赖 isMuted(mute 切换由按钮处理)
|
// 仅在 videoSrc 变化时执行 · 不依赖 isMuted(mute 切换由按钮处理)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [videoSrc]);
|
}, [videoSrc]);
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ export default function HeroBanner({
|
|||||||
{/* Eyebrow 左上 · 紧贴导航下方 */}
|
{/* Eyebrow 左上 · 紧贴导航下方 */}
|
||||||
<div className="absolute top-[6.5rem] sm:top-[7.5rem] left-4 sm:left-6 lg:left-8 z-10">
|
<div className="absolute top-[6.5rem] sm:top-[7.5rem] left-4 sm:left-6 lg:left-8 z-10">
|
||||||
<p className="font-label text-[10px] sm:text-xs tracking-[0.4em] uppercase text-purple-200/90">
|
<p className="font-label text-[10px] sm:text-xs tracking-[0.4em] uppercase text-purple-200/90">
|
||||||
Top 12 · Virtual Idol Debut Project
|
Top 12 · Cyber Star Debut Survival
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -25,9 +25,10 @@ export default function Logo({
|
|||||||
|
|
||||||
// 用原生 <img> 绕开 Next/Image 的格式转换 —— 某些环境下 sharp 把透明 PNG
|
// 用原生 <img> 绕开 Next/Image 的格式转换 —— 某些环境下 sharp 把透明 PNG
|
||||||
// 转 webp/avif 时会铺白底,导致 logo 在深色 nav 上出现白色矩形。
|
// 转 webp/avif 时会铺白底,导致 logo 在深色 nav 上出现白色矩形。
|
||||||
|
// ?v=2 缓存破坏:logo 改版时 +1,浏览器立刻拉新版而不读老缓存。
|
||||||
const inner = (
|
const inner = (
|
||||||
<img
|
<img
|
||||||
src="/logo.png"
|
src="/logo.png?v=3"
|
||||||
alt="CYBER STAR"
|
alt="CYBER STAR"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
@ -35,8 +36,6 @@ export default function Logo({
|
|||||||
height: `${h}px`,
|
height: `${h}px`,
|
||||||
width: "auto",
|
width: "auto",
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
// 保留紫色辉光,但 drop-shadow 不会引入白底
|
|
||||||
filter: "drop-shadow(0 0 14px rgba(139,92,246,0.4))",
|
|
||||||
}}
|
}}
|
||||||
className={`block select-none ${className}`}
|
className={`block select-none ${className}`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import Logo from "./Logo";
|
|
||||||
import NavLinks from "./NavLinks";
|
import NavLinks from "./NavLinks";
|
||||||
import SearchTrigger from "./SearchTrigger";
|
import SearchTrigger from "./SearchTrigger";
|
||||||
import AuthMenu from "./auth/AuthMenu";
|
import AuthMenu from "./auth/AuthMenu";
|
||||||
@ -82,9 +81,7 @@ export default function Navigation() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<nav className="relative max-w-[1500px] mx-auto h-20 px-4 sm:px-6 lg:px-8 flex items-center gap-8">
|
<nav className="relative max-w-[1500px] mx-auto h-20 px-4 sm:px-6 lg:px-8 flex items-center gap-8">
|
||||||
<Logo size="md" />
|
{/* 左侧:首页 / 排行榜 / 我的(logo 已移除) */}
|
||||||
|
|
||||||
{/* 中部:首页 / 排行榜 / 我的 */}
|
|
||||||
<NavLinks className="hidden md:flex" />
|
<NavLinks className="hidden md:flex" />
|
||||||
|
|
||||||
{/* 右侧:搜索 + 今日余票 + 登录/注册 (或 头像+下拉) */}
|
{/* 右侧:搜索 + 今日余票 + 登录/注册 (或 头像+下拉) */}
|
||||||
|
|||||||
@ -40,8 +40,8 @@ export default function ArtistCard({
|
|||||||
className="block"
|
className="block"
|
||||||
aria-label={`查看 ${artist.name} 详情`}
|
aria-label={`查看 ${artist.name} 详情`}
|
||||||
>
|
>
|
||||||
{/* 立绘区(13+ 卡片轻度暗化) */}
|
{/* 立绘区 · Top12 区分仅靠紫色边框 + 辉光,不再降低非 Top12 卡片亮度 */}
|
||||||
<div className={cn("relative aspect-[4/5]", !inTop12 && "opacity-[0.78]")}>
|
<div className="relative aspect-[4/5]">
|
||||||
<ArtistPortrait
|
<ArtistPortrait
|
||||||
artist={artist}
|
artist={artist}
|
||||||
rounded="rounded-none"
|
rounded="rounded-none"
|
||||||
@ -59,9 +59,6 @@ export default function ArtistCard({
|
|||||||
>
|
>
|
||||||
{artist.rank}
|
{artist.rank}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 顶部轻微渐变蒙层 */}
|
|
||||||
<div className="absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-black/40 to-transparent pointer-events-none" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 信息区(黑色背景明显分隔) */}
|
{/* 信息区(黑色背景明显分隔) */}
|
||||||
|
|||||||
@ -324,7 +324,7 @@ export const ARTIST_SEEDS: ArtistSeed[] = [
|
|||||||
{
|
{
|
||||||
no: `021`,
|
no: `021`,
|
||||||
name: `温景然`,
|
name: `温景然`,
|
||||||
enName: `RYAN`,
|
enName: `KINGSTON`,
|
||||||
age: 27,
|
age: 27,
|
||||||
gender: `M`,
|
gender: `M`,
|
||||||
height: 178,
|
height: 178,
|
||||||
|
|||||||
@ -11,22 +11,34 @@ import { tosUrl } from "./tos";
|
|||||||
* 票数 / 排名是运行时计算(store 投票后会更新)。除此之外不再有任何虚构数据。
|
* 票数 / 排名是运行时计算(store 投票后会更新)。除此之外不再有任何虚构数据。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** 没有 solo.mp4 的艺人编号(docx 标注"缺视频") */
|
/** 没有 solo.mp4 的艺人编号(docx 标注"缺视频")。
|
||||||
const MISSING_VIDEO: ReadonlySet<string> = new Set(["003", "010", "017", "027"]);
|
003/010/017/027 在 v2 物料里已补上,033 已替换,这些都从集合里移除。 */
|
||||||
|
const MISSING_VIDEO: ReadonlySet<string> = new Set<string>();
|
||||||
|
|
||||||
/** 缺氛围图3 的艺人编号(资料文件夹里实际只到氛围图2) */
|
/**
|
||||||
const MISSING_ATMOSPHERE_3: ReadonlySet<string> = new Set(["036"]);
|
* 自定义封面:这些艺人的卡片/详情主立绘改用单独上传的 cover 图,
|
||||||
|
* 而不是默认的氛围图1。氛围图1/2/3 在画廊里保持不变。
|
||||||
|
* (调整.xlsx 里 "封面图用氛围图N" 的实现 —— 新封面已转 webp 上传到
|
||||||
|
* portraits/{no}-cover.webp,这里把 portrait 字段切到该路径。)
|
||||||
|
*/
|
||||||
|
const CUSTOM_COVERS: ReadonlySet<string> = new Set([
|
||||||
|
"002",
|
||||||
|
"003",
|
||||||
|
"005",
|
||||||
|
"012",
|
||||||
|
"013",
|
||||||
|
"014",
|
||||||
|
"019",
|
||||||
|
"025",
|
||||||
|
]);
|
||||||
|
|
||||||
/** 画廊 = 三张氛围图(1/2/3)。不包含三视图,因为长宽比与卡片不一致。 */
|
/** 画廊 = 三张氛围图(1/2/3)。不包含三视图,因为长宽比与卡片不一致。 */
|
||||||
function buildGallery(no: string): string[] {
|
function buildGallery(no: string): string[] {
|
||||||
const items = [
|
return [
|
||||||
tosUrl(`portraits/${no}.webp`),
|
tosUrl(`portraits/${no}.webp`),
|
||||||
tosUrl(`portraits/${no}-2.webp`),
|
tosUrl(`portraits/${no}-2.webp`),
|
||||||
|
tosUrl(`portraits/${no}-3.webp`),
|
||||||
];
|
];
|
||||||
if (!MISSING_ATMOSPHERE_3.has(no)) {
|
|
||||||
items.push(tosUrl(`portraits/${no}-3.webp`));
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildArtists(): Artist[] {
|
function buildArtists(): Artist[] {
|
||||||
@ -39,7 +51,11 @@ function buildArtists(): Artist[] {
|
|||||||
age: seed.age,
|
age: seed.age,
|
||||||
gender: seed.gender,
|
gender: seed.gender,
|
||||||
bio: seed.bio,
|
bio: seed.bio,
|
||||||
portrait: tosUrl(`portraits/${seed.no}.webp`),
|
portrait: tosUrl(
|
||||||
|
CUSTOM_COVERS.has(seed.no)
|
||||||
|
? `portraits/${seed.no}-cover.webp`
|
||||||
|
: `portraits/${seed.no}.webp`,
|
||||||
|
),
|
||||||
avatar: "",
|
avatar: "",
|
||||||
gallery: buildGallery(seed.no),
|
gallery: buildGallery(seed.no),
|
||||||
videoUrl: MISSING_VIDEO.has(seed.no)
|
videoUrl: MISSING_VIDEO.has(seed.no)
|
||||||
|
|||||||
@ -3,16 +3,21 @@
|
|||||||
*
|
*
|
||||||
* 用法:
|
* 用法:
|
||||||
* tosUrl("portraits/001.webp")
|
* tosUrl("portraits/001.webp")
|
||||||
* → https://cyberstar.tos-cn-shanghai.volces.com/cyber-star/portraits/001.webp
|
* → https://cyberstar.tos-cn-shanghai.volces.com/cyber-star/portraits/001.webp?v=2
|
||||||
*
|
*
|
||||||
* 环境变量 NEXT_PUBLIC_TOS_DOMAIN 配置:
|
* 环境变量 NEXT_PUBLIC_TOS_DOMAIN 配置:
|
||||||
* .env.local / .env.production → 完整的桶 + 路径前缀 (含 scheme, 不含末尾 /)
|
* .env.local / .env.production → 完整的桶 + 路径前缀 (含 scheme, 不含末尾 /)
|
||||||
* 未设置时 fallback 到相对路径 (/path/...), 适合本地用 public/ 静态文件托管的场景。
|
* 未设置时 fallback 到相对路径 (/path/...), 适合本地用 public/ 静态文件托管的场景。
|
||||||
|
*
|
||||||
|
* 缓存版本号 TOS_VERSION:每次有 TOS 文件被原地覆盖更新(图片/视频),
|
||||||
|
* 把这个数字 +1。浏览器和 CDN 会把带新版本号的 URL 当作全新资源,
|
||||||
|
* 立即看到更新后的内容,不必等 TTL 过期或手动 invalidate。
|
||||||
*/
|
*/
|
||||||
const TOS_BASE = (process.env.NEXT_PUBLIC_TOS_DOMAIN ?? "").replace(/\/+$/, "");
|
const TOS_BASE = (process.env.NEXT_PUBLIC_TOS_DOMAIN ?? "").replace(/\/+$/, "");
|
||||||
|
const TOS_VERSION = "7";
|
||||||
|
|
||||||
export function tosUrl(path: string): string {
|
export function tosUrl(path: string): string {
|
||||||
const clean = path.replace(/^\/+/, "");
|
const clean = path.replace(/^\/+/, "");
|
||||||
if (!TOS_BASE) return `/${clean}`;
|
const base = TOS_BASE ? `${TOS_BASE}/${clean}` : `/${clean}`;
|
||||||
return `${TOS_BASE}/${clean}`;
|
return `${base}?v=${TOS_VERSION}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user