feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* 🤖 100 个 bottts 机器人「技能变换」变体预览页
|
||||
* 组合技能库变换(全部来自 js/skills/):
|
||||
* 1. palette-build → 10 个行业色板(primary/secondary/accent)
|
||||
* 2. gradient-suggest→ 每个主色的同色/互补/邻近渐变方向
|
||||
* 3. lucide 源图标 → 科技徽章叠加
|
||||
* 每个色板 × 5 渐变模式 × 2 徽章 = 10 变体 × 10 色板 = 100 个变体。
|
||||
* 主体保持 bottts 机器人(资源库 DiceBear 源),渐变圆底 + 亮环 + 科技徽章。
|
||||
*
|
||||
* 用法:node scripts/gen-100-bot-variants.js
|
||||
*/
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const vm = require('vm');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const OUT_DIR = path.join(ROOT, 'output', 'agent-avatars-png');
|
||||
|
||||
// —— 1. 加载技能环境 ——
|
||||
global.window = global;
|
||||
function load(rel) { vm.runInThisContext(fs.readFileSync(path.join(ROOT, rel), 'utf8'), { filename: rel }); }
|
||||
load('js/data/icons.js'); // HGB_Icons
|
||||
load('js/data/industries.js'); // HGB_Industries
|
||||
load('js/data/styles.js'); // HGB_Styles
|
||||
load('js/data/palettes.js'); // HGB_Palettes
|
||||
load('js/data/fonts.js'); // HGB_Fonts
|
||||
load('js/data/decorations.js'); // HGB_Decorations
|
||||
load('js/skills/core.js'); // HGB_Skills (const)
|
||||
load('js/skills/color.js'); // palette-build / gradient-suggest 等
|
||||
load('js/data/sources/lucide.js'); // HGB_Source_lucide(徽章图标)
|
||||
load('js/data/sources/dicebear-bottts.js');
|
||||
load('js/core/dicebear.js'); // HGB_Dicebear
|
||||
|
||||
const Skills = HGB_Skills;
|
||||
console.log('✅ 技能已加载,技能数:', Skills.all().length);
|
||||
|
||||
// —— 2. 色板:6 行业 + 4 内置色板 = 10 ——
|
||||
const inds = HGB_Industries.slice(0, 6);
|
||||
const extraPals = HGB_Palettes.slice(0, 4);
|
||||
const palettes = [
|
||||
...inds.map(ind => {
|
||||
const r = Skills.run('palette-build', { desc: '科技感 ' + ind.name, industry: ind.id });
|
||||
return { industry: ind.id, indName: ind.name, palette: r.palette || { primary: '#2563eb', secondary: '#3b82f6', accent: '#60a5fa' } };
|
||||
}),
|
||||
...extraPals.map((p, i) => ({
|
||||
industry: 'palette-' + i, indName: p.name,
|
||||
palette: { primary: p.colors[0], secondary: p.colors[1] || p.colors[0], accent: p.colors[2] || p.colors[1] || p.colors[0] }
|
||||
}))
|
||||
];
|
||||
console.log('✅ 色板数:', palettes.length, '→', palettes.map(p => p.indName).join(' / '));
|
||||
|
||||
// —— 3. HSL 工具(渐变派生)——
|
||||
function hexToHsl(hex) {
|
||||
let c = String(hex).replace('#', ''); if (c.length === 3) c = c.split('').map(x => x + x).join('');
|
||||
const r = parseInt(c.slice(0, 2), 16) / 255, g = parseInt(c.slice(2, 4), 16) / 255, b = parseInt(c.slice(4, 6), 16) / 255;
|
||||
const max = Math.max(r, g, b), min = Math.min(r, g, b); let h = 0, s = 0, l = (max + min) / 2;
|
||||
if (max !== min) {
|
||||
const d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
|
||||
else if (max === g) h = ((b - r) / d + 2) * 60;
|
||||
else h = ((r - g) / d + 4) * 60;
|
||||
}
|
||||
return { h, s: s * 100, l: l * 100 };
|
||||
}
|
||||
function hslToHex(h, s, l) {
|
||||
s /= 100; l /= 100;
|
||||
const k = n => (n + h / 30) % 12;
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = n => { const x = k(n); return Math.round(255 * (l - a * Math.max(-1, Math.min(x - 3, Math.min(9 - x, 1))))); };
|
||||
return '#' + [f(0), f(8), f(4)].map(x => x.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
function adjust(hex, dl) { const { h, s, l } = hexToHsl(hex); return hslToHex(h, s, Math.max(2, Math.min(98, l + dl))); }
|
||||
|
||||
// —— 4. 徽章图标(lucide 科技图标 10 个)——
|
||||
const lucideIcons = window['HGB_Source_lucide'].icons;
|
||||
const badgeNames = ['bot', 'sparkles', 'cpu', 'radar', 'satellite', 'zap', 'shield', 'atom', 'circuit-board', 'brain-circuit'];
|
||||
const badges = badgeNames.map(n => {
|
||||
const ic = lucideIcons.find(x => x.name === n);
|
||||
return { name: n, body: ic ? ic.svg : '<circle cx="12" cy="12" r="9"/>' };
|
||||
});
|
||||
|
||||
// —— 5. 生成 100 个变体 ——
|
||||
const MODES = [
|
||||
{ name: '主色渐变', f: (p, g) => [p.primary, p.secondary] },
|
||||
{ name: '点缀渐变', f: (p, g) => [p.primary, p.accent] },
|
||||
{ name: '深邃渐变', f: (p, g) => [p.primary, adjust(p.primary, -30)] },
|
||||
{ name: '互补渐变', f: (p, g) => [p.primary, hslToHex((g.h + 180) % 360, 60, 50)] },
|
||||
{ name: '邻近渐变', f: (p, g) => [p.primary, hslToHex((g.h + 30) % 360, 60, 50)] }
|
||||
];
|
||||
|
||||
const cards = [];
|
||||
let idx = 1;
|
||||
for (let pi = 0; pi < palettes.length; pi++) {
|
||||
const P = palettes[pi];
|
||||
const gradInfo = Skills.run('gradient-suggest', { color: P.palette.primary });
|
||||
for (let mi = 0; mi < MODES.length; mi++) {
|
||||
for (let bi = 0; bi < 2; bi++) {
|
||||
const badge = badges[(pi * 5 + mi * 2 + bi) % badges.length];
|
||||
const [c1, c2] = MODES[mi].f(P.palette, gradInfo);
|
||||
const seed = 'agent-' + idx;
|
||||
const { svg } = HGB_Dicebear.renderAvatar('bottts', seed);
|
||||
// 包裹:渐变圆底 + bottts + 亮环 + 科技徽章
|
||||
const wrapped = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="96" height="96">
|
||||
<defs>
|
||||
<linearGradient id="g${idx}" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="${c1}"/><stop offset="100%" stop-color="${c2}"/>
|
||||
</linearGradient>
|
||||
<clipPath id="c${idx}"><circle cx="120" cy="120" r="120"/></clipPath>
|
||||
</defs>
|
||||
<rect width="240" height="240" fill="url(#g${idx})" clip-path="url(#c${idx})"/>
|
||||
<g transform="translate(120 120) scale(1.28) translate(-${120 / 1.28} -${120 / 1.28})">${svg.replace(/^<svg[^>]*>|<\/svg>$/g, '')}</g>
|
||||
<circle cx="120" cy="120" r="112" fill="none" stroke="#ffffff" stroke-width="3" opacity=".35"/>
|
||||
<g transform="translate(188 188)"><circle r="24" fill="${c2}"/><g transform="translate(-12 -12) scale(1)" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${badge.body}</g></g>
|
||||
</svg>`;
|
||||
cards.push({ idx, pi, mi, bi, seed, svg: wrapped, name: `${P.indName} · ${MODES[mi].name} · ${badge.name}` });
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('✅ 变体数:', cards.length);
|
||||
|
||||
// —— 6. 唯一性校验 ——
|
||||
const { createHash } = require('crypto');
|
||||
const hashes = new Set();
|
||||
let dup = 0;
|
||||
for (const c of cards) {
|
||||
const h = createHash('md5').update(c.svg).digest('hex');
|
||||
if (hashes.has(h)) dup++;
|
||||
hashes.add(h);
|
||||
}
|
||||
console.log('唯一性:' + (cards.length - dup) + '/' + cards.length + (dup ? '(⚠️ ' + dup + ' 重复)' : ' ✅'));
|
||||
|
||||
// —— 7. 预览页 ——
|
||||
let cardHtml = '';
|
||||
for (const c of cards) {
|
||||
const n = String(c.idx).padStart(2, '0');
|
||||
cardHtml += `<label class="card" data-idx="${c.idx}">
|
||||
<input type="checkbox" class="cb" value="${c.idx}" data-name="${c.name}">
|
||||
${c.svg}
|
||||
<div class="no">#${n}</div>
|
||||
<div class="name" title="${c.name}">${c.name}</div>
|
||||
</label>\n`;
|
||||
}
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>🎨 100 个 bottts 技能变换变体 · 勾选后批量渲染</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background:#0f1115; color:#e8eaed; margin:0; padding:24px; }
|
||||
h1 { font-size:20px; margin:0 0 4px; }
|
||||
.sub { color:#9aa0a6; font-size:13px; margin-bottom:16px; }
|
||||
.toolbar { position:sticky; top:0; background:#0f1115ee; padding:10px 0; z-index:9; border-bottom:1px solid #2a2f3a; margin-bottom:16px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
|
||||
.toolbar button { background:#2563eb; color:#fff; border:none; border-radius:6px; padding:6px 14px; font-size:13px; cursor:pointer; }
|
||||
.toolbar .count { color:#8ab4f8; font-size:13px; }
|
||||
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:12px; }
|
||||
.card { background:#1b1e26; border:1px solid #2a2f3a; border-radius:12px; padding:12px 8px 10px; text-align:center; cursor:pointer; position:relative; transition:border-color .15s; }
|
||||
.card:hover { border-color:#3b82f6; }
|
||||
.card.sel { border-color:#22c55e; background:#1a2b1f; }
|
||||
.card svg { width:96px; height:96px; border-radius:50%; display:block; margin:0 auto; }
|
||||
.card .no { font-size:11px; color:#80868b; margin-top:8px; }
|
||||
.card .name { font-size:10px; color:#bdc1c6; word-break:break-all; }
|
||||
.card .cb { position:absolute; top:8px; left:8px; width:16px; height:16px; accent-color:#22c55e; }
|
||||
.out { margin-top:16px; }
|
||||
.out textarea { width:100%; height:56px; background:#1b1e26; color:#8ab4f8; border:1px solid #2a2f3a; border-radius:8px; padding:8px; font-size:12px; box-sizing:border-box; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎨 100 个 bottts 技能变换变体</h1>
|
||||
<div class="sub">技能库组合变换:palette-build 色板 × gradient-suggest 渐变 × lucide 科技徽章 · 点击勾选(默认全选)· 复制编号交渲染</div>
|
||||
<div class="toolbar">
|
||||
<button onclick="sel(true)">全选</button>
|
||||
<button onclick="sel(false)">清空</button>
|
||||
<span class="count" id="count">已选 0/100</span>
|
||||
<button onclick="copySel()">复制已选编号</button>
|
||||
</div>
|
||||
<div class="grid">${cardHtml}</div>
|
||||
<div class="out">
|
||||
<textarea id="selText" placeholder="已选编号将显示在这里…" readonly></textarea>
|
||||
</div>
|
||||
<script>
|
||||
const cards = document.querySelectorAll('.card');
|
||||
const countEl = document.getElementById('count');
|
||||
const selText = document.getElementById('selText');
|
||||
function update() {
|
||||
const sel = [...cards].filter(c => c.querySelector('.cb').checked);
|
||||
cards.forEach(c => c.classList.toggle('sel', c.querySelector('.cb').checked));
|
||||
countEl.textContent = '已选 ' + sel.length + '/' + cards.length;
|
||||
selText.value = sel.map(c => c.dataset.idx).join(',');
|
||||
}
|
||||
function sel(v) { cards.forEach(c => c.querySelector('.cb').checked = v); update(); }
|
||||
function copySel() {
|
||||
if (!selText.value) return alert('请先勾选头像');
|
||||
navigator.clipboard.writeText(selText.value).then(() => alert('已复制编号:' + selText.value)).catch(() => alert(selText.value));
|
||||
}
|
||||
cards.forEach(c => c.addEventListener('click', e => {
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
const cb = c.querySelector('.cb'); cb.checked = !cb.checked; update();
|
||||
}));
|
||||
sel(true);
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
fs.writeFileSync(path.join(OUT_DIR, 'preview-100-variants.html'), html, 'utf8');
|
||||
console.log('🎉 已生成 100 个技能变换变体 → output/agent-avatars-png/preview-100-variants.html');
|
||||
Reference in New Issue
Block a user