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
+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 !== '' };
}
}
]);
})();