/** * 画个标 · 技能库:配色类(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 !== '' }; } } ]); })();