feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能

This commit is contained in:
sgp
2026-10-03 21:44:18 +08:00
commit 9e222877ea
497 changed files with 43611 additions and 0 deletions
+224
View File
@@ -0,0 +1,224 @@
/**
* 画个标 · 技能库:AI 工作流类(ai)
* 注册:HGB_Skills.register([...])
*/
(function () {
'use strict';
HGB_Skills.register([
{
id: 'ai-workflow',
name: 'AI 品牌设计工作流',
category: 'ai',
emoji: '🤖',
scene: '从品牌简报 → AI 生成 → 校验 → 迭代的全流程设计',
summary: '借鉴 9 阶段专业工作流(需求采集/方向/配色策略/排版/交付/迭代),驱动 DeepSeek 完成智能设计并在渲染层自动校验修正。',
params: [
{ name: 'brand', type: 'string', required: true, desc: '品牌名' },
{ name: 'industry', type: 'string', required: true, desc: '行业 id' },
{ name: 'desc', type: 'string', required: false, desc: '品牌描述' }
],
source: 'GitHub: atypica-ai/logo-design-skill(9 阶段工作流)',
run: function (args) {
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const brand = String(args.brand || '');
const desc = String(args.desc || '');
const stages = [
{ phase: '1 需求采集', out: `${brand} / ${ind.name} / ${desc || '行业默认'}` },
{ phase: '2 方向探索', out: '由 DeepSeek 基于品牌描述确定设计方向(AI 生成 5 套)' },
{ phase: '3 图标选择', out: 'AI 从内置库按语义匹配图标' },
{ phase: '4 配色策略', out: '行业配色 + 描述色系偏好 + WCAG 对比度校验' },
{ phase: '5 排版实施', out: '布局模板 + 字号自适应 + 构图审计' },
{ phase: '6 交付迭代', out: '方案库一屏展示 + 换配色/布局变体 + 导出' }
];
return { workflow: stages, model: HGB_AI.getModel(), keyConfigured: HGB_AI.hasKey() };
},
test: function () {
return { model: HGB_AI.getModel(), stages: this.run({ brand: '远渡教育', industry: 'edu' }).workflow.length };
}
},
{
id: 'brand-brief',
name: '品牌简报解析',
category: 'ai',
emoji: '📋',
scene: '解析品牌描述,提炼设计关键词与方向',
summary: '将用户自然语言品牌描述解析为结构化简报:行业倾向、色系偏好、气质关键词、适用场景,供 AI 生成与人工确认。',
params: [
{ name: 'desc', type: 'string', required: true, desc: '品牌描述(自然语言)' }
],
source: '自研(关键词启发式解析)',
run: function (args) {
const desc = String(args.desc || '');
const D = desc.toLowerCase();
const indMap = [
{ kw: ['教育', '学习', '培训', '学校'], ind: 'edu' },
{ kw: ['餐饮', '美食', '烘焙', '咖啡', '餐厅'], ind: 'food' },
{ kw: ['医疗', '健康', '诊所', '药'], ind: 'health' },
{ kw: ['科技', '软件', '互联网', 'AI', '数码'], ind: 'tech' },
{ kw: ['运动', '健身', '体育'], ind: 'sport' },
{ kw: ['金融', '投资', '银行', '财富'], ind: 'finance' }
];
const ind = indMap.find(m => m.kw.some(k => desc.includes(k)));
const toneMap = [
{ kw: ['高端', '奢华', '优雅', '专业'], tone: '高端专业' },
{ kw: ['活力', '年轻', '热情', '潮流'], tone: '年轻活力' },
{ kw: ['温馨', '治愈', '亲切', '温暖'], tone: '温馨亲切' },
{ kw: ['科技', '未来', '极简', '现代'], tone: '科技极简' },
{ kw: ['自然', '环保', '清新', '绿色'], tone: '自然清新' }
];
const tone = toneMap.find(m => m.kw.some(k => desc.includes(k)));
const colorMap = [
{ kw: ['蓝'], color: '蓝色系' }, { kw: ['红'], color: '红色系' },
{ kw: ['绿'], color: '绿色系' }, { kw: ['橙', '橘'], color: '橙色系' },
{ kw: ['紫'], color: '紫色系' }, { kw: ['金', '黄'], color: '金色/黄色系' },
{ kw: ['黑', '灰'], color: '黑灰系' }, { kw: ['白'], color: '白色系' }
];
const color = colorMap.find(m => m.kw.some(k => desc.includes(k)));
return {
desc: desc || '(空)',
industry: ind ? ind.ind : '(未识别,默认通用)',
tone: tone ? tone.tone : '(未识别,默认通用)',
color: color ? color.color : '(未指定,智能匹配)',
keywords: desc.split(/[,,。\s]+/).filter(Boolean).slice(0, 6)
};
},
test: function () {
return this.run({ desc: '一家高端优雅的蓝色科技教育品牌' });
}
},
{
id: 'slogan-gen',
name: '品牌标语生成',
category: 'ai',
emoji: '💬',
scene: '基于品牌名与行业生成英文品牌标语',
summary: '按行业气质与品牌名生成 3 条英文 Slogan 候选(形容词 + 品牌名 + 场景短语结构),附中文释义,供副标题/宣传使用。',
params: [
{ name: 'brand', type: 'string', required: true, desc: '品牌名(英文或拼音)' },
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: '自研(标语模板引擎)',
run: function (args) {
const brand = String(args.brand || '');
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const styleMap = {
edu: { adj: ['Bright', 'Wise', 'Future-ready'], phrase: 'Learn Beyond Limits', zh: '学无止境' },
food: { adj: ['Fresh', 'Tasty', 'Warm'], phrase: 'Taste the Moment', zh: '品味当下' },
health: { adj: ['Gentle', 'Careful', 'Vital'], phrase: 'Care for Life', zh: '守护生命' },
tech: { adj: ['Smart', 'Swift', 'Next-gen'], phrase: 'Innovate Forward', zh: '创新向前' },
sport: { adj: ['Strong', 'Swift', 'Relentless'], phrase: 'Push Your Limits', zh: '突破极限' },
finance: { adj: ['Secure', 'Stable', 'Wealth-wise'], phrase: 'Grow with Trust', zh: '信任成长' }
};
const st = styleMap[ind.id] || { adj: ['Great', 'Remarkable', 'Beyond'], phrase: 'Make It Stand Out', zh: '与众不同' };
const en = /^[a-zA-Z0-9\s.-]+$/.test(brand) ? brand.toUpperCase() : (brand || 'BRAND');
const slogans = st.adj.map(a => a + ' ' + en + ' — ' + st.phrase);
return { brand: en, industry: ind.name, slogans, zh: st.zh };
},
test: function () {
return this.run({ brand: 'NEXUS', industry: 'tech' });
}
},
{
id: 'prompt-craft',
name: 'AI 提示词工坊',
category: 'ai',
emoji: '🧪',
scene: '把品牌需求编译成可直接提交 DeepSeek 的提示词',
summary: '将品牌名/行业/描述/偏好转译为结构化 AI 提示词(风格/布局/图标/字体),与内置 AI 生成器同源规范,可复制使用。',
params: [
{ name: 'brand', type: 'string', required: true, desc: '品牌名' },
{ name: 'industry', type: 'string', required: false, desc: '行业 id' },
{ name: 'desc', type: 'string', required: false, desc: '品牌描述' },
{ name: 'styleId', type: 'string', required: false, desc: '风格 id(可选)' }
],
source: '自研(与 HGB_AIDesigner.buildPrompt 同源)',
run: function (args) {
const brand = String(args.brand || '');
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const style = HGB_Styles.find(s => s.id === args.styleId) || null;
const prompt = [
'你是资深品牌设计师。为品牌「' + brand + '」设计一套 LOGO。',
'行业:' + ind.name,
'品牌描述:' + (args.desc || '无'),
'请输出 JSON:{styleId, layoutId, iconId(可选), fontFamily, 主色建议}',
'约束:极简、充足负空间、可缩放至 64px 仍清晰、不使用渐变描边。'
].join('\n');
return {
brand, industry: ind.name, style: style ? style.name : '(自动)',
prompt,
model: HGB_AI.getModel(),
copyable: true
};
},
test: function () {
const r = this.run({ brand: '远渡教育', industry: 'edu', desc: '高端教育品牌' });
return { lines: r.prompt.split('\n').length, model: r.model, style: r.style };
}
},
{
id: 'ai-iterate',
name: 'AI 迭代方案',
category: 'ai',
emoji: '🔄',
scene: '根据审计结果生成方案的迭代改进步骤',
summary: '汇总构图/配色/字体/图标等技能审计结论,转化为按优先级排序的迭代动作,指导用户快速改进方案。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象' },
{ name: 'audits', type: 'object', required: false, desc: '各技能审计结果 {skillId: result}' }
],
source: '自研(审计 → 迭代动作编排)',
run: function (args) {
const audits = args.audits || {};
const steps = [];
for (const [sid, r] of Object.entries(audits)) {
if (r && r.pass === false && (r.verdict || r.issues)) {
steps.push({ skill: sid, action: r.verdict || (Array.isArray(r.issues) ? r.issues.join(';') : '见审计详情'), priority: sid === 'composition' ? '高' : '中' });
}
}
if (!steps.length) steps.push({ skill: '-', action: '各项审计均通过,可尝试换配色/布局体验更多变体', priority: '低' });
return { total: steps.length, steps, order: '按「高→低」优先级逐条执行后重新生成预览' };
},
test: function () {
return this.run({ audits: { composition: { pass: false, verdict: '存在元素重叠,请加大间距' }, contrast: { pass: true }, spacing: { pass: false, issues: ['留白不足'] } } });
}
},
{
id: 'concept-words',
name: '品牌概念词联想',
category: 'ai',
emoji: '💡',
scene: '从品牌名 + 行业发散概念关键词(供 AI Prompt / 图标检索使用)',
summary: '基于行业联想词库 + 品牌名首字拆解,输出名词/特质/场景三类概念词:直接喂给 AI Prompt 或图标检索,让生成方向更聚焦。',
params: [
{ name: 'brand', type: 'string', required: true, desc: '品牌名' },
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: '自研(行业概念词库 + 词根拆解)',
run: function (args) {
const brand = String(args.brand || '');
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const BANK = {
edu: { noun: ['书本', '毕业帽', '铅笔', '阶梯', '灯塔'], trait: ['专业', '求知', '成长'], scene: ['教室', '图书馆'] },
food: { noun: ['餐具', '锅铲', '食材', '火焰', '餐盘'], trait: ['温暖', '食欲', '烟火气'], scene: ['厨房', '餐桌'] },
health: { noun: ['十字', '爱心', '叶子', '药丸', '听诊器'], trait: ['健康', '治愈', '可靠'], scene: ['诊室', '自然'] },
tech: { noun: ['芯片', '齿轮', '闪电', '终端', '云朵'], trait: ['创新', '极客', '高效'], scene: ['机房', '代码'] },
sport: { noun: ['奖杯', '速度', '球', '火焰', '奖牌'], trait: ['活力', '拼搏', '突破'], scene: ['赛场', '跑道'] },
finance: { noun: ['盾牌', '钱币', '上升线', '钥匙', '建筑'], trait: ['稳健', '财富', '信任'], scene: ['银行', '投资'] }
};
const hit = BANK[ind.id] || { noun: ['星星', '圆环', '翅膀', '箭头', '闪电'], trait: ['创意', '独特', '进取'], scene: ['灵感', '未来'] };
const cjk = brand.match(/[\u4e00-\u9fa5]/g);
const initial = cjk ? cjk[0] : (brand.slice(0, 1) || '').toUpperCase();
return {
brand, industry: ind.name, initial,
noun: hit.noun, trait: hit.trait, scene: hit.scene,
suggested: hit.noun.slice(0, 3).concat(hit.trait.slice(0, 2)).join('、'),
note: '概念词可直接用于 AI Prompt(主题/气质/场景)或图标检索关键词'
};
},
test: function () {
return { food: this.run({ brand: '香满堂', industry: 'food' }).suggested, tech: this.run({ brand: '云帆', industry: 'tech' }).noun.length };
}
}
]);
})();
+345
View File
@@ -0,0 +1,345 @@
/**
* 画个标 · 技能库:配色类(color)
* 注册:HGB_Skills.register([...])
*/
(function () {
'use strict';
const U = HGB_Skills.utils;
const { lum, contrast } = U;
HGB_Skills.register([
{
id: 'contrast-check',
name: '对比度校验',
category: 'color',
emoji: '🎨',
scene: '配色合规检查(WCAG 2.1 AA)',
summary: '计算主色与背景的对比度并对照 WCAG AA(正文 ≥4.5:1,大文本/UI ≥3:1),深色主色配深色背景时给出调亮建议。',
params: [
{ name: 'foreground', type: 'string', required: true, desc: '前景色(主色)HEX' },
{ name: 'background', type: 'string', required: true, desc: '背景色 HEX' }
],
source: 'GitHub: stagius/flowize DESIGN_SYSTEM.md(WCAG AA 体系)',
run: function (args) {
const fg = args.foreground || '#1B4F72';
const bg = args.background || '#FFFFFF';
const ratio = contrast(fg, bg);
return {
foreground: fg, background: bg, ratio,
passLarge: ratio >= 3, passText: ratio >= 4.5,
verdict: ratio >= 4.5 ? 'AA 达标(正文可用)' : (ratio >= 3 ? '仅限大文本/UI' : '不达标,建议加深前景或调亮背景')
};
},
test: function () {
return {
light: this.run({ foreground: '#1B4F72', background: '#FFFFFF' }),
dark: this.run({ foreground: '#0F172A', background: '#0F172A' })
};
}
},
{
id: 'color-harmony',
name: '行业配色推荐',
category: 'color',
emoji: '🌈',
scene: '按行业与品牌气质推荐和谐配色',
summary: '从行业推荐配色与全局配色库中筛选贴合品牌描述的方案,返回主色/辅色/点缀色与适用场景。',
params: [
{ name: 'industry', type: 'string', required: true, desc: '行业 id(edu/food/health/tech/sport/finance)' },
{ name: 'desc', type: 'string', required: false, desc: '品牌描述(可含色系偏好)' }
],
source: 'GitHub: design-resources-for-developers(配色资源索引)+ 自研行业库',
run: function (args) {
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const desc = String(args.desc || '');
const pool = [...(ind.palettes || []), ...HGB_Palettes];
const keywordMap = [
{ kw: ['蓝', '科技', '理性'], field: 'colors', idx: 0, hint: '科技蓝系' },
{ kw: ['橙', '暖', '美食'], field: 'colors', idx: 0, hint: '温暖暖色系' },
{ kw: ['绿', '健康', '自然'], field: 'colors', idx: 0, hint: '清新绿系' }
];
const matched = [];
for (const m of keywordMap) {
if (m.kw.some(k => desc.includes(k))) {
const pal = pool.find(p => p.colors && p.colors[0] && lum(p.colors[0]) > 0.05) || pool[0];
if (pal && !matched.includes(pal.name)) matched.push(pal.name + '(' + m.hint + ')');
}
}
return {
industry: ind.name,
desc: desc || '(无偏好)',
recommend: pool.slice(0, 3).map(p => p.name),
match: matched
};
},
test: function () {
return this.run({ industry: 'food', desc: '温暖治愈的橙色系烘焙品牌' });
}
},
{
id: 'palette-build',
name: '品牌色板生成',
category: 'color',
emoji: '🖌️',
scene: '从品牌描述生成主/辅/点缀三级色板',
summary: '解析品牌描述中的色系关键词,从配色库提取一套 主色+辅色+点缀色 的完整色板,并附明暗度与搭配说明。',
params: [
{ name: 'desc', type: 'string', required: true, desc: '品牌描述(含色系/气质偏好)' },
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: '自研(基于行业色板 + 关键词色系匹配)',
run: function (args) {
const desc = String(args.desc || '');
const ind = HGB_Industries.find(x => x.id === args.industry);
const pool = [...(ind ? ind.palettes || [] : []), ...HGB_Palettes];
const toneMap = [
{ kw: ['蓝', '科技', '理性', '沉稳'], idx: 0 },
{ kw: ['橙', '暖', '美食', '活力'], idx: 0 },
{ kw: ['绿', '健康', '自然', '环保'], idx: 0 },
{ kw: ['红', '热烈', '喜庆', '激情'], idx: 0 },
{ kw: ['紫', '优雅', '神秘', '高端'], idx: 0 },
{ kw: ['金', '贵气', '奢华', '复古'], idx: 0 }
];
let pal = null;
for (const t of toneMap) {
if (t.kw.some(k => desc.includes(k))) {
pal = pool.find(p => p.colors && p.colors.length >= 3);
if (pal) break;
}
}
pal = pal || pool.find(p => p.colors && p.colors.length >= 3) || pool[0] || { colors: ['#1B4F72', '#2E86C1', '#F5B041'] };
const c = pal.colors;
const palette = { primary: c[0], secondary: c[1] || c[0], accent: c[2] || c[1] || c[0] };
const bright = lum(palette.primary) > 0.5;
return {
desc: desc || '(无偏好)',
palette,
contrast: Math.round(contrast(palette.primary, '#FFFFFF') * 100) / 100,
usage: `${palette.primary} 主色(品牌核心)/ ${palette.secondary} 辅色(次层级)/ ${palette.accent} 点缀色(CTA/高亮)`,
tone: bright ? '亮色系' : '深色系',
note: bright ? '建议搭配深色文字保证对比度' : '建议搭配浅色背景凸显品牌色'
};
},
test: function () {
return this.run({ desc: '科技感十足的蓝色软件品牌' });
}
},
{
id: 'gradient-suggest',
name: '渐变搭配建议',
category: 'color',
emoji: '🌅',
scene: '品牌色渐变色相搭配推荐',
summary: '基于品牌主色推荐和谐渐变色相(同色系渐变/互补渐变/邻近渐变三类),并给出渐变方向与使用场景。',
params: [
{ name: 'color', type: 'string', required: true, desc: '主色 HEX' }
],
source: '自研(基于色相环渐变理论)',
run: function (args) {
const color = String(args.color || '#1B4F72').replace('#', '');
if (color.length !== 6) return { error: '需提供 6 位 HEX 色值' };
const r = parseInt(color.slice(0, 2), 16), g = parseInt(color.slice(2, 4), 16), b = parseInt(color.slice(4, 6), 16);
const max = Math.max(r, g, b), min = Math.min(r, g, b);
const l = (max + min) / 2 / 255;
let h = 0;
if (max !== min) {
const 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;
}
const variants = [
{ name: '同色系渐变', hue: h, note: '由深到浅同色相,稳重安全,适合企业品牌' },
{ name: '互补渐变', hue: (h + 180) % 360, note: '与主色互补,视觉冲击强,适合活动/电商' },
{ name: '邻近渐变', hue: (h + 30) % 360, note: '邻近色相过渡自然,柔和现代,适合科技品牌' }
];
return { color: '#' + color, hue: Math.round(h), lightness: Math.round(l * 100) + '%', variants };
},
test: function () {
return this.run({ color: '#1B4F72' });
}
},
{
id: 'color-blind-check',
name: '色盲友好检查',
category: 'color',
emoji: '🧑‍🦱',
scene: '红绿色盲场景下的主色与背景可区分度检查',
summary: '基于常见红绿色盲(约 8% 男性)感知模型,检查前景与背景是否同属红绿系且对比不足,给出改蓝/紫或加大明度差的建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {bg, elements}' }
],
source: '自研(红绿色盲感知启发式)',
run: function (args) {
const plan = args.plan || {};
const bg = plan.bg || '#FFFFFF';
const els = plan.elements || [];
const icon = els.find(el => el.type === 'icon' && el.iconId && !['ring', 'deco-bar'].includes(el.iconId));
const fg = (icon && icon.stroke) || '#333333';
const hueOf = (hex) => {
const c = String(hex || '#FFF').replace('#', '');
if (c.length !== 6) return -1;
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), d = max - min;
if (d === 0) return -1;
let h;
if (max === r) h = ((g - b) / d) % 6;
else if (max === g) h = (b - r) / d + 2;
else h = (r - g) / d + 4;
return ((h * 60) % 360 + 360) % 360;
};
const inRG = (h) => h >= 0 && (h <= 60 || (h >= 75 && h <= 170) || h >= 320); // 红/橙/绿近似区
const h1 = hueOf(fg), h2 = hueOf(bg);
const bothRG = inRG(h1) && inRG(h2);
const ratio = contrast(fg, bg);
const risky = bothRG && ratio < 3;
return {
pass: !risky,
foreground: fg, background: bg,
fgFamily: h1 < 0 ? '中性色' : (inRG(h1) ? '红绿系' : '非红绿系'),
contrast: ratio,
verdict: risky ? '前景与背景同属红绿系且对比不足(' + ratio + ':1),红绿色盲人群难以区分' : (bothRG ? '虽同为红绿系,但明度对比足够(' + ratio + ':1)' : '前景非红绿系或与背景区分良好'),
suggest: risky ? '点缀/主色建议改用蓝/紫色系,或加大明度差至 3:1 以上' : '无需调整'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '色盲测试' });
return { safe: this.run({ plan: { bg: '#FFFFFF', elements: [{ type: 'icon', iconId: 'x', stroke: '#1B4F72' }] } }).pass, risky: this.run({ plan: { bg: '#2E7D32', elements: [{ type: 'icon', iconId: 'x', stroke: '#B71C1C' }] } }).pass };
}
},
{
id: 'color-psychology',
name: '色彩心理学推荐',
category: 'color',
emoji: '🧠',
scene: '按行业与情感目标推荐品牌色彩心理学配色',
summary: '借鉴色彩心理学情感色系(红=热情/蓝=专业/绿=自然/金=高端等),按行业推荐最能传达品牌情感的主色与情感标签。',
params: [
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: 'GitHub: color-psychology 色彩心理学资料 + 自研行业映射',
run: function (args) {
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const map = {
edu: [{ color: '#2F5FE3', emotion: '专业与信赖', hex: '蓝色' }],
food: [{ color: '#E8590C', emotion: '食欲与热情', hex: '橙色' }],
health: [{ color: '#2FA36B', emotion: '健康与治愈', hex: '绿色' }],
tech: [{ color: '#1F7A8C', emotion: '创新与科技', hex: '青蓝' }],
sport: [{ color: '#E63946', emotion: '活力与冲击', hex: '红色' }],
finance: [{ color: '#1B4F72', emotion: '稳重与财富', hex: '深蓝' }]
};
const hit = map[ind.id] || [{ color: '#5B21B6', emotion: '创意与神秘', hex: '紫色' }];
return {
industry: ind.name,
primary: hit[0].color, emotion: hit[0].emotion, family: hit[0].hex,
note: '「主色 + 中性灰 + 少量点缀色」的三角配色最稳妥,避免满屏高饱和'
};
},
test: function () {
return { food: this.run({ industry: 'food' }).primary, tech: this.run({ industry: 'tech' }).emotion };
}
},
{
id: 'monochrome-check',
name: '单色灰度适配',
category: 'color',
emoji: '⬛',
scene: 'LOGO 转为单色(黑白/传真/印章)后的辨识度检查',
summary: '用亮度对比判断主体与背景在灰度下是否仍可辨识:单色印刷、传真、水印场景需要足够的明暗对比。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements, bg}' }
],
source: '自研(灰度对比度规则)',
run: function (args) {
const plan = args.plan || {};
const bg = plan.bg || '#FFFFFF';
const els = plan.elements || [];
const fgEl = els.find(el => (el.type === 'icon' && el.iconId && !['ring', 'deco-bar'].includes(el.iconId)) || (el.type === 'text'));
const fg = (fgEl && (fgEl.stroke || fgEl.fill)) || '#333333';
const c = contrast(fg, bg);
const pass = c >= 3;
return {
pass, contrast: c,
grayLum: Math.round(lum(fg) * 100) + '%',
verdict: pass ? '灰度对比度 ' + c + ':1,单色场景可辨识' : '灰度对比度 ' + c + ':1,单色场景易糊,建议加大明暗差',
suggest: pass ? '无需调整' : '主体与背景明暗相近,单色印刷时建议加深主体或换浅色背景'
};
},
test: function () {
return { dark: this.run({ plan: { bg: '#FFFFFF', elements: [{ type: 'icon', iconId: 'x', stroke: '#111111' }] } }).pass, light: this.run({ plan: { bg: '#FFFFFF', elements: [{ type: 'icon', iconId: 'x', stroke: '#EEEEEE' }] } }).pass };
}
},
{
id: 'wcag-contrast',
name: 'WCAG 无障碍对比度',
category: 'color',
emoji: '♿',
scene: '品牌色与背景的 WCAG 无障碍对比度评级',
summary: '计算前景/背景颜色的 WCAG 对比度并评级:普通文本需 ≥4.5:1(AA)、大字号 ≥3:1,AAA 需 7:1/4.5:1,输出达标级别与调色方向。',
params: [
{ name: 'fg', type: 'string', required: true, desc: '前景色(#RRGGBB)' },
{ name: 'bg', type: 'string', required: true, desc: '背景色(#RRGGBB)' },
{ name: 'size', type: 'string', required: false, desc: 'text 普通文本 / large 大字号(≥24px 或 18.66px 加粗)' }
],
source: 'W3C WCAG 2.1 对比度规范 + 自研评级',
run: function (args) {
const fg = args.fg || '#333333', bg = args.bg || '#FFFFFF';
const c = contrast(fg, bg);
const large = args.size === 'large';
const need = large ? { aa: 3, aaa: 4.5 } : { aa: 4.5, aaa: 7 };
const level = c >= need.aaa ? 'AAA' : (c >= need.aa ? 'AA' : '不达标');
const pass = c >= need.aa;
return {
pass, ratio: c, level, large,
verdict: '对比度 ' + c + ':1,' + (large ? '大字号' : '普通文本') + ' 达' + level + (pass ? '' : ',未达 AA(需 ' + need.aa + ':1)'),
suggest: pass ? '对比度符合 WCAG,无障碍友好' : '建议加深前景或提亮背景,拉开明暗差(当前 ' + c + ':1)'
};
},
test: function () {
return { good: this.run({ fg: '#111111', bg: '#FFFFFF' }).level, bad: this.run({ fg: '#CCCCCC', bg: '#FFFFFF' }).pass, large: this.run({ fg: '#777777', bg: '#FFFFFF', size: 'large' }).pass };
}
},
{
id: 'neutral-palette',
name: '中性色系统',
category: 'color',
emoji: '⬜',
scene: '为 LOGO/VI 推荐黑白灰中性色系统(单色/双色场景)',
summary: '基于品牌主色亮度生成配套中性灰阶(黑/深灰/中灰/浅灰/白),并给出双色与单色场景的搭档建议:主色 + 中性色构成极简双色系统,单色印刷仍保留明暗层次。',
params: [
{ name: 'color', type: 'string', required: false, desc: '品牌主色 HEX(默认 #1B4F72)' },
{ name: 'usage', type: 'string', required: false, desc: 'mono 单色 / duo 双色 / full 完整系统(默认 duo)' }
],
source: '自研(中性色阶 + 明度对比规范)',
run: function (args) {
const c = String(args.color || '#1B4F72').replace('#', '');
const base = /^[0-9a-fA-F]{6}$/.test(c) ? c : '1B4F72';
const usage = String(args.usage || 'duo');
const l = lum('#' + base);
const scale = [
{ name: '纯黑', hex: '#000000' },
{ name: '深灰', hex: '#333333' },
{ name: '中灰', hex: '#757575' },
{ name: '浅灰', hex: '#E0E0E0' },
{ name: '纯白', hex: '#FFFFFF' }
];
const mates = scale.filter(s => contrast(s.hex, '#' + base) >= 3);
const duo = mates.length ? mates : scale;
const primary = '#' + base;
return {
primary, tone: l > 0.5 ? '亮色系' : '深色系',
scale,
duo: { primary, neutral: duo[0].hex, note: '主色 + 「' + duo[0].name + '」构成双色系统,单色印刷/传真场景主体依然清晰' },
mono: { note: '单色场景用纯黑/纯白表现明暗层次,主色转灰度时保留 ≥3:1 明暗差(配合「单色灰度适配」技能复核)' },
full: { note: '完整系统 = 主色 + 5 级灰阶 + 黑/白:灰阶用于次层级信息,主色仅作强调,避免满屏高饱和' },
usage: usage === 'mono' ? '单色' : (usage === 'duo' ? '双色' : '完整系统'),
tip: '中性色是品牌色的「背景板」:浅灰作底色、深灰作正文、黑/白作极简标识,主色只出现在关键图形上'
};
},
test: function () {
const r = this.run({ color: '#1B4F72', usage: 'duo' });
return { scale: r.scale.length, duo: r.duo.neutral, mono: r.mono.note !== '' };
}
}
]);
})();
+144
View File
@@ -0,0 +1,144 @@
/**
* 画个标 · 技能资源库(核心:注册表 + 通用工具)
* 每个技能采用标准化标识:id / name / category / emoji / scene / summary / params / source / run / test
* 分类:layout(构图) color(配色) typography(字体) icon(图标) ai(智能工作流) presentation(展示) export(导出)
*
* 技能按分类拆分文件(js/skills/{category}.js),各自调用 HGB_Skills.register([...]) 注册:
* layout.js color.js typography.js icon.js ai.js presentation.js export.js
*/
const HGB_Skills = (function () {
'use strict';
/* ---------- 通用工具 ---------- */
/** 颜色亮度(0-1) */
function lum(hex) {
const c = String(hex || '#FFFFFF').replace('#', '');
if (c.length !== 6) return 0.9;
const r = parseInt(c.slice(0, 2), 16) / 255;
const g = parseInt(c.slice(2, 4), 16) / 255;
const b = parseInt(c.slice(4, 6), 16) / 255;
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
/** WCAG 对比度 */
function contrast(a, b) {
const la = lum(a), lb = lum(b);
const hi = Math.max(la, lb), lo = Math.min(la, lb);
return Math.round(((hi + 0.05) / (lo + 0.05)) * 100) / 100;
}
/** 从内置图标库搜索图标(懒加载适配:优先搜已加载源,其次索引;返回带库前缀 id 的图标) */
function searchIcon(keyword) {
const kw = String(keyword || '').toLowerCase();
if (!kw) return [];
const out = [];
const seen = new Set();
const push = (id, source) => {
if (!seen.has(id)) { seen.add(id); out.push({ id, source }); }
};
// 已加载源(含 svg,能力最全)
for (const hit of HGB_IconLib.searchLoaded(kw, 8)) push(hit.id, hit.source);
// 索引兜底(轻量全量 id/name,无需 svg)
if (out.length < 8) {
for (const hit of HGB_IconLib.searchIndex(kw, 8)) push(hit.id, hit.source);
}
return out.slice(0, 8);
}
/** 估算文字元素的实际渲染宽度(中文全角,西文 0.55,计入字距) */
function estTextW(el) {
const t = String(el.text || '');
const cn = (t.match(/[\u4e00-\u9fa5]/g) || []).length;
const fs = el.fontSize || 24;
const sp = el.letterSpacing || 0;
return cn * fs + (t.length - cn) * fs * 0.55 + Math.max(0, t.length - 1) * sp;
}
/** 元素实际包围盒(文字按估算宽度,水平居中) */
function elBox(el) {
if (el.type === 'text') {
const w = estTextW(el);
return { x: el.x - w / 2, y: el.y - (el.h || 40) / 2, w, h: el.h || 40 };
}
return { x: el.x, y: el.y, w: el.w, h: el.h };
}
/* ---------- 设计流程步骤(2026-08-15:技能按「设计流水线」组织) ---------- */
const DESIGN_STEPS = [
{ step: 1, id: 'insight', name: '品牌洞察', emoji: '🔍', desc: '把品牌描述提炼成关键词与方向,指导后续生成' },
{ step: 2, id: 'create', name: '方案生成', emoji: '🎨', desc: '产出色板/字体/布局/图标等设计建议,生成多套候选方案' },
{ step: 3, id: 'review', name: '方案评审', emoji: '🔬', desc: '对每套方案统一体检(构图/配色/字体/图标),输出检查报告' },
{ step: 4, id: 'iterate', name: '迭代优化', emoji: '🔄', desc: '按评审结果自动迭代,生成改进方案' },
{ step: 5, id: 'deliver', name: '交付导出', emoji: '📦', desc: '按用途导出多格式文件,输出交付清单' }
];
/** 技能 → 设计流程步骤(1-5)映射(按 id 显式指定) */
const SKILL_STEPS = {
// ① 品牌洞察
'ai-workflow': 1, 'brand-brief': 1, 'slogan-gen': 1, 'prompt-craft': 1, 'concept-words': 1,
// ② 方案生成
'layout-choice': 2, 'palette-build': 2, 'color-harmony': 2, 'gradient-suggest': 2, 'color-psychology': 2,
'font-pairing': 2, 'font-weight-choose': 2, 'icon-match': 2, 'icon-search-cn': 2, 'showcase-bg': 2,
'neutral-palette': 2,
// ③ 方案评审(检查类)
'composition': 3, 'align-check': 3, 'negative-space': 3, 'spacing-check': 3, 'proportion-check': 3,
'visual-balance': 3, 'focal-point': 3, 'rule-of-thirds': 3, 'circle-crop-safety': 3,
'contrast-check': 3, 'wcag-contrast': 3, 'color-blind-check': 3, 'monochrome-check': 3,
'font-hierarchy': 3, 'text-fit': 3, 'tiny-readable': 3, 'letter-spacing-check': 3, 'line-break': 3,
'cjk-mix-check': 3,
'icon-consistency': 3, 'icon-tone': 3, 'icon-count': 3, 'icon-text-ratio': 3, 'icon-padding': 3,
'stroke-consistency': 3,
'print-safety': 3,
// ④ 迭代优化
'ai-iterate': 4,
// ⑤ 交付导出
'format-choose': 5, 'export-sizing': 5, 'export-multi': 5, 'export-avatar': 5, 'favicon-kit': 5,
'transparent-check': 5, 'mockup-scene': 5, 'vi-checklist': 5, 'launch-kit': 5,
'file-naming': 5, 'brand-story': 5
};
/** 技能性质:gen 生成建议 / check 方案检查 / deliver 交付导出 */
function kind(id) {
const st = SKILL_STEPS[id];
if (st === 3) return 'check';
if (st === 5) return (id === 'transparent-check' || id === 'print-safety') ? 'check' : 'deliver';
return 'gen';
}
/* ---------- 注册表 API ---------- */
const skills = [];
function stepOf(id) { return SKILL_STEPS[id] || 2; } // 技能所属流程步骤(未映射默认方案生成)
/** 技能适用工作区:logo(LOGO) / avatar(头像) / poster(海报);未显式标注按分类默认 */
function workspacesOf(id) {
const s = get(id);
if (!s) return ['logo', 'avatar', 'poster'];
if (s.workspaces) return s.workspaces;
if (s.category === 'typography' || id === 'slogan-gen') return ['logo', 'poster']; // 头像纯图形无文字(slogan 为文字类)
return ['logo', 'avatar', 'poster'];
}
/** 某工作区可用的全部技能 */
function forWorkspace(ws) {
return skills.filter(s => workspacesOf(s.id).includes(ws));
}
function byStep(step, ws) { return (ws ? forWorkspace(ws) : skills).filter(s => SKILL_STEPS[s.id] === step); } // 某步骤的全部技能
function steps() { return DESIGN_STEPS; } // 设计流程步骤元数据
function register(defs) {
for (const d of (defs || [])) skills.push(d);
return skills.length;
}
function all() { return skills; }
function categories() {
const map = {};
for (const s of skills) map[s.category] = (map[s.category] || 0) + 1;
return Object.entries(map).map(([id, n]) => ({ id, count: n }));
}
function get(id) { return skills.find(s => s.id === id) || null; }
function run(id, args) {
const s = get(id);
if (!s) return { error: '技能不存在: ' + id };
try { return s.run(args || {}); }
catch (e) { return { error: '技能执行失败: ' + e.message }; }
}
function test(id) {
const s = get(id);
if (!s) return { error: '技能不存在: ' + id };
try { return s.test ? s.test() : { note: '该技能未提供自测' }; }
catch (e) { return { error: '技能自测失败: ' + e.message }; }
}
return { register, all, categories, get, run, test, steps, stepOf, byStep, workspacesOf, forWorkspace, kind, utils: { lum, contrast, searchIcon, estTextW, elBox } };
})();
+254
View File
@@ -0,0 +1,254 @@
/**
* 画个标 · 技能库:导出类(export)
* 注册:HGB_Skills.register([...])
*/
(function () {
'use strict';
HGB_Skills.register([
{
id: 'export-multi',
name: '多格式导出',
category: 'export',
emoji: '📦',
scene: '方案导出 PNG / JPG / SVG / PDF 与多尺寸规格',
summary: '封装导出引擎:任意方案一键导出 SVG(矢量)与 PNG/PDF(JPEG 压缩位图),支持 512/1024/2048 等尺寸。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象' },
{ name: 'format', type: 'string', required: false, desc: 'png/svg/pdf(默认 png)' },
{ name: 'size', type: 'number', required: false, desc: '导出边长(默认 1024)' }
],
source: '自研导出引擎(SVG 渲染 + Canvas 位图 + 手写 PDF)',
run: function (args) {
const plan = args.plan || {};
const format = (args.format || 'png').toLowerCase();
const size = args.size || 1024;
const svg = HGB_Exporter.buildSvgFrom(plan.elements || [], plan.bg || '#FFFFFF', size);
return {
plan: plan.name || '未命名', format, size,
svgLength: svg.length,
supported: ['png', 'jpg', 'svg', 'pdf'],
canExport: HGB_Exporter.exportPng && HGB_Exporter.exportSvg ? true : false,
note: format === 'pdf' ? 'PDF 内嵌 JPEG 位图,适合打印' : (format === 'svg' ? 'SVG 为矢量,可无限缩放' : 'PNG 为位图,建议 1024+')
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '导出测试' });
const r = this.run({ plan, format: 'svg', size: 1024 });
return { format: r.format, svgLength: r.svgLength, canExport: r.canExport };
}
},
{
id: 'export-avatar',
name: '头像平台规格',
category: 'export',
emoji: '🪪',
scene: '头像导出的主流平台尺寸规格',
summary: '返回微信/QQ/抖音/YouTube/App Store/Favicon 等平台头像规格清单,指导按平台要求批量导出头像。',
params: [
{ name: 'platform', type: 'string', required: false, desc: '指定平台(wechat/qq/douyin/youtube/app/favicon),留空返回全部' }
],
source: '自研(平台头像规格表)',
run: function (args) {
const specs = [
{ platform: '微信', size: 300, note: '微信个人/公众号头像推荐' },
{ platform: 'QQ', size: 640, note: 'QQ 头像常用尺寸' },
{ platform: '抖音', size: 400, note: '抖音头像展示尺寸' },
{ platform: 'YouTube', size: 800, note: 'YouTube 频道头像' },
{ platform: 'App Store', size: 1024, note: 'App 图标规范' },
{ platform: 'Favicon', size: 64, note: '浏览器标签页图标' }
];
const p = String(args.platform || '').toLowerCase();
const map = { wechat: 0, qq: 1, douyin: 2, youtube: 3, app: 4, favicon: 5 };
const list = p && map[p] !== undefined ? [specs[map[p]]] : specs;
return { list, total: specs.length, exportNote: '头像导出走 exporter.exportAvatarPngs(圆形裁切)' };
},
test: function () {
return { wechat: this.run({ platform: 'wechat' }).list[0], all: this.run({}).list.length };
}
},
{
id: 'export-sizing',
name: '导出尺寸规范',
category: 'export',
emoji: '📐',
scene: 'LOGO 各用途推荐导出尺寸',
summary: '按用途(网页/印刷/社交/App 图标)推荐 LOGO 导出尺寸与格式,网页用 PNG/SVG、印刷用矢量或高分辨率位图。',
params: [
{ name: 'use', type: 'string', required: false, desc: '用途(web/print/social/app)' }
],
source: '自研(LOGO 交付尺寸规范)',
run: function (args) {
const use = String(args.use || 'web').toLowerCase();
const specMap = {
web: { use: '网页', size: '512×512 PNG', format: 'PNG/SVG', dpi: '96dpi', note: 'Web 加载快,建议 SVG 为主 PNG 兜底' },
print: { use: '印刷', size: '1024×1024 起', format: 'PDF(矢量)/ 高分辨率 PNG', dpi: '300dpi', note: '印刷务必矢量或 300dpi 位图,避免模糊' },
social: { use: '社交媒体', size: '400~800×400~800', format: 'PNG', dpi: '144dpi', note: '头像/封面按平台规格导出' },
app: { use: 'App 图标', size: '1024×1024', format: 'PNG(含透明)', dpi: '96dpi', note: 'App Store 需 1024px 无圆角原图' }
};
return specMap[use] || specMap.web;
},
test: function () {
return { web: this.run({ use: 'web' }).size, print: this.run({ use: 'print' }).dpi };
}
},
{
id: 'favicon-kit',
name: 'Favicon 全套尺寸',
category: 'export',
emoji: '🌐',
scene: '浏览器/移动端 Favicon 全尺寸导出清单',
summary: '列出 Favicon 标准尺寸集(16/32/48/64/180/192/512),并给出各文件用途与浏览器兼容说明。',
params: [
{ name: 'plan', type: 'object', required: false, desc: '方案对象(可选)' }
],
source: '自研(Favicon 规范:HTML5 link 尺寸)',
run: function (args) {
const specs = [
{ file: 'favicon-16x16.png', size: 16, use: '浏览器标签页' },
{ file: 'favicon-32x32.png', size: 32, use: '任务栏/快捷键' },
{ file: 'favicon-48x48.png', size: 48, use: 'Windows 站点图标' },
{ file: 'favicon-64x64.png', size: 64, use: '传统标签页' },
{ file: 'apple-touch-icon.png', size: 180, use: 'iOS 主屏书签' },
{ file: 'android-chrome-192x192.png', size: 192, use: 'Android 添加到主屏' },
{ file: 'android-chrome-512x512.png', size: 512, use: 'PWA 图标' }
];
return { specs, tip: '小尺寸(16~32)务必先过「小尺寸可读性」检查;导出使用 exportSvg → PNG 缩放', canExport: !!(HGB_Exporter && HGB_Exporter.exportPng) };
},
test: function () {
const r = this.run({});
return { sizes: r.specs.map(s => s.size).join(','), canExport: r.canExport };
}
},
{
id: 'transparent-check',
name: '透明背景检查',
category: 'export',
emoji: '👁️',
scene: '导出格式的透明背景支持检查',
summary: '检查方案背景是否为透明:PNG/SVG 支持透明,JPG 不支持(自动垫白);透明底适合头像/贴纸,深色展示图则建议保留底色。',
params: [
{ name: 'plan', type: 'object', required: false, desc: '方案对象 {bg}' }
],
source: '自研(导出格式透明规则)',
run: function (args) {
const plan = args.plan || {};
const bg = plan.bg;
const transparent = !bg || String(bg).toLowerCase() === 'transparent' || plan.transparent === true;
return {
background: transparent ? '透明' : (bg || '#FFFFFF'),
transparent,
png: '支持透明(PNG-24 含 Alpha)', svg: '支持透明(矢量透明)', jpg: '不支持透明(自动填充白色)',
verdict: transparent ? '当前为透明背景:适合头像/贴纸/叠加场景,导出 PNG 或 SVG' : '当前为纯色背景:如需透明底,请切换背景为「透明」后导出 PNG',
suggest: transparent ? '导出推荐 PNG(保留 Alpha)或 SVG' : '背景为 ' + (bg || '#FFFFFF') + ',导出 JPG 无透明需求时可用'
};
},
test: function () {
return { transparent: this.run({ plan: { bg: 'transparent' } }).verdict, solid: this.run({ plan: { bg: '#FFFFFF' } }).transparent };
}
},
{
id: 'print-safety',
name: '印刷安全区检查',
category: 'export',
emoji: '🖨️',
scene: 'LOGO 印刷/裁切的安全边距检查',
summary: '检查元素是否过于贴近画布边缘:印刷裁切、印章、贴纸需要预留安全边距(出血),贴边元素易被裁掉。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(印刷出血/安全边距规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600, MARGIN = 30;
const vis = els.filter(el => el.visible !== false);
if (!vis.length) return { pass: true, note: '无可见元素' };
const tight = [];
for (const el of vis) {
const b = (el.type === 'text')
? { x: el.x - (el.w || 60) / 2, y: el.y - (el.h || 40) / 2, w: el.w || 60, h: el.h || 40 }
: { x: el.x, y: el.y, w: el.w || 0, h: el.h || 0 };
if (b.x < MARGIN || b.y < MARGIN || (CW - (b.x + b.w)) < MARGIN || (CH - (b.y + b.h)) < MARGIN) {
tight.push(el.text || el.iconId || '元素');
}
}
const pass = tight.length === 0;
return {
pass,
margin: MARGIN,
tightElements: tight,
verdict: pass ? '所有元素均在安全区内,印刷裁切安全' : tight.length + ' 个元素贴近边缘(' + tight.join('、') + '),有被裁切风险',
suggest: pass ? '无需调整' : '建议把主体元素内缩至安全区(距边缘 ≥ ' + MARGIN + 'px),预留出血'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '印刷测试' });
return this.run({ plan });
}
},
{
id: 'format-choose',
name: '导出格式推荐',
category: 'export',
emoji: '🧭',
scene: '按使用场景推荐 LOGO 导出格式与尺寸',
summary: '按用途(网页/印刷/应用图标/社交头像/视频)推荐最优导出格式(SVG/PNG/JPG)、尺寸、分辨率与背景策略,避免「一张 PNG 走天下」的质量损耗。',
params: [
{ name: 'usage', type: 'string', required: false, desc: '使用场景:web 网页 / print 印刷 / app 应用图标 / social 社交头像 / video 视频' }
],
source: '自研(各平台规格映射)',
run: function (args) {
const usage = String(args.usage || 'web');
const MAP = {
web: { format: 'SVG(主)+ PNG 512/1024', size: '512–1024px', dpi: '72', bg: '透明背景', note: '网页用 SVG 矢量最佳,PNG 用于兼容旧环境' },
print: { format: 'SVG / PDF(矢量)', size: 'A4 及以上', dpi: '300', bg: '含出血版 + 透明版各一份', note: '印刷必须矢量或 300dpi 位图,避免小图放大模糊' },
app: { format: 'PNG', size: '1024×1024', dpi: '72', bg: '不留透明(应用商店要求不透明底)', note: '系统自动圆角,主体需居中并预留约 8% 安全边距' },
social: { format: 'PNG', size: '400–1024px', dpi: '72', bg: '透明背景', note: '头像按圆形裁切展示,图形主体需居中避免切边' },
video: { format: 'PNG(带透明)', size: '1920×1080 或更大', dpi: '72', bg: '透明背景', note: '视频叠加层需透明 PNG,避免 JPG 黑/白底' }
};
const hit = MAP[usage] || MAP.web;
return { usage, usageCn: ({ web: '网页', print: '印刷', app: '应用图标', social: '社交头像', video: '视频' })[usage] || '网页', format: hit.format, size: hit.size, dpi: hit.dpi, bg: hit.bg, note: hit.note };
},
test: function () {
return { web: this.run({ usage: 'web' }).format, print: this.run({ usage: 'print' }).dpi, app: this.run({ usage: 'app' }).bg };
}
},
{
id: 'file-naming',
name: '交付文件命名',
category: 'export',
emoji: '🏷️',
scene: '交付/导出文件的统一命名规范,避免重名与版本混乱',
summary: '按「品牌_用途_规格_版本」生成统一文件命名规范(小写 + 连字符),输出各用途(logo/avatar/favicon)示例文件名与命名规则清单。',
params: [
{ name: 'brand', type: 'string', required: true, desc: '品牌名(自动转小写连字符)' },
{ name: 'purpose', type: 'string', required: false, desc: '用途:logo/avatar/favicon/vi(默认 logo)' },
{ name: 'version', type: 'string', required: false, desc: '版本号(默认 v1)' }
],
source: '自研(交付文件命名规范)',
run: function (args) {
const raw = String(args.brand || 'brand').toLowerCase().replace(/[^\w-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
const brand = raw || 'brand';
const purpose = String(args.purpose || 'logo').toLowerCase();
const ver = String(args.version || 'v1');
const p = ['logo', 'avatar', 'favicon', 'vi'].includes(purpose) ? purpose : 'logo';
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
const convention = brand + '-' + p + '-{规格}-' + ver;
const examples = p === 'favicon'
? [brand + '-favicon-32-' + ver + '.png', brand + '-favicon-180-' + ver + '.png', brand + '-favicon-512-' + ver + '.png']
: (p === 'avatar'
? [brand + '-avatar-300-' + ver + '.png', brand + '-avatar-640-' + ver + '.png', brand + '-avatar-1024-' + ver + '.png']
: [brand + '-logo-main-' + ver + '.svg', brand + '-logo-reverse-' + ver + '.png', brand + '-logo-mono-' + ver + '.png']);
return {
brand, purpose: p, version: ver, date, convention, examples,
rules: ['小写字母 + 连字符,禁止中文与空格', '「用途」段区分 logo / avatar / favicon / vi 交付物', '「规格」段标注尺寸(512 / 300)或版别(main / reverse / mono)', '「版本」段 v1 / v2…,迭代不覆盖旧版'],
tip: '批量交付按「品牌_用途_规格_版本」归档目录,迭代导出时递增版本号,配合版本号联动规则避免同名覆盖'
};
},
test: function () {
const r = this.run({ brand: '远渡教育', purpose: 'logo', version: 'v2' });
return { convention: r.convention, examples: r.examples.length, rules: r.rules.length };
}
}
]);
})();
+263
View File
@@ -0,0 +1,263 @@
/**
* 画个标 · 技能库:图标类(icon)
* 注册:HGB_Skills.register([...])
*/
(function () {
'use strict';
const U = HGB_Skills.utils;
HGB_Skills.register([
{
id: 'icon-match',
name: '图标语义检索',
category: 'icon',
emoji: '🪄',
scene: '从内置 32 万+ 图标库中按语义关键词检索图标',
summary: '在内置多源图标库(Feather/Lucide/Tabler 等 197 源)中按名称关键词模糊搜索,返回匹配图标(源:名称),供 AI 与用户选择。',
params: [
{ name: 'keyword', type: 'string', required: true, desc: '语义关键词(如 graduation / rocket / heart)' }
],
source: 'GitHub: 内置图标库 197 源(Iconify + GitHub 聚合)',
run: function (args) {
return { keyword: args.keyword, hits: U.searchIcon(args.keyword) };
},
test: function () {
return { rocket: U.searchIcon('rocket').slice(0, 3), graduation: U.searchIcon('graduation').slice(0, 3) };
}
},
{
id: 'icon-consistency',
name: '图标风格一致性',
category: 'icon',
emoji: '🎯',
scene: '同一方案多图标风格一致性检查',
summary: '检查方案中多个图标是否同源同风(线条/填充/圆角一致):混用不同来源图标会破坏品牌统一感,给出统一建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(多源图标库统一性原则)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const icons = els.filter(el => el.type === 'icon' && el.iconId && !['ring', 'deco-bar'].includes(el.iconId));
if (icons.length < 2) {
return { pass: true, note: '仅 1 个图标或无需一致性检查', iconCount: icons.length };
}
const sources = new Map();
for (const ic of icons) {
const src = String(ic.source || (ic.id || '').split(':')[0] || 'unknown');
sources.set(src, (sources.get(src) || 0) + 1);
}
const uniq = [...sources.entries()].map(([s, n]) => s + '×' + n);
return {
pass: sources.size === 1,
iconCount: icons.length,
sources: uniq,
verdict: sources.size === 1 ? '图标同源统一,风格一致' : `混用 ${sources.size} 个来源(${uniq.join('、')}),建议统一为同一来源图标`
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '一致性测试' });
return this.run({ plan });
}
},
{
id: 'icon-tone',
name: '图标明暗适配',
category: 'icon',
emoji: '🌗',
scene: '图标在深/浅背景下的可读性检查',
summary: '根据方案背景色明暗,检查图标/主色是否清晰可见:深底配深色图标、浅底配浅色图标都会降低可读性。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements, bg}' }
],
source: '自研(背景明暗 × 前景对比)',
run: function (args) {
const plan = args.plan || {};
const bg = plan.bg || '#FFFFFF';
const bgDark = U.lum(bg) < 0.5;
const els = plan.elements || [];
const icon = els.find(el => el.type === 'icon' && el.iconId && !['ring', 'deco-bar'].includes(el.iconId));
const text = els.find(el => el.type === 'text');
const fg = (icon && icon.stroke) || (text && text.fill) || '#333333';
const fgDark = U.lum(fg) < 0.5;
const mismatch = bgDark === fgDark;
return {
pass: !mismatch,
bg: bg, bgTone: bgDark ? '深色' : '浅色',
fg: fg, fgTone: fgDark ? '深色' : '浅色',
verdict: mismatch ? '背景与主体同向明暗,可读性偏低,建议调整主色或背景' : '背景与主体明暗互补,可读性良好'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '明暗测试' });
return this.run({ plan });
}
},
{
id: 'icon-count',
name: '图标数量检查',
category: 'icon',
emoji: '🔢',
scene: 'LOGO 图标元素数量与复杂度检查',
summary: '检查方案中的图标数量:专业 LOGO 通常 1 个主图标(装饰环除外),图标过多会稀释辨识度,建议精简。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(极简设计原则量化)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const icons = els.filter(el => el.type === 'icon' && el.iconId && !['ring', 'deco-bar'].includes(el.iconId));
const n = icons.length;
const pass = n <= 2;
return {
pass,
count: n,
icons: icons.map(ic => ic.iconId),
verdict: n === 0 ? '无图标(纯文字标)' : (pass ? '图标数量适中,辨识度高' : '图标多达 ' + n + ' 个,易稀释品牌辨识度,建议精简为 1 个主图标'),
suggest: n > 2 ? '合并/删除次要图标,保留最具辨识度的一个' : '无需调整'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '图标数测试' });
return this.run({ plan });
}
},
{
id: 'icon-text-ratio',
name: '图标文字比例',
category: 'icon',
emoji: '🔍',
scene: '图标与品牌文字的大小比例检查',
summary: '检查主图标与主文字的宽度比例:图标过小失去存在感、过大挤压文字,理想区间约 0.6~1.5。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(图标/文字比例规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const icon = els.find(el => el.type === 'icon' && el.iconId && !['ring', 'deco-bar'].includes(el.iconId));
const text = els.find(el => el.type === 'text' && el.visible !== false);
if (!icon || !text) return { pass: true, note: '缺少图标或文字,跳过比例检查' };
const bi = U.elBox(icon), bt = U.elBox(text);
const ratio = Math.round((bi.w / bt.w) * 100) / 100;
const good = ratio >= 0.6 && ratio <= 1.5;
return {
pass: good,
ratio,
iconW: Math.round(bi.w), textW: Math.round(bt.w),
verdict: good ? '图标/文字宽度比 ' + ratio + ',主次协调' : (ratio > 1.5 ? '图标偏大(' + ratio + '),建议缩小图标或加大文字' : '图标偏小(' + ratio + '),建议放大图标增强存在感'),
suggest: good ? '无需调整' : (ratio > 1.5 ? '缩小图标或加大文字' : '放大图标或缩短文字')
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '比例测试' });
return this.run({ plan });
}
},
{
id: 'icon-search-cn',
name: '中文语义图标检索',
category: 'icon',
emoji: '🔍',
scene: '用中文关键词(分类/语义)检索内置图标',
summary: '在内置图标索引(含中文分类)中按中文关键词检索:支持「教育」「餐饮」「医疗」「科技」等中文分类词,返回图标 id/名称/分类,方便中文用户选图。',
params: [
{ name: 'keyword', type: 'string', required: true, desc: '中文关键词(如 教育 / 餐饮 / 医疗 / 科技)' }
],
source: 'GitHub: 内置图标索引中文分类(categoryCn)',
run: function (args) {
const kw = String(args.keyword || '').toLowerCase();
if (!kw) return { keyword: args.keyword, hits: [], note: '请输入中文关键词' };
const idx = (typeof HGB_BuiltinIcons !== 'undefined' && HGB_BuiltinIcons.iconIndex) || [];
const hits = [];
for (const ic of idx) {
const cat = String(ic.category || '').toLowerCase();
const name = String(ic.name || '').toLowerCase();
if (cat.includes(kw) || name.includes(kw)) {
hits.push({ id: ic.id, name: ic.name, category: ic.category });
if (hits.length >= 20) break;
}
}
return { keyword: args.keyword, hits };
},
test: function () {
return { edu: this.run({ keyword: '教育' }).hits.slice(0, 3), food: this.run({ keyword: '餐饮' }).hits.slice(0, 3) };
}
},
{
id: 'icon-padding',
name: '图标安全边距检查',
category: 'icon',
emoji: '🖼️',
scene: '检查图标图形是否贴边(缺乏安全边距)',
summary: '解析图标 path 坐标,检测图形是否触及 24 网格边缘:贴边图标在叠加底色/圆形裁切时易被切边,输出最小边距与建议。',
params: [
{ name: 'svg', type: 'string', required: true, desc: '图标 SVG body(path d 片段)' }
],
source: '自研(图标网格安全边距规范)',
run: function (args) {
const body = String(args.svg || '');
const nums = [];
const re = /\bd="([^"]+)"/g;
let m;
while ((m = re.exec(body))) {
for (const n of m[1].match(/-?\d*\.?\d+(?:e[+-]?\d+)?/gi) || []) {
const v = parseFloat(n);
if (v >= -1000 && v <= 1000) nums.push(v);
}
}
if (!nums.length) return { pass: true, note: '未解析到 path 坐标(可能为图形类图标)' };
const min = Math.min(...nums), max = Math.max(...nums);
const padL = Math.round(min * 100) / 100, padR = Math.round((24 - max) * 100) / 100;
const pass = min >= 0.8 && max <= 23.2;
return {
pass, min, max, paddingLeft: Math.max(0, padL), paddingRight: Math.max(0, padR),
verdict: pass ? '图形在 24 网格内留有安全边距(约 ' + Math.round(Math.min(padL, padR) * 10) / 10 + 'px)' : '图形贴近/超出网格边缘(左 ' + padL + ' 右 ' + padR + '),叠加底色或圆形裁切时易被切边',
suggest: pass ? '无需调整' : '优先选择边距更足的图标,或为图标叠加留白容器(badge/圆环)'
};
},
test: function () {
const tight = '<path d="M0 0h24v24H0z"/>';
const safe = '<path d="M2 2h20v20H2z"/>';
return { tight: this.run({ svg: tight }).pass, safe: this.run({ svg: safe }).pass };
}
},
{
id: 'stroke-consistency',
name: '图标描边一致性',
category: 'icon',
emoji: '✏️',
scene: '同一方案多个图标的描边粗细一致性检查',
summary: '检查方案中多个图标的描边粗细(stroke-width)是否统一:混用不同粗细体系(2px / 1.5px)的图标群会显得杂乱,输出各图标描边清单与统一建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(图标描边体系规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const icons = els.filter(el => el.type === 'icon' && el.iconId && !['ring', 'deco-bar'].includes(el.iconId));
if (icons.length < 2) return { pass: true, note: '仅 1 个图标或无需检查', count: icons.length };
const DEF_STROKE = { lucide: 2, feather: 2, tabler: 1.5, 'heroicons-outline': 1.5, phosphor: 2 };
const rows = icons.map(ic => {
const src = String(ic.source || (ic.id || '').split(':')[0] || '').toLowerCase();
const sw = ic.strokeWidth != null ? Number(ic.strokeWidth) : (DEF_STROKE[src] != null ? DEF_STROKE[src] : null);
return { iconId: ic.iconId, source: src, strokeWidth: sw };
});
const known = rows.filter(r => r.strokeWidth != null);
if (!known.length) return { pass: true, note: '未解析到描边数据(可能为填充图标),跳过', count: icons.length };
const widths = [...new Set(known.map(r => r.strokeWidth))];
const pass = widths.length === 1;
return {
pass, rows, strokeWidths: widths,
verdict: pass ? '图标描边统一(' + widths[0] + 'px),线条体系一致' : '图标描边不统一(' + widths.join('px / ') + 'px),混用不同粗细体系易显杂乱',
suggest: pass ? '无需调整' : '统一为同一描边体系(推荐 2px:小尺寸清晰、与多数图标集一致),或整体换用填充图标'
};
},
test: function () {
const same = { plan: { elements: [{ type: 'icon', iconId: 'x', source: 'lucide' }, { type: 'icon', iconId: 'y', source: 'feather' }] } };
const diff = { plan: { elements: [{ type: 'icon', iconId: 'x', source: 'tabler' }, { type: 'icon', iconId: 'y', source: 'lucide' }] } };
return { same: this.run(same).pass, diffBad: this.run(diff).pass === false, widths: this.run(diff).strokeWidths.length === 2 };
}
}
]);
})();
+394
View File
@@ -0,0 +1,394 @@
/**
* 画个标 · 技能库:构图类(layout)
* 注册:HGB_Skills.register([...])
*/
(function () {
'use strict';
const U = HGB_Skills.utils;
const { estTextW, elBox } = U;
HGB_Skills.register([
{
id: 'composition',
name: '构图质量评估',
category: 'layout',
emoji: '📐',
scene: 'LOGO 构图设计与质量检查(生成后自动审计)',
summary: '依据"极简、充足负空间、精确比例"专业原则,检测方案的元素越界、重叠与文字过宽,输出质量评分与修正建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '待检查的方案对象 {elements, bg}' }
],
source: 'GitHub: op7418/logo-generator-skill(设计原则)+ 自研几何审计',
run: function (args) {
const plan = args.plan || {};
const els = plan.elements || [];
const CW = 600, CH = 600;
const issues = [], suggestions = [];
for (const el of els) {
if (el.visible === false) continue;
const b = elBox(el);
if (b.x < 0 || b.y < 0 || b.x + b.w > CW || b.y + b.h > CH) {
issues.push(`"${el.name}" 超出画布边界`);
}
}
for (let i = 0; i < els.length; i++) {
for (let j = i + 1; j < els.length; j++) {
const a = els[i], b = els[j];
const types = [a.type, b.type].sort().join('|');
if (types !== 'icon|text') continue;
// 圆环/装饰条是有意的背景元素(徽章/印章中文字位于环内),不参与重叠判定
if (['ring', 'deco-bar'].includes(a.iconId) || ['ring', 'deco-bar'].includes(b.iconId)) continue;
const ba = elBox(a), bb = elBox(b);
const ox = Math.min(ba.x + ba.w, bb.x + bb.w) - Math.max(ba.x, bb.x);
const oy = Math.min(ba.y + ba.h, bb.y + bb.h) - Math.max(ba.y, bb.y);
if (ox > 0 && oy > 0 && ox * oy > (ba.w * ba.h) * 0.08) {
issues.push(`"${a.name}" 与 "${b.name}" 存在重叠(建议加大间距)`);
}
}
}
for (const el of els) {
if (el.type === 'text' && el.fontSize && el.fontSize >= 36) {
const b = elBox(el);
if (b.x + b.w > CW - 20) {
suggestions.push(`"${el.name}" 文字偏宽(约 ${Math.round(b.w)}px),建议缩小字号或缩短文字`);
}
}
}
const score = issues.length ? Math.max(40, 95 - issues.length * 20) : (suggestions.length ? 88 : 96);
return { score, pass: issues.length === 0, issues, suggestions };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '测试品牌名' });
const r = this.run({ plan });
return { score: r.score, pass: r.pass, issues: r.issues, suggestions: r.suggestions, elements: plan.elements.length };
}
},
{
id: 'layout-choice',
name: '布局匹配推荐',
category: 'layout',
emoji: '🧱',
scene: '根据品牌名长度与行业选择最合适的布局模板',
summary: '按品牌名长度(中文字数/西文字符数)匹配布局:短名用徽章/印章/双图标,长名用上下/左右式,避免破版。',
params: [
{ name: 'brand', type: 'string', required: true, desc: '品牌名' },
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: 'GitHub: neonwatty/logo-designer-skill(interview 阶段)+ 自研',
run: function (args) {
const brand = String(args.brand || '');
const def = HGB_Generator.defaultLayout(brand);
const cn = (brand.match(/[\u4e00-\u9fa5]/g) || []).length;
const len = cn || brand.length;
const pool = HGB_Generator.LAYOUTS.filter(l => {
if (['badge', 'seal', 'duo'].includes(l.id)) return len <= 4;
return true;
});
return { brand, len, recommend: def.id, recommendName: def.name, candidates: pool.map(l => l.id + ':' + l.name) };
},
test: function () {
return { short: this.run({ brand: '远渡' }).recommend, medium: this.run({ brand: '远渡教育咨询' }).recommend, long: this.run({ brand: 'FAR CROSS EDUCATION' }).recommend };
}
},
{
id: 'align-check',
name: '对齐与居中检查',
category: 'layout',
emoji: '📏',
scene: 'LOGO 元素对齐与居中规范审计',
summary: '检查方案元素是否围绕画布中心对齐(水平/垂直居中),并检测同级元素是否存在明显错位,输出对齐修正建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: 'GitHub: op7418/logo-generator-skill(对齐规范)+ 自研',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const issues = [];
const main = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (!main.length) return { pass: true, issues: ['无可见主体元素'], centered: true };
// 主体元素中心应落在画布中心附近(容差 20px)
let cxSum = 0, cySum = 0, n = 0;
for (const el of main) {
const b = elBox(el);
cxSum += b.x + b.w / 2;
cySum += b.y + b.h / 2;
n++;
}
const cx = cxSum / n, cy = cySum / n;
if (Math.abs(cx - CW / 2) > 20) issues.push(`主体水平中心偏移 ${Math.round(cx - CW / 2)}px(建议居中)`);
if (Math.abs(cy - CH / 2) > 20) issues.push(`主体垂直中心偏移 ${Math.round(cy - CH / 2)}px(建议居中)`);
return { pass: issues.length === 0, issues, centerX: Math.round(cx), centerY: Math.round(cy), canvasCenter: CW / 2 };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '对齐测试' });
const r = this.run({ plan });
return { pass: r.pass, issues: r.issues };
}
},
{
id: 'negative-space',
name: '负空间评估',
category: 'layout',
emoji: '🕳️',
scene: 'LOGO 负空间(留白)充足性评估',
summary: '计算主体元素在画布中的占比:占比过高说明负空间不足(拥挤),占比过低说明主体偏小(空洞),给出建议区间。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(基于"极简 + 充足负空间"设计原则量化)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const area = CW * CH;
let used = 0;
for (const el of els) {
if (el.visible === false) continue;
const b = elBox(el);
const w = Math.max(0, Math.min(b.x + b.w, CW) - Math.max(b.x, 0));
const h = Math.max(0, Math.min(b.y + b.h, CH) - Math.max(b.y, 0));
used += w * h;
}
const ratio = Math.round((used / area) * 1000) / 10;
const verdict = ratio > 55 ? '负空间不足,建议缩小主体或加大留白'
: (ratio < 12 ? '主体偏小,建议放大增强存在感'
: '负空间充足,比例健康');
return { ratio, verdict, suggest: ratio > 55 ? '缩小主体或精简元素' : (ratio < 12 ? '放大主体或增加细节' : '无需调整') };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '留白测试' });
return this.run({ plan });
}
},
{
id: 'spacing-check',
name: '间距节奏检查',
category: 'layout',
emoji: '↔️',
scene: '元素间距与画布边距的一致性检查',
summary: '检查主体元素与画布边缘的留白是否充足一致,以及图标与文字间距是否过近(<8px 视为贴边),保证构图不挤不散。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(间距节奏规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const issues = [];
const main = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (!main.length) return { pass: true, issues: ['无可见主体元素'] };
for (const el of main) {
const b = elBox(el);
const minPad = Math.min(b.x, CW - (b.x + b.w), b.y, CH - (b.y + b.h));
if (minPad < 16) issues.push(`"${el.name}" 距画布边缘仅 ${Math.round(minPad)}px,建议留白 ≥ 24px`);
}
const icon = main.find(el => el.type === 'icon');
const text = main.find(el => el.type === 'text');
if (icon && text) {
const bi = elBox(icon), bt = elBox(text);
// 仅当水平相邻(垂直中心距小于图标高)时检查水平间距
const verticalGap = Math.abs((bi.y + bi.h / 2) - (bt.y + bt.h / 2));
if (verticalGap < (bi.h + bt.h) / 2) {
const gap = Math.abs((bi.x + bi.w / 2) - (bt.x + bt.w / 2)) - (bi.w + bt.w) / 2;
if (gap < 8) issues.push('图标与文字水平间距过近(<8px),建议加大间距');
}
}
return { pass: issues.length === 0, issues, ok: issues.length ? [] : ['间距节奏协调,留白充足'] };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '间距测试' });
return this.run({ plan });
}
},
{
id: 'proportion-check',
name: '黄金比例评估',
category: 'layout',
emoji: '⚖️',
scene: '主体占比与黄金分割的匹配度评估',
summary: '计算主体元素宽度与画布宽度之比:接近 0.618 黄金分割视觉最舒适,过高拥挤、过低空泛,输出调整方向。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(黄金分割比例规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600;
const main = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (!main.length) return { pass: true, note: '无可见主体元素' };
let maxW = 0;
for (const el of main) {
const b = elBox(el);
if (b.w > maxW) maxW = b.w;
}
const ratio = Math.round((maxW / CW) * 1000) / 1000;
const pass = Math.abs(ratio - 0.618) < 0.15;
return {
pass,
ratio, golden: 0.618,
verdict: pass ? '主体占比接近黄金分割(' + ratio + '),比例协调' : '主体占比 ' + ratio + ',偏离黄金分割,建议' + (ratio > 0.768 ? '缩小主体' : '放大主体'),
suggest: pass ? '无需调整' : (ratio > 0.768 ? '缩小主体或精简元素' : '放大主体增强存在感')
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '比例测试' });
return this.run({ plan });
}
},
{
id: 'visual-balance',
name: '视觉重量平衡',
category: 'layout',
emoji: '⚖️',
scene: '画布左右/上下的视觉重量均衡检查',
summary: '计算所有可见元素的包围盒质心与画布中心的偏移:偏移过大说明构图偏一侧、视觉失衡,输出偏移方向与调整建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(视觉重量质心评估)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const vis = els.filter(el => el.visible !== false);
if (!vis.length) return { pass: true, note: '无可见元素' };
let sx = 0, sy = 0, sw = 0;
for (const el of vis) {
const b = elBox(el);
const w = Math.max(b.w * b.h, 1);
sx += ((b.x + b.w / 2) - CW / 2) * w;
sy += ((b.y + b.h / 2) - CH / 2) * w;
sw += w;
}
const dx = Math.round((sx / sw) * 10) / 10;
const dy = Math.round((sy / sw) * 10) / 10;
const off = Math.sqrt(dx * dx + dy * dy);
const pass = off < 40;
const dir = Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? '右' : '左') : (dy > 0 ? '下' : '上');
return {
pass,
offsetX: dx, offsetY: dy, offset: Math.round(off),
verdict: pass ? '视觉重心接近中心,构图平衡' : '视觉重心偏移 ' + Math.round(off) + 'px,构图偏' + dir,
suggest: pass ? '无需调整' : '调整元素位置或补充留白,让视觉重心回归中心'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '平衡测试' });
return this.run({ plan });
}
},
{
id: 'focal-point',
name: '视觉焦点检查',
category: 'layout',
emoji: '🎯',
scene: 'LOGO 是否有明确视觉焦点(主次分明)',
summary: '找出面积最大的主体元素,判断其是否显著大于其他元素:主次分明有助于快速识别,多个等权元素会让视线分散。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(主次层级量化)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const vis = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (vis.length < 2) return { pass: true, note: '元素过少,无需焦点检查' };
const areas = vis.map(el => { const b = elBox(el); return { el, area: b.w * b.h }; }).sort((a, b) => b.area - a.area);
const top = areas[0], second = areas[1];
const ratio = Math.round((top.area / Math.max(second.area, 1)) * 100) / 100;
const pass = ratio >= 1.5;
return {
pass,
focusRatio: ratio,
main: top.el.text || top.el.iconId || '主体',
verdict: pass ? '主元素面积是次元素的 ' + ratio + ' 倍,主次分明' : '主次面积接近(' + ratio + ' 倍),视觉焦点不明确',
suggest: pass ? '无需调整' : '放大主元素或缩小次要元素,强化视觉焦点'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '焦点测试' });
return this.run({ plan });
}
},
{
id: 'rule-of-thirds',
name: '构图位置分析',
category: 'layout',
emoji: '📍',
scene: '主体构图位置(居中 / 三分 / 偏移)分析',
summary: '计算主体元素重心相对画布中心与三分线交点的位置,判定构图类型:居中构图适合徽章/印章类经典版式,三分构图更具视觉张力,随机偏移则需要纠正。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(居中/三分构图规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const main = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (!main.length) return { pass: true, note: '无可见主体元素' };
let sx = 0, sy = 0, n = 0;
for (const el of main) {
const b = elBox(el);
sx += b.x + b.w / 2; sy += b.y + b.h / 2; n++;
}
const cx = sx / n, cy = sy / n;
const dc = Math.hypot(cx - CW / 2, cy - CH / 2);
const xs = [CW / 3, CW * 2 / 3], ys = [CH / 3, CH * 2 / 3];
let dt = Infinity;
for (const gx of xs) for (const gy of ys) dt = Math.min(dt, Math.hypot(cx - gx, cy - gy));
const type = dc < 60 ? '居中构图(经典稳健,适合徽章/印章版式)'
: (dt < 60 ? '三分构图(主体落在分割线交点,视觉有张力)' : '偏移构图(主体既不在中心也不在分割点)');
const pass = dc < 60 || dt < 60;
return {
pass, type,
centerOffset: Math.round(dc), thirdsOffset: Math.round(dt),
centerX: Math.round(cx), centerY: Math.round(cy),
verdict: pass ? '构图位置合理:' + type : '构图位置偏散:' + type,
suggest: pass ? '无需调整' : '建议把主体重心移回画布中心或三分线交点附近,避免随意偏移'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '构图测试' });
return this.run({ plan });
}
},
{
id: 'circle-crop-safety',
name: '圆形裁切安全区',
category: 'layout',
emoji: '⭕',
scene: '头像/贴纸/印章等圆形裁切场景的构图安全检查',
summary: '以画布内切圆为裁切边界,检查各元素包围盒是否落在圆内:越界的元素在圆形裁切(头像、印章、贴纸)时会被切边,输出越界清单与缩放建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(圆形裁切安全区规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const cx = CW / 2, cy = CH / 2, r = CW / 2;
const vis = els.filter(el => el.visible !== false);
if (!vis.length) return { pass: true, note: '无可见元素' };
const risky = [];
let maxD = 0;
for (const el of vis) {
const b = elBox(el);
const corners = [[b.x, b.y], [b.x + b.w, b.y], [b.x, b.y + b.h], [b.x + b.w, b.y + b.h]];
let d = 0;
for (const [px, py] of corners) d = Math.max(d, Math.hypot(px - cx, py - cy));
maxD = Math.max(maxD, d);
if (d > r) risky.push({ name: el.text || el.iconId || '元素', maxDist: Math.round(d) });
}
const pass = risky.length === 0;
return {
pass, risky, radius: r, maxDist: Math.round(maxD),
verdict: pass ? '所有元素均在内切圆内,圆形裁切安全' : risky.length + ' 个元素超出内切圆(' + risky.map(x => x.name).join('、') + '),圆形裁切时会被切边',
suggest: pass ? '无需调整' : '建议缩小越界元素或整体向圆心内缩 ' + Math.max(10, Math.round(maxD - r)) + 'px,保证圆形裁切后主体完整'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '圆裁测试' });
const edge = { plan: { elements: [{ type: 'icon', iconId: 'x', x: 585, y: 300, w: 30, h: 30 }] } };
return { generated: this.run({ plan }).pass !== undefined, edge: this.run(edge).risky.length };
}
}
]);
})();
+172
View File
@@ -0,0 +1,172 @@
/**
* 画个标 · 技能库:展示类(presentation)
* 注册:HGB_Skills.register([...])
*/
(function () {
'use strict';
const U = HGB_Skills.utils;
HGB_Skills.register([
{
id: 'showcase-bg',
name: '展示背景推荐',
category: 'presentation',
emoji: '🖼️',
scene: '为方案推荐专业展示背景(深/浅两系)',
summary: '借鉴 12 种专业展示背景风格,按方案配色明暗推荐匹配的展示底色,用于预览大图与导出场景。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象(含 bg)' }
],
source: 'GitHub: op7418/logo-generator-skill(12 背景风格)',
run: function (args) {
const plan = args.plan || {};
const bg = plan.bg || '#FFFFFF';
const dark = U.lum(bg) < 0.5;
const styles = dark
? ['The Void 纯黑', 'Frosted Horizon 钛灰', 'Studio Spotlight 编辑级打光']
: ['Editorial Paper 米白', 'Morning Aura 暖象牙', 'Swiss Flat 纯色块'];
return { currentBg: bg, tone: dark ? '暗色系' : '亮色系', recommend: styles };
},
test: function () {
return { dark: this.run({ plan: { bg: '#0F172A' } }).recommend[0], light: this.run({ plan: { bg: '#FFFFFF' } }).recommend[0] };
}
},
{
id: 'mockup-scene',
name: '应用场景推荐',
category: 'presentation',
emoji: '📱',
scene: '为 LOGO 推荐常见品牌应用场景',
summary: '按行业与品牌气质推荐最值得展示的品牌应用场景(名片/门头/App 图标/包装/工作服等),用于提案与宣传素材。',
params: [
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: '自研(品牌 VI 应用场景规范)',
run: function (args) {
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const sceneMap = {
edu: ['校徽/学生证', '招生海报', '教学楼门头', '文具周边'],
food: ['门头招牌', '外卖包装', '餐巾纸/杯具', '菜单封面'],
health: ['诊所门头', '处方笺/病历', '药袋包装', '健康宣传栏'],
tech: ['App 图标', '官网 Logo', '工牌/名片', '演示 PPT 封面'],
sport: ['队服胸前', '场馆横幅', '奖杯/证书', '赛事海报'],
finance: ['名片', '合同封面', '营业厅门头', '年报封面']
};
const scenes = sceneMap[ind.id] || ['名片', '官网首页', '包装袋', '门头招牌'];
return { industry: ind.name, scenes };
},
test: function () {
return { tech: this.run({ industry: 'tech' }).scenes, food: this.run({ industry: 'food' }).scenes };
}
},
{
id: 'vi-checklist',
name: 'VI 规范清单',
category: 'presentation',
emoji: '📋',
scene: '品牌基础 VI 交付物检查清单',
summary: '输出品牌 VI 基础系统清单(主标/反白/单色版本、标准色、标准字、应用规范),对照勾选交付完整度。',
params: [
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: '自研(VI 基础系统规范)',
run: function (args) {
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const items = [
{ name: '主标(彩色版)', done: true, note: '已生成方案即为主标' },
{ name: '反白版(深底)', done: false, note: '用展示背景推荐生成深色底预览' },
{ name: '单色版(黑/白)', done: false, note: '导出时选择单色方案' },
{ name: '标准色值', done: true, note: '见方案主色 HEX' },
{ name: '标准字', done: true, note: '主标字体现已选' },
{ name: '安全边距/最小尺寸', done: false, note: '按小尺寸可读性技能确认' },
{ name: '应用示例(名片/门头/App)', done: false, note: '按应用场景推荐技能出图' }
];
const done = items.filter(i => i.done).length;
return { industry: ind.name, items, done, total: items.length, progress: Math.round(done / items.length * 100) + '%' };
},
test: function () {
const r = this.run({});
return { total: r.total, progress: r.progress };
}
},
{
id: 'launch-kit',
name: '上线素材包',
category: 'presentation',
emoji: '🚀',
scene: '品牌上线所需的完整素材清单与生成指引',
summary: '汇总 LOGO 多格式导出、头像规格、展示背景、应用场景为一体,给出上线素材包的一站式生成顺序。',
params: [
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: '自研(素材包组装流程)',
run: function (args) {
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const kit = [
{ item: 'LOGO 主标(SVG + 1024 PNG)', tool: '多格式导出' },
{ item: '头像(微信 300/QQ 640/抖音 400)', tool: '头像平台规格' },
{ item: 'App 图标 1024(无圆角)', tool: '导出尺寸规范' },
{ item: 'Favicon 32/180', tool: 'Favicon 全套' },
{ item: '展示图(深/浅背景各一)', tool: '展示背景推荐' },
{ item: '应用场景图(名片/门头)', tool: '应用场景推荐' }
];
return { industry: ind.name, kit, order: '按清单顺序:先导出 LOGO → 头像 → 图标 → 展示/场景' };
},
test: function () {
return { items: this.run({ industry: 'food' }).kit.length };
}
},
{
id: 'brand-story',
name: '品牌设计说明',
category: 'presentation',
emoji: '📖',
scene: '为方案生成品牌设计说明/提案文案(VI 提案、设计交付说明)',
summary: '基于品牌名、行业与方案的主色/布局,生成「设计理念 + 色彩寓意 + 版式逻辑 + 应用价值」四段式品牌故事文案,可直接用于提案与交付说明。',
params: [
{ name: 'brand', type: 'string', required: true, desc: '品牌名' },
{ name: 'industry', type: 'string', required: false, desc: '行业 id' },
{ name: 'color', type: 'string', required: false, desc: '主色 HEX' },
{ name: 'layoutId', type: 'string', required: false, desc: '布局 id(badge/seal/duo/stacked/centered/horizontal,可选)' }
],
source: '自研(品牌故事四段式文案框架)',
run: function (args) {
const brand = String(args.brand || '');
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const color = String(args.color || '').replace('#', '');
const c = /^[0-9a-fA-F]{6}$/.test(color) ? '#' + color : '';
const layoutId = String(args.layoutId || '');
const indName = ind.name;
const toneOf = (hex) => {
const l = U.lum(hex);
if (l < 0.15) return '深邃的深色系';
if (l < 0.5) return '沉稳的深色系';
return '明快的亮色系';
};
const layoutMap = {
badge: '徽章式圆形构图,传递经典与可信赖感',
seal: '印章式构图,象征权威与传统',
duo: '双图标并列,表达协同与多元',
stacked: '上下结构,突出主次层级',
centered: '居中构图,稳重凝练',
horizontal: '横向排布,便于延展应用'
};
const story = {
concept: '「' + brand + '」以' + indName + '为根基,通过简洁的图形语言传递品牌气质,追求极简、充足负空间与高辨识度。',
colorMeaning: c ? toneOf(c) + '(' + c + ')作为主色,传递品牌核心气质,配合留白确保多场景清晰呈现。' : '主色延续品牌既有视觉体系,保证识别一致性。',
layoutLogic: layoutMap[layoutId] ? layoutMap[layoutId] + ',让图形与品牌名主次分明。' : '构图遵循居中与留白原则,保证小尺寸(64px)仍清晰可辨。',
value: '整套标识可延展至名片、门头、App 图标与社交媒体头像,兼顾数字与印刷场景。'
};
return {
brand, industry: indName, primary: c || '(自动)',
story,
full: story.concept + story.colorMeaning + story.layoutLogic + story.value
};
},
test: function () {
const r = this.run({ brand: '远渡教育', industry: 'edu', color: '#1B4F72', layoutId: 'badge' });
return { sections: Object.keys(r.story).length, full: r.full.length > 20 };
}
}
]);
})();
+285
View File
@@ -0,0 +1,285 @@
/**
* 画个标 · 技能库:字体类(typography)
* 注册:HGB_Skills.register([...])
*/
(function () {
'use strict';
const U = HGB_Skills.utils;
HGB_Skills.register([
{
id: 'font-pairing',
name: '字体配对',
category: 'typography',
emoji: '🔤',
scene: '中英文品牌字体的专业配对',
summary: '依据文本语种(中文/西文)与行业推荐字体库,给出主标题与副标题的字体配对方案。',
params: [
{ name: 'text', type: 'string', required: true, desc: '品牌文本(自动识别中英文)' },
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: 'GitHub: design-resources-for-developers(Fontjoy 字体配对方法)+ 自研字体库',
run: function (args) {
const text = String(args.text || '');
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const favs = ind.fontFamilies || [];
const hasCn = /[\u4e00-\u9fa5]/.test(text);
const enPool = ['Arial', 'Georgia', 'Impact', 'Verdana', 'Montserrat', 'Playfair Display'];
const cnPool = ['SimHei', 'Microsoft YaHei', 'SimSun', 'KaiTi'];
const cn = (favs.find(f => cnPool.includes(f)) || 'SimHei');
const en = (favs.find(f => enPool.includes(f)) || 'Arial');
return {
text, language: hasCn ? '中文为主' : '西文为主',
pairing: hasCn ? cn + '(主标题) / ' + en + '(副标题)' : en + '(主标题) / Georgia(副标题)',
industryFav: favs
};
},
test: function () {
return { cn: this.run({ text: '远渡教育' }).pairing, en: this.run({ text: 'NEXUS' }).pairing };
}
},
{
id: 'font-hierarchy',
name: '字号层级检查',
category: 'typography',
emoji: '📐',
scene: 'LOGO 中主标题/副标题的字号比例审计',
summary: '检查主标题与副标题字号比是否在专业区间(主:副 约 2:1 ~ 3:1),字号过近会导致主次不分,给出调整建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(基于排版层级比例规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const texts = els.filter(el => el.type === 'text' && el.visible !== false)
.sort((a, b) => (b.fontSize || 0) - (a.fontSize || 0));
if (texts.length < 2) {
return { pass: true, note: '仅 1 个文字元素或无需层级检查', texts: texts.map(t => ({ text: t.text, size: t.fontSize })) };
}
const main = texts[0], sub = texts[1];
const ratio = Math.round(((main.fontSize || 24) / (sub.fontSize || 18)) * 100) / 100;
const good = ratio >= 1.8 && ratio <= 3.5;
return {
pass: good,
ratio,
main: { text: main.text, size: main.fontSize },
sub: { text: sub.text, size: sub.fontSize },
verdict: good ? '层级比例专业(主:副 ≈ ' + ratio + ':1)' : '层级偏平(主:副 ≈ ' + ratio + ':1),建议副标题缩小或主标题加大'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '远渡教育咨询' });
return this.run({ plan });
}
},
{
id: 'text-fit',
name: '文字适配检查',
category: 'typography',
emoji: '✍️',
scene: '长品牌名文字是否超出容器的检查',
summary: '估算文字渲染宽度并与容器/画布宽度比较:文字过宽会破版(尤其徽章/印章圆形布局),给出缩字或换行建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(文字宽度估算 + 容器适配)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600;
const texts = els.filter(el => el.type === 'text' && el.visible !== false);
const issues = [], ok = [];
for (const t of texts) {
const w = U.estTextW(t);
const container = (t.containerW || (t.layoutId && t.layoutId.includes('badge') ? CW * 0.62 : CW * 0.9));
if (w > container) {
issues.push(`"${t.text}" 估算宽 ${Math.round(w)}px 超出容器 ${Math.round(container)}px,建议缩小字号或缩短文案`);
} else {
ok.push(`"${t.text}" 适配良好(${Math.round(w)}px / 容器 ${Math.round(container)}px)`);
}
}
return { pass: issues.length === 0, issues, ok, total: texts.length };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '一个非常长的品牌名称测试' });
return this.run({ plan });
}
},
{
id: 'font-weight-choose',
name: '字重选择建议',
category: 'typography',
emoji: '🔠',
scene: '按行业与品牌气质选择主标题字重',
summary: '依据行业与气质关键词推荐主标题字重:科技/力量用 Bold/Black,高端/优雅用 Light/Regular,教育培训用 Medium 兼具辨识度与亲和。',
params: [
{ name: 'industry', type: 'string', required: false, desc: '行业 id' },
{ name: 'desc', type: 'string', required: false, desc: '品牌气质描述(如 高端/活力/科技)' }
],
source: '自研(字重 × 气质映射规范)',
run: function (args) {
const ind = HGB_Industries.find(x => x.id === args.industry) || HGB_Industries[0];
const desc = String(args.desc || '');
const map = [
{ kw: ['高端', '奢华', '优雅', '轻奢', 'premium', 'luxury'], w: 'Light / Regular', note: '细字重传递优雅与克制' },
{ kw: ['科技', '力量', '运动', '硬朗', 'future'], w: 'Bold / Black', note: '粗字重强化力量与冲击' },
{ kw: ['活力', '年轻', '潮流', '时尚', 'fun'], w: 'SemiBold / Bold', note: '中等偏粗兼顾活力与辨识' },
{ kw: ['温馨', '亲切', '治愈', '自然'], w: 'Regular / Medium', note: '常规字重亲和耐读' }
];
const hit = map.find(m => m.kw.some(k => desc.includes(k)));
return {
industry: ind.name,
recommend: hit ? hit.w : 'Bold(标题)/ Regular(正文)',
note: hit ? hit.note : '默认粗标题 + 常规正文,按落地场景微调',
weightScale: ['Thin', 'Light', 'Regular', 'Medium', 'SemiBold', 'Bold', 'Black']
};
},
test: function () {
return { lux: this.run({ desc: '高端优雅' }).recommend, tech: this.run({ industry: 'tech' }).recommend };
}
},
{
id: 'tiny-readable',
name: '小尺寸可读性',
category: 'typography',
emoji: '🔎',
scene: 'LOGO/头像在小尺寸(64px 头像、favicon)下的可读性检查',
summary: '按渲染尺寸推算文字的有效像素大小:头像 64px、Favicon 32px 场景下字号过小会糊成一片,输出最小可读字号建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(画布 600px → 目标尺寸换算可读性)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600;
const texts = els.filter(el => el.type === 'text' && el.visible !== false);
if (!texts.length) return { pass: true, note: '无文字元素,跳过检查' };
const rows = texts.map(t => {
const fs = t.fontSize || 24;
const at64 = Math.round(fs * (64 / CW) * 10) / 10;
const at32 = Math.round(fs * (32 / CW) * 10) / 10;
return { text: t.text, fontSize: fs, at64px: at64, at32px: at32, readable: at64 >= 4 && at32 >= 2 };
});
const allOk = rows.every(r => r.readable);
return {
pass: allOk,
rows,
verdict: allOk ? '小尺寸下可读性良好' : '存在小尺寸不可读文字,建议加大字号或加粗',
suggest: allOk ? '无需调整' : '主文字建议 ≥ 40px(600 画布),64px 头像仍可辨;Favicon 场景建议纯图形'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '小尺寸测试' });
return this.run({ plan });
}
},
{
id: 'letter-spacing-check',
name: '字距检查',
category: 'typography',
emoji: '↔️',
scene: '品牌文字字距(letter-spacing)合理性检查',
summary: '检查文字字距是否过大或过小:字距过大显松散、过小拥挤,中英文场景给出相对字号的字距建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(字距可读性规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const texts = els.filter(el => el.type === 'text' && el.visible !== false);
if (!texts.length) return { pass: true, note: '无文字元素' };
const rows = texts.map(t => {
const fs = t.fontSize || 24;
const ls = t.letterSpacing || 0;
const ratio = Math.round((ls / fs) * 1000) / 1000;
const pass = ratio >= -0.05 && ratio <= 0.25;
return { text: t.text, fontSize: fs, letterSpacing: ls, ratio, pass };
});
const allOk = rows.every(r => r.pass);
return {
pass: allOk, rows,
verdict: allOk ? '字距合理' : '存在字距异常(过大或过小),建议调整',
suggest: allOk ? '无需调整' : '中文字距建议 0~0.2em、西文标题 0~0.1em;过大松散、过小拥挤'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '字距测试' });
return this.run({ plan });
}
},
{
id: 'line-break',
name: '文字断行建议',
category: 'typography',
emoji: '↩️',
scene: '多行品牌文字的断行位置与行长均衡建议',
summary: '检查多行文字各行的字符长度是否均衡、是否出现孤行(单字/单字母成行):均衡断行更耐看,孤行与悬殊行长会破坏排版节奏。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(断行均衡/孤行规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const multi = els.filter(el => el.type === 'text' && el.visible !== false && String(el.text || '').includes('\n'));
if (!multi.length) return { pass: true, note: '无多行文字,无需断行检查' };
const rows = multi.map(t => {
const lines = String(t.text).split('\n').map(s => s.trim());
const lens = lines.map(l => l.length);
const max = Math.max(...lens);
const balance = max === 0 ? 1 : Math.round((Math.min(...lens) / max) * 100) / 100;
const orphan = lens.length > 1 && Math.min(...lens) === 1;
const pass = balance >= 0.5 && !orphan;
return { text: t.text, lines: lens, balance, orphan, pass };
});
const allOk = rows.every(r => r.pass);
return {
pass: allOk, rows,
verdict: allOk ? '断行均衡、无孤行' : '存在断行不均衡或孤行,建议调整分行',
suggest: allOk ? '无需调整' : '尽量让各行长度接近(最短/最长 ≥ 0.5),避免单字/单字母孤行'
};
},
test: function () {
return { bad: this.run({ plan: { elements: [{ type: 'text', text: '远\n渡设计' }] } }).pass, ok: this.run({ plan: { elements: [{ type: 'text', text: '远渡\n设计' }] } }).pass };
}
},
{
id: 'cjk-mix-check',
name: '中英混排检查',
category: 'typography',
emoji: '🈴',
scene: 'LOGO/海报中中英文混排的间距、字距与搭配检查',
summary: '检测文字元素是否中英混排:混排场景检查字距是否异常(过大破坏西文连排、过小易粘连),并输出中西文间距、字体搭配与对齐建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(中西文混排排版规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const texts = els.filter(el => el.type === 'text' && el.visible !== false);
if (!texts.length) return { pass: true, note: '无文字元素' };
const rows = texts.map(t => {
const s = String(t.text || '');
const hasCjk = /[\u4e00-\u9fa5]/.test(s);
const hasLatin = /[a-zA-Z0-9]/.test(s);
const mixed = hasCjk && hasLatin;
const fs = t.fontSize || 24;
const ls = t.letterSpacing || 0;
const ratio = fs ? Math.round((ls / fs) * 1000) / 1000 : 0;
const spOk = mixed ? (ratio >= -0.05 && ratio <= 0.1) : true;
return { text: s.slice(0, 18) + (s.length > 18 ? '…' : ''), mixed, cjk: hasCjk, latin: hasLatin, spacing: ratio, spOk };
});
const mixed = rows.filter(r => r.mixed);
const bad = mixed.filter(r => !r.spOk);
return {
pass: bad.length === 0, rows, mixedCount: mixed.length,
verdict: mixed.length === 0 ? '无中英混排文字,无需检查' : (bad.length === 0 ? '中英混排字距合理(' + mixed.length + ' 处)' : bad.length + ' 处中英混排字距异常,建议中西文之间加 1/4 空格并统一字距'),
suggest: mixed.length ? '中西文混排建议:① 中文与英文/数字之间加半角空格;② 中文用中文字体、西文用西文字体,避免西文挤占中文字宽;③ 标题场景字距不超过 0.1em,正文不超过 0.05em' : '无需调整'
};
},
test: function () {
const bad = { plan: { elements: [{ type: 'text', text: 'ABC远渡教育2026', fontSize: 24, letterSpacing: 12 }] } };
const pure = { plan: { elements: [{ type: 'text', text: '远渡教育', fontSize: 24 }] } };
return { mixed: this.run(bad).mixedCount, pure: this.run(pure).mixedCount, spBad: this.run(bad).rows[0].spOk === false };
}
}
]);
})();