feat: 本地 codex 为五站生成品牌 logo 与 favicon

- 每站独立的 SVG 品牌资产(public/icon.svg favicon + public/logo.svg 横版
  lockup),由本地 codex CLI 按各站设计语言手工矢量绘制:
  · AI Stack:海军蓝渐变 + 层叠方块(SaaS)
  · LaunchAI:橙渐变 + 上升火箭(社区)
  · The AI Ledger:墨黑方形 + 衬线 L + 双细线(报纸)
  · DevAI.tools:深底绿色 >_ 终端提示符(CLI)
  · findai:紫底放大镜 + 光点(极简搜索)
- header 品牌区:字母方块 → 生成的 icon.svg 图标(img)
- 生成器:public 目录纳入重建保留清单(codex 品牌资产不再被清空),
  favicon 改为已存在则不覆盖
- 验证:10 个 SVG XML 有效、五站 header 图标全部加载、favicon 200
This commit is contained in:
AI Directory Platform
2026-09-10 12:09:41 +00:00
parent e382f2d4c4
commit 0eed5183b2
16 changed files with 73 additions and 26 deletions
+26 -6
View File
@@ -454,9 +454,9 @@ function generateSite(siteKey) {
console.log(`\n=== 生成站点 ${siteKey} (port ${site.port}, theme ${site.theme}) ===`);
fs.mkdirSync(siteRoot, { recursive: true });
// 清空生成物,但保留本地环境与依赖
// 清空生成物,但保留本地环境、依赖与品牌资产(public 内含 codex 生成的 icon/logo)
for (const entry of fs.readdirSync(siteRoot)) {
if (entry === ".env.local" || entry === "node_modules") continue;
if (entry === ".env.local" || entry === "node_modules" || entry === "public") continue;
fs.rmSync(path.join(siteRoot, entry), { recursive: true, force: true });
}
@@ -761,13 +761,33 @@ export default getRequestConfig(async ({ requestLocale }) => {
writeFileSafe(path.join(siteRoot, "src/i18n/request.ts"), requestSrc);
// ---------- 9) public + env ----------
// cpSync 前备份 codex 品牌资产(icon.svg / logo.svg),拷贝后恢复
const brandAssets = ["icon.svg", "logo.svg"];
const backupDir = path.join(siteRoot, ".brand-backup");
for (const asset of brandAssets) {
const assetPath = path.join(siteRoot, "public", asset);
if (fs.existsSync(assetPath)) {
fs.mkdirSync(backupDir, { recursive: true });
fs.copyFileSync(assetPath, path.join(backupDir, asset));
}
}
fs.cpSync(path.join(CORE_ROOT, "public"), path.join(siteRoot, "public"), { recursive: true });
for (const asset of brandAssets) {
const backupPath = path.join(backupDir, asset);
if (fs.existsSync(backupPath)) {
fs.copyFileSync(backupPath, path.join(siteRoot, "public", asset));
fs.rmSync(backupDir, { recursive: true, force: true });
}
}
// 品牌首字母 favicon(SVG,无 Emoji)
const initial = site.brand.name.slice(0, 1).toUpperCase();
writeFileSafe(
path.join(siteRoot, "public", "icon.svg"),
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="currentColor"/><text x="32" y="44" font-family="system-ui,sans-serif" font-size="36" font-weight="700" text-anchor="middle" fill="#fff">${initial}</text></svg>\n`
);
// favicon:保留已生成的品牌 icon.svg(codex 生成),不存在时才写字母占位
if (!fs.existsSync(path.join(siteRoot, "public", "icon.svg"))) {
writeFileSafe(
path.join(siteRoot, "public", "icon.svg"),
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="currentColor"/><text x="32" y="44" font-family="system-ui,sans-serif" font-size="36" font-weight="700" text-anchor="middle" fill="#fff">${initial}</text></svg>\n`
);
}
writeFileSafe(
path.join(siteRoot, ".env.example"),
`# ${site.brand.name} 环境变量(与底座共享同一套 PostgreSQL / Better Auth / Admin)
@@ -120,12 +120,8 @@ export function FormShell<TFieldValues extends FieldValues>({
>
<div className="w-full max-w-md rounded-3xl bg-card p-8 shadow-[0_24px_70px_rgba(0,0,0,0.25)] sm:p-10">
<div className="mb-7 flex items-center gap-2.5">
<span
className="flex h-10 w-10 items-center justify-center rounded-2xl text-lg font-bold text-white"
style={{ backgroundColor: accentHsl }}
>
{initial}
</span>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/icon.svg" alt="" className="h-10 w-10 rounded-2xl" width={40} height={40} />
<span className="text-xl font-extrabold tracking-tight text-foreground">{brand}</span>
</div>
<h1 className="text-3xl font-extrabold tracking-tight text-foreground">{title}</h1>
+3 -3
View File
@@ -72,9 +72,9 @@ export function ThemeHeader({
<div className="mx-auto flex h-14 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6">
{/* Logo */}
<Link href={localizedHref("/", locale)} className="flex shrink-0 items-center gap-2">
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary font-bold text-primary-foreground">
{brand.name.slice(0, 1)}
</span>
{/* 站点 logo(codex 生成的 icon.svg,自带品牌色底,深浅通吃) */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/icon.svg" alt="" className="h-7 w-7 rounded-lg" width={28} height={28} />
<span className="text-[0.95rem] font-bold tracking-tight text-foreground">
{brand.name}
</span>