- 原生 Next.js App Router + 严格 TypeScript 实现花魁短剧 PC/H5 页面,按 src/features/{catalog,content,playback} 组织,保留完整 SEO(metadata、JSON-LD、canonical、robots、llms、sitemap、IndexNow)。
- 主域名通过 SITE_URL=https://huakuidj.com 强制覆盖后端 foreverUrlAddr(config/*.env、src/server/config.ts、src/server/seo/shared.ts、src/server/services/shared.ts),支持邮箱改为 support@huakuidj.com。
- 移除登录与账户体系:删除 /account 全部页面、登录/注册/充值弹窗、收藏点赞、评论、金币解锁及对应 BFF 接口;旧账户地址 GET/HEAD 统一 301 到首页(src/lib/routes.ts REMOVED_ACCOUNT_PATHS),保留游客会话、反馈、图片上传、观看上报与搜索联想。
- 播放页:评论位改为同类推荐 + 热播榜单,锁定集改为 App 观看遮罩与底部弹层,新增简介标签、分集图例与独立视觉皮肤(src/features/playback、src/styles/pages/playback.css)。
- 卡片:短剧/视频卡状态与时长移至信息行胶囊标签,封面角标移至左上角(src/components/catalog/Cards.tsx、src/styles/foundations/base.css)。
- 同步更新单元测试、浏览器冒烟脚本、路由清单与 README。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46 lines
2.5 KiB
TypeScript
46 lines
2.5 KiB
TypeScript
import { createHash } from 'node:crypto';
|
||
import { readFile, writeFile } from 'node:fs/promises';
|
||
|
||
// Preserve the complete Figma SVG export as an image; never redraw or recolor its paths.
|
||
const brandFile = new URL('../public/static/brand/footer.svg', import.meta.url);
|
||
const outputFile = new URL('../public/static/brand/placeholder.svg', import.meta.url);
|
||
const manifestFile = new URL('../public/static/brand/placeholder.manifest.json', import.meta.url);
|
||
const brand = await readFile(brandFile);
|
||
const sha256 = (bytes: Buffer): string => createHash('sha256').update(bytes).digest('hex');
|
||
const width = 240;
|
||
const logoOpacity = 0.2;
|
||
const height = width * 44 / 124;
|
||
const x = (480 - width) / 2;
|
||
const y = (480 - height) / 2;
|
||
const svg = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="480" height="480" viewBox="0 0 480 480" role="img" aria-labelledby="placeholder-title">
|
||
<title id="placeholder-title">花魁短剧内容占位</title>
|
||
<rect width="480" height="480" fill="#26272f"/>
|
||
<image x="${x}" y="${y.toFixed(6)}" width="${width}" height="${height.toFixed(6)}" opacity="${logoOpacity}" preserveAspectRatio="xMidYMid meet" href="data:image/svg+xml;base64,${brand.toString('base64')}"/>
|
||
</svg>
|
||
`);
|
||
const manifest = Buffer.from(JSON.stringify({
|
||
file: 'public/static/brand/placeholder.svg',
|
||
width: 480, height: 480, sha256: sha256(svg),
|
||
source: 'Composition using the unmodified Figma brand export and the design panel background token.',
|
||
background: { color: '#26272f', source: 'Figma global panel token' },
|
||
composition: {
|
||
type: 'svg-container',
|
||
embeddedImage: {
|
||
file: 'public/static/brand/footer.svg',
|
||
source: 'https://www.figma.com/design/1vUq4wf5xOs8ZOlmt26Q3O/?node-id=1-706',
|
||
nodeId: '1:706', sha256: sha256(brand), encoding: 'base64', unmodified: true,
|
||
x, y: Number(y.toFixed(6)), width, height: Number(height.toFixed(6)), opacity: logoOpacity,
|
||
},
|
||
},
|
||
}, null, 2) + '\n');
|
||
if (process.argv.includes('--check')) {
|
||
const existing = await Promise.all([readFile(outputFile), readFile(manifestFile)]);
|
||
if (!existing[0].equals(svg) || !existing[1].equals(manifest)) {
|
||
throw new Error('Brand placeholder is out of date. Run tsx scripts/compose-placeholder.ts.');
|
||
}
|
||
console.log('Brand placeholder matches the complete unmodified Figma footer.svg export.');
|
||
} else {
|
||
await Promise.all([writeFile(outputFile, svg), writeFile(manifestFile, manifest)]);
|
||
console.log('Composed 480×480 brand placeholder from the complete unmodified Figma footer.svg export.');
|
||
}
|