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
@@ -454,9 +454,9 @@ function generateSite(siteKey) {
|
|||||||
|
|
||||||
console.log(`\n=== 生成站点 ${siteKey} (port ${site.port}, theme ${site.theme}) ===`);
|
console.log(`\n=== 生成站点 ${siteKey} (port ${site.port}, theme ${site.theme}) ===`);
|
||||||
fs.mkdirSync(siteRoot, { recursive: true });
|
fs.mkdirSync(siteRoot, { recursive: true });
|
||||||
// 清空生成物,但保留本地环境与依赖
|
// 清空生成物,但保留本地环境、依赖与品牌资产(public 内含 codex 生成的 icon/logo)
|
||||||
for (const entry of fs.readdirSync(siteRoot)) {
|
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 });
|
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);
|
writeFileSafe(path.join(siteRoot, "src/i18n/request.ts"), requestSrc);
|
||||||
|
|
||||||
// ---------- 9) public + env ----------
|
// ---------- 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 });
|
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)
|
// 品牌首字母 favicon(SVG,无 Emoji)
|
||||||
const initial = site.brand.name.slice(0, 1).toUpperCase();
|
const initial = site.brand.name.slice(0, 1).toUpperCase();
|
||||||
|
// favicon:保留已生成的品牌 icon.svg(codex 生成),不存在时才写字母占位
|
||||||
|
if (!fs.existsSync(path.join(siteRoot, "public", "icon.svg"))) {
|
||||||
writeFileSafe(
|
writeFileSafe(
|
||||||
path.join(siteRoot, "public", "icon.svg"),
|
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`
|
`<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(
|
writeFileSafe(
|
||||||
path.join(siteRoot, ".env.example"),
|
path.join(siteRoot, ".env.example"),
|
||||||
`# ${site.brand.name} 环境变量(与底座共享同一套 PostgreSQL / Better Auth / Admin)
|
`# ${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="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">
|
<div className="mb-7 flex items-center gap-2.5">
|
||||||
<span
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
className="flex h-10 w-10 items-center justify-center rounded-2xl text-lg font-bold text-white"
|
<img src="/icon.svg" alt="" className="h-10 w-10 rounded-2xl" width={40} height={40} />
|
||||||
style={{ backgroundColor: accentHsl }}
|
|
||||||
>
|
|
||||||
{initial}
|
|
||||||
</span>
|
|
||||||
<span className="text-xl font-extrabold tracking-tight text-foreground">{brand}</span>
|
<span className="text-xl font-extrabold tracking-tight text-foreground">{brand}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-3xl font-extrabold tracking-tight text-foreground">{title}</h1>
|
<h1 className="text-3xl font-extrabold tracking-tight text-foreground">{title}</h1>
|
||||||
|
|||||||
@@ -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">
|
<div className="mx-auto flex h-14 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link href={localizedHref("/", locale)} className="flex shrink-0 items-center gap-2">
|
<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">
|
{/* 站点 logo(codex 生成的 icon.svg,自带品牌色底,深浅通吃) */}
|
||||||
{brand.name.slice(0, 1)}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
</span>
|
<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">
|
<span className="text-[0.95rem] font-bold tracking-tight text-foreground">
|
||||||
{brand.name}
|
{brand.name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<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">A</text></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><linearGradient id="g" x2="0" y2="1"><stop stop-color="#1e3a8a"/><stop offset="1" stop-color="#2563eb"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#g)"/><g fill="#fff"><path d="M13 19.5c0-2.5 2-4.5 4.5-4.5h29c2.5 0 4.5 2 4.5 4.5S49 24 46.5 24h-29c-2.5 0-4.5-2-4.5-4.5Z"/><path d="M10 31.5c0-2.5 2-4.5 4.5-4.5h29c2.5 0 4.5 2 4.5 4.5S46 36 43.5 36h-29c-2.5 0-4.5-2-4.5-4.5Z"/><path d="M7 43.5C7 41 9 39 11.5 39h29c2.5 0 4.5 2 4.5 4.5S43 48 40.5 48h-29C9 48 7 46 7 43.5Z"/></g></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 575 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 48"><defs><linearGradient id="g" x2="0" y2="1"><stop stop-color="#1e3a8a"/><stop offset="1" stop-color="#2563eb"/></linearGradient></defs><g transform="translate(4 4) scale(.625)"><rect width="64" height="64" rx="14" fill="url(#g)"/><g fill="#fff"><path d="M13 19.5c0-2.5 2-4.5 4.5-4.5h29c2.5 0 4.5 2 4.5 4.5S49 24 46.5 24h-29c-2.5 0-4.5-2-4.5-4.5Z"/><path d="M10 31.5c0-2.5 2-4.5 4.5-4.5h29c2.5 0 4.5 2 4.5 4.5S46 36 43.5 36h-29c-2.5 0-4.5-2-4.5-4.5Z"/><path d="M7 43.5C7 41 9 39 11.5 39h29c2.5 0 4.5 2 4.5 4.5S43 48 40.5 48h-29C9 48 7 46 7 43.5Z"/></g></g><text x="56" y="32" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="24" font-weight="700" fill="#0f172a">AI Stack</text></svg>
|
||||||
|
After Width: | Height: | Size: 784 B |
@@ -1 +1 @@
|
|||||||
<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">D</text></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="8" fill="#10151d"/><path d="M18 20l12 12-12 12M34 44h12" fill="none" stroke="#34d399" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 251 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 48"><g transform="translate(4 4) scale(.625)"><rect width="64" height="64" rx="8" fill="#10151d"/><path d="M18 20l12 12-12 12M34 44h12" fill="none" stroke="#34d399" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/></g><text x="56" y="31" font-family="ui-monospace,SF Mono,Menlo,monospace" font-size="21" font-weight="700" fill="#e2e8f0">DevAI<tspan fill="#34d399">.tools</tspan></text></svg>
|
||||||
|
After Width: | Height: | Size: 465 B |
@@ -1 +1 @@
|
|||||||
<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">F</text></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="16" fill="#7c3aed"/><circle cx="27" cy="27" r="11" fill="none" stroke="#fff" stroke-width="4.5"/><path d="m35 35 8 8" fill="none" stroke="#fff" stroke-width="4.5" stroke-linecap="round"/><circle cx="45" cy="15" r="2.25" fill="#fff"/></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 333 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 48"><g transform="translate(4 4) scale(.625)"><rect width="64" height="64" rx="16" fill="#7c3aed"/><circle cx="27" cy="27" r="11" fill="none" stroke="#fff" stroke-width="4.5"/><path d="m35 35 8 8" fill="none" stroke="#fff" stroke-width="4.5" stroke-linecap="round"/><circle cx="45" cy="15" r="2.25" fill="#fff"/></g><text x="55" y="34" fill="#1a1523" font-family="system-ui,-apple-system,sans-serif" font-size="28" font-weight="700" letter-spacing="-1.2">findai</text></svg>
|
||||||
|
After Width: | Height: | Size: 532 B |
@@ -15,7 +15,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -55,7 +55,8 @@
|
|||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|||||||
@@ -1 +1,11 @@
|
|||||||
<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">L</text></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<defs><linearGradient id="g" x2="1" y2="1"><stop stop-color="#f97316"/><stop offset="1" stop-color="#fb923c"/></linearGradient></defs>
|
||||||
|
<rect width="64" height="64" rx="14" fill="url(#g)"/>
|
||||||
|
<g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m12 52 11-11M10 43l7-7" stroke-width="4" opacity=".72"/>
|
||||||
|
<path d="M20 47c8-2 17-7 24-16 5-6 8-13 8-18-5 0-12 3-18 8-9 7-14 16-16 26Z" fill="#fff" stroke-width="2"/>
|
||||||
|
<path d="m25 43 7 7c2-4 3-8 2-12M22 32l-8-1c2-4 5-7 9-9" fill="#fff" stroke-width="2"/>
|
||||||
|
<path d="M20 47c-1 4-1 6 1 7 3-1 5-3 7-6" fill="#fff" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="40" cy="25" r="3.5" fill="#f97316" stroke="#fff" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 780 B |
@@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 48">
|
||||||
|
<defs><linearGradient id="g" x2="1" y2="1"><stop stop-color="#f97316"/><stop offset="1" stop-color="#fb923c"/></linearGradient></defs>
|
||||||
|
<g transform="translate(4 4) scale(.625)">
|
||||||
|
<rect width="64" height="64" rx="14" fill="url(#g)"/>
|
||||||
|
<g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m12 52 11-11M10 43l7-7" stroke-width="4" opacity=".72"/>
|
||||||
|
<path d="M20 47c8-2 17-7 24-16 5-6 8-13 8-18-5 0-12 3-18 8-9 7-14 16-16 26Z" fill="#fff" stroke-width="2"/>
|
||||||
|
<path d="m25 43 7 7c2-4 3-8 2-12M22 32l-8-1c2-4 5-7 9-9" fill="#fff" stroke-width="2"/>
|
||||||
|
<path d="M20 47c-1 4-1 6 1 7 3-1 5-3 7-6" fill="#fff" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="40" cy="25" r="3.5" fill="#f97316" stroke="#fff" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
<text x="54" y="34" font-family="system-ui,-apple-system,sans-serif" font-size="25" font-weight="800" fill="#1c1917">LaunchAI</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 984 B |
@@ -15,7 +15,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -55,7 +55,8 @@
|
|||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<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">T</text></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" fill="#26221f"/><g stroke="#fbf7f0" stroke-width="1"><path d="M10 11h44M10 13h44M10 51h44M10 53h44"/></g><text x="32" y="45" fill="#fbf7f0" font-family="Georgia,'Times New Roman',serif" font-size="36" font-weight="700" text-anchor="middle">L</text></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 344 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 48"><g transform="translate(0 4)"><rect width="40" height="40" fill="#26221f"/><g stroke="#fbf7f0" stroke-width=".625"><path d="M6.25 6.875h27.5M6.25 8.125h27.5M6.25 31.875h27.5M6.25 33.125h27.5"/></g><text x="20" y="28.125" fill="#fbf7f0" font-family="Georgia,'Times New Roman',serif" font-size="22.5" font-weight="700" text-anchor="middle">L</text></g><text x="52" y="30" fill="#26221f" font-family="Georgia,'Times New Roman',serif" font-size="22" font-weight="700">The AI Ledger</text><path d="M52 39h244" stroke="#26221f" stroke-width="1"/></svg>
|
||||||
|
After Width: | Height: | Size: 608 B |
@@ -15,7 +15,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -55,7 +55,8 @@
|
|||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|||||||