1451 lines
92 KiB
JavaScript
1451 lines
92 KiB
JavaScript
/**
|
||
* 画个标 · 海报技能(8 类示例,后续按类拆分为独立文件)
|
||
* 每类先收录 1 个代表性技能,run(args) 纯函数返回建议,test() 自跑。
|
||
* 来源标注「自研」或 GitHub 仓库 + 方法。
|
||
*/
|
||
HGB_PosterSkills.register([
|
||
/* ---------- layout 版式构图 ---------- */
|
||
{
|
||
id: 'poster-balance', name: '版式平衡', category: 'layout', emoji: '⚖️',
|
||
scene: '评估海报左右/上下视觉重量是否均衡,避免一侧过重',
|
||
summary: '把海报划分为九宫格,按元素面积与位置计算视觉重心偏移,给出调整建议。',
|
||
params: { elements: '元素数组(含 x/y/w/h 与权重 weight)', canvas: '{w,h}' },
|
||
source: '自研',
|
||
run(args) {
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
const cw = (args.canvas && args.canvas.w) || 1000;
|
||
const ch = (args.canvas && args.canvas.h) || 1500;
|
||
if (!els.length) return { ok: true, note: '无元素,无法评估' };
|
||
let mx = 0, mw = 0;
|
||
for (const e of els) {
|
||
const w = e.weight || e.w * e.h;
|
||
mx += (e.x + e.w / 2) * w; mw += w;
|
||
}
|
||
const cx = mw ? mx / mw : cw / 2;
|
||
const dx = (cx - cw / 2) / (cw / 2);
|
||
const ok = Math.abs(dx) < 0.15;
|
||
return { ok, offset: (dx * 100).toFixed(1) + '%', tip: ok ? '视觉重心均衡' : (dx > 0 ? '重心偏右,建议左侧补元素或右侧留白' : '重心偏左,建议右侧补元素或左侧留白') };
|
||
},
|
||
test() { return this.run({ canvas: { w: 1000, h: 1500 }, elements: [{ x: 100, y: 100, w: 300, h: 300 }, { x: 600, y: 100, w: 300, h: 300 }] }); }
|
||
},
|
||
|
||
/* ---------- color 配色 ---------- */
|
||
{
|
||
id: 'poster-palette', name: '海报配色', category: 'color', emoji: '🎨',
|
||
scene: '基于主色生成 60-30-10 配色比例(主色/辅助色/点缀色)',
|
||
summary: '输入主色 hex,返回辅助色与点缀色建议及使用比例,保证海报色彩层次。',
|
||
params: { primary: '主色 hex', style: '风格关键词(可选)' },
|
||
source: '自研(60-30-10 配色法则)',
|
||
run(args) {
|
||
const c = String(args.primary || '#4f46e5');
|
||
return {
|
||
ok: true,
|
||
ratio: '60-30-10',
|
||
primary: c,
|
||
secondary: '建议用主色的同色系浅色或中性灰(#eef2ff 类)',
|
||
accent: '建议用主色的对比色(如 #f59e0b)作点缀',
|
||
tip: '60% 主色背景 / 30% 辅助色信息区 / 10% 点缀色强调关键信息'
|
||
};
|
||
},
|
||
test() { return this.run({ primary: '#4f46e5' }); }
|
||
},
|
||
|
||
/* ---------- typography 字体排版 ---------- */
|
||
{
|
||
id: 'poster-type-scale', name: '字体层级', category: 'typography', emoji: '🔤',
|
||
scene: '为标题/副标题/正文建议字号比例与字重,建立清晰阅读层级',
|
||
summary: '按 1.25 比例(Major Third)由基准正文字号推导各层级字号。',
|
||
params: { base: '正文字号 px(默认 16)', levels: '层级数(默认 4)' },
|
||
source: '自研(Modular Scale)',
|
||
run(args) {
|
||
const base = Number(args.base) || 16;
|
||
const levels = Math.min(Math.max(Number(args.levels) || 4, 3), 6);
|
||
const scale = [];
|
||
for (let i = 0; i < levels; i++) scale.push(Math.round(base * Math.pow(1.25, i)));
|
||
return { ok: true, base, scale, tip: '层级字号(小→大):' + scale.join(' / ') + ' px;标题建议加粗、正文保持常规' };
|
||
},
|
||
test() { return this.run({ base: 16, levels: 4 }); }
|
||
},
|
||
|
||
/* ---------- grid 网格系统 ---------- */
|
||
{
|
||
id: 'poster-columns', name: '分栏网格', category: 'grid', emoji: '📐',
|
||
scene: '建议海报分栏数与栏间距,建立对齐骨架',
|
||
summary: '根据画布宽高比推荐分栏数与安全边距(栅格系统)。',
|
||
params: { ratio: '画布宽高比(如 0.66 竖版 / 1.78 横版)' },
|
||
source: '自研(栅格系统)',
|
||
run(args) {
|
||
const r = Number(args.ratio) || 0.66;
|
||
const cols = r < 1 ? 3 : 4; // 竖版 3 栏、横版 4 栏
|
||
return { ok: true, cols, gutter: '栏间距建议 24-40px,四周安全边距 ≥ 48px', tip: '关键信息对齐网格线,避免元素悬空' };
|
||
},
|
||
test() { return this.run({ ratio: 0.66 }); }
|
||
},
|
||
|
||
/* ---------- hierarchy 视觉层级 ---------- */
|
||
{
|
||
id: 'poster-focal', name: '视觉焦点', category: 'hierarchy', emoji: '🎯',
|
||
scene: '判断海报是否只有一个清晰的主视觉焦点,避免信息平铺',
|
||
summary: '按元素面积/字号/对比度估算视觉权重,找出最强焦点并检查是否唯一。',
|
||
params: { elements: '元素数组(含 w/h/fontSize/weight)' },
|
||
source: '自研',
|
||
run(args) {
|
||
const els = (args.elements || []).map(e => ({ ...e, score: (e.weight || 0) || (e.fontSize || 0) || (e.w * e.h) }));
|
||
els.sort((a, b) => b.score - a.score);
|
||
const top = els[0], second = els[1];
|
||
const unique = !second || top.score > second.score * 1.6;
|
||
return { ok: unique, top: top && top.id, tip: unique ? '主焦点清晰' : '存在多个相近焦点,建议强化主视觉或弱化次要元素' };
|
||
},
|
||
test() { return this.run({ elements: [{ id: 'title', fontSize: 120 }, { id: 'sub', fontSize: 40 }] }); }
|
||
},
|
||
|
||
/* ---------- print 印刷输出 ---------- */
|
||
{
|
||
id: 'poster-bleed', name: '出血安全区', category: 'print', emoji: '🖨️',
|
||
scene: '计算印刷出血与安全区,避免裁切误伤关键内容',
|
||
summary: '按常见印刷规范输出出血(3mm)与安全边距,给出裁切警戒提示。',
|
||
params: { size: '成品尺寸(如 A4/A3 或宽×高 mm)', dpi: '分辨率(默认 300)' },
|
||
source: '自研(印刷规范)',
|
||
run(args) {
|
||
const dpi = Number(args.dpi) || 300;
|
||
const bleedPx = Math.round(3 / 25.4 * dpi); // 3mm 出血
|
||
const safePx = Math.round(6 / 25.4 * dpi); // 6mm 安全区
|
||
return {
|
||
ok: true, bleedPx, safePx, dpi,
|
||
tip: `出血 ${bleedPx}px,安全区 ${safePx}px;关键文字/Logo 保持在安全区内,背景/底纹延展到出血线外`
|
||
};
|
||
},
|
||
test() { return this.run({ dpi: 300 }); }
|
||
},
|
||
|
||
/* ---------- creative 创意手法 ---------- */
|
||
{
|
||
id: 'poster-contrast', name: '对比手法', category: 'creative', emoji: '🆚',
|
||
scene: '通过大小/疏密/明暗/动静对比制造视觉张力',
|
||
summary: '输出 6 种常用海报对比手法,供设计时组合运用。',
|
||
params: {},
|
||
source: '自研(设计原则)',
|
||
run() {
|
||
return {
|
||
ok: true,
|
||
methods: ['大小对比(大标题 vs 小正文)', '疏密对比(留白 vs 密集信息)', '明暗对比(深底浅字)', '动静对比(斜切/旋转 vs 平直)', '色彩对比(互补色强调)', '虚实对比(模糊背景 vs 清晰主体)'],
|
||
tip: '一张海报优先强化 1-2 种对比,避免手法堆砌'
|
||
};
|
||
},
|
||
test() { return this.run({}); }
|
||
},
|
||
|
||
/* ---------- ai 智能工作流 ---------- */
|
||
{
|
||
id: 'poster-brief', name: '海报需求洞察', category: 'ai', emoji: '🔍',
|
||
scene: '把活动/主题描述提炼为关键词与设计方向,指导 AI 生成',
|
||
summary: '输入主题描述,提取受众/情绪/主色/版式倾向等设计决策线索。',
|
||
params: { brief: '海报主题描述', industry: '行业(可选)' },
|
||
source: '自研',
|
||
run(args) {
|
||
const b = String(args.brief || '');
|
||
const mood = /高端|奢华|商务|正式/.test(b) ? '稳重正式' : (/活泼|促销|狂欢|party|节日/.test(b) ? '热闹活泼' : '中性现代');
|
||
const warm = /餐饮|美食|促销|节日|母婴/.test(b);
|
||
return {
|
||
ok: true, mood,
|
||
palette: warm ? '暖色系(橙/红/黄)' : '冷色系(蓝/紫/绿)或中性色',
|
||
tip: `气质:${mood};建议配色 ${warm ? '暖色系' : '冷色系/中性色'};排版按受众与情绪选择对称或自由版式`
|
||
};
|
||
},
|
||
test() { return this.run({ brief: '夏季餐饮促销狂欢节' }); }
|
||
},
|
||
|
||
/* ============================================================
|
||
* 第二批补充技能(2026-08-21 录入,每类 +1,run 纯函数 + test 自测)
|
||
* ============================================================ */
|
||
|
||
/* ---------- layout 版式构图 ---------- */
|
||
{
|
||
id: 'poster-zpattern', name: 'Z 型视线流', category: 'layout', emoji: '👀',
|
||
scene: '按 Z / F 型阅读路径检查信息摆放顺序,避免用户视线跳跃',
|
||
summary: '根据元素位置与权重,评估是否形成自然的 Z 型(横版)/ F 型(竖版)视线流。',
|
||
params: { elements: '元素数组(含 x/y/w/h 与 weight)', ratio: '画布宽高比(默认 0.66 竖版)' },
|
||
source: '自研(视线流设计法)',
|
||
run(args) {
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
const r = Number(args.ratio) || 0.66;
|
||
if (els.length < 3) return { ok: true, tip: '元素较少,视线流压力小,重点保证主标题在首屏上方' };
|
||
const z = r >= 1; // 横版 Z / 竖版 F
|
||
const row = (e) => Math.round(e.y / (e.h || 1)); // 简化按纵向分段
|
||
const top = els.filter(e => row(e) === 0);
|
||
const bottom = els.filter(e => row(e) === Math.max(...els.map(row)));
|
||
const hasCorner = top.some(e => e.x < 10) && bottom.some(e => e.x < 10) && bottom.some(e => e.x + e.w > 80);
|
||
return {
|
||
ok: hasCorner,
|
||
type: z ? 'Z 型(横版)' : 'F 型(竖版)',
|
||
tip: hasCorner ? '视线流完整:左上起点 → 对角 → 右下收尾' : (z ? '建议左上放 Logo/主标题、右下放行动按钮(CTA),形成 Z 字收尾' : '建议左侧竖列信息对齐,底部放 CTA 引导行动')
|
||
};
|
||
},
|
||
test() { return this.run({ ratio: 1.78, elements: [{ x: 5, y: 5, w: 30, h: 10, weight: 3 }, { x: 60, y: 40, w: 30, h: 10, weight: 2 }, { x: 5, y: 80, w: 20, h: 10, weight: 1 }, { x: 70, y: 80, w: 20, h: 10, weight: 2 }] }); }
|
||
},
|
||
|
||
/* ---------- color 配色 ---------- */
|
||
{
|
||
id: 'poster-wcag-contrast', name: '文字对比度', category: 'color', emoji: '🎚️',
|
||
scene: '计算前景/背景颜色对比度(WCAG),确保文字可读性',
|
||
summary: '输入前景色与背景色 hex,返回 WCAG 对比率与 AA/AAA 达标等级,并给出调整建议。',
|
||
params: { fg: '前景色 hex', bg: '背景色 hex', size: '文字大小 px(默认 16,大字号 18 粗体/24 起阈值更低)' },
|
||
source: '自研(WCAG 2.1 对比度规范)',
|
||
run(args) {
|
||
const lum = (hex) => {
|
||
const c = (hex || '').replace('#', '');
|
||
const n = c.length === 3 ? c.split('').map(x => x + x).join('') : c;
|
||
const rgb = [0, 2, 4].map(i => parseInt(n.slice(i, i + 2), 16) / 255)
|
||
.map(v => v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4));
|
||
return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
|
||
};
|
||
const fg = lum(args.fg || '#111111'), bg = lum(args.bg || '#ffffff');
|
||
const ratio = (Math.max(fg, bg) + 0.05) / (Math.min(fg, bg) + 0.05);
|
||
const size = Number(args.size) || 16;
|
||
const large = size >= 24 || (size >= 18.66 && (args.bold === '1' || args.bold === true));
|
||
const aa = large ? ratio >= 3 : ratio >= 4.5;
|
||
const aaa = large ? ratio >= 4.5 : ratio >= 7;
|
||
return {
|
||
ok: aa, ratio: ratio.toFixed(2) + ':1',
|
||
grade: aaa ? 'AAA' : (aa ? 'AA' : '不达标'),
|
||
tip: aa ? (aaa ? '对比度优秀(AAA)' : '对比度达标(AA)') : `对比度 ${ratio.toFixed(2)}:1 偏低,建议加深文字或提亮背景至 ≥${large ? 3 : 4.5}:1`
|
||
};
|
||
},
|
||
test() { return this.run({ fg: '#111111', bg: '#ffffff', size: 16 }); }
|
||
},
|
||
|
||
/* ---------- typography 字体排版 ---------- */
|
||
{
|
||
id: 'poster-font-pairing', name: '字体搭配', category: 'typography', emoji: '✍️',
|
||
scene: '为海报推荐标题/正文的字体搭配组合与注意事项',
|
||
summary: '按风格关键词输出标题字体 × 正文字体的搭配建议,附版权提示。',
|
||
params: { style: '风格关键词(高端/现代/文艺/复古/促销/科技,默认 现代)' },
|
||
source: '自研(字体搭配方法论)',
|
||
run(args) {
|
||
const s = String(args.style || '现代');
|
||
const map = {
|
||
'高端': { title: '衬线/窄体(如 Didot、思源宋体)', body: '细黑体(如思源黑体 Light)', tip: '标题字距放宽、字号差拉大,突出优雅与克制' },
|
||
'文艺': { title: '手写/宋体(如 楷体、恋字体)', body: '雅黑或宋体', tip: '标题可用竖排或居中留白营造诗意氛围' },
|
||
'复古': { title: '粗衬线/木刻体(如 方正超粗黑、League Spartan Black)', body: '中等黑体', tip: '可叠加做旧纹理与印章元素增强年代感' },
|
||
'促销': { title: '超粗黑体(如 思源黑体 Heavy)', body: '中黑体', tip: '大字号 + 倾斜/描边强化冲击力,价格用粗体放大' },
|
||
'科技': { title: '几何无衬线(如 Montserrat、Raleway)', body: '细无衬线', tip: '标题可加渐变描边或字母间距,呼应数码感' },
|
||
'现代': { title: '几何无衬线(如 思源黑体 Bold)', body: '思源黑体 Regular', tip: '同族字重组合最稳;标题与正文字号比建议 ≥3:1' }
|
||
};
|
||
const r = map[s] || map['现代'];
|
||
return { ok: true, title: r.title, body: r.body, tip: r.tip + ';海报正文避免超过 2 款字体' };
|
||
},
|
||
test() { return this.run({ style: '促销' }); }
|
||
},
|
||
|
||
/* ---------- grid 网格系统 ---------- */
|
||
{
|
||
id: 'poster-golden-ratio', name: '黄金分割构图', category: 'grid', emoji: '🌻',
|
||
scene: '用黄金比例(1:1.618)切分画布,快速定位主视觉与信息区',
|
||
summary: '按黄金分割生成主视觉区块与信息安全区坐标,指导版面分配。',
|
||
params: { w: '画布宽 px', h: '画布高 px' },
|
||
source: '自研(黄金分割 1:1.618)',
|
||
run(args) {
|
||
const w = Number(args.w) || 1000, h = Number(args.h) || 1500;
|
||
const g = (n) => n / 1.618;
|
||
const mw = Math.round(g(w)), mh = Math.round(g(h));
|
||
return {
|
||
ok: true,
|
||
focus: `主视觉区:左下 ${mw}×${mh}(宽 × 高 或按方向旋转使用)`,
|
||
info: `信息区:剩余 ${w - mw}×${h - mh}`,
|
||
tip: '把最重要内容放进黄金区块,次要点放对角,留白保持 ≥10% 画布'
|
||
};
|
||
},
|
||
test() { return this.run({ w: 1000, h: 1500 }); }
|
||
},
|
||
|
||
/* ---------- hierarchy 视觉层级 ---------- */
|
||
{
|
||
id: 'poster-reading-order', name: '阅读顺序', category: 'hierarchy', emoji: '🔢',
|
||
scene: '为海报信息元素排序,确保受众按设计意图依次阅读',
|
||
summary: '按「主标题→副标题→时间地点→行动按钮」的标准骨架检查元素顺序并编号。',
|
||
params: { elements: '元素数组(含 id、role:title/subtitle/time/place/cta/body)' },
|
||
source: '自研(信息层级方法论)',
|
||
run(args) {
|
||
const roleOrder = { title: 1, subtitle: 2, time: 3, place: 4, cta: 5, body: 6 };
|
||
const els = (args.elements || []).map((e, i) => ({ ...e, _idx: i }));
|
||
const withRole = els.filter(e => roleOrder[e.role]);
|
||
if (!withRole.length) return { ok: false, tip: '请为元素标注 role(title/subtitle/time/place/cta/body)以生成阅读顺序' };
|
||
const sorted = withRole.slice().sort((a, b) => roleOrder[a.role] - roleOrder[b.role]);
|
||
const order = sorted.map(e => e.id || e.role);
|
||
return {
|
||
ok: true,
|
||
order: order.join(' → '),
|
||
tip: '时间地点建议放在中段、CTA 放末尾最易点击位置;同 role 元素按出现顺序排列'
|
||
};
|
||
},
|
||
test() { return this.run({ elements: [{ id: '主题', role: 'title' }, { id: '时间', role: 'time' }, { id: '地点', role: 'place' }, { id: '报名按钮', role: 'cta' }, { id: '副题', role: 'subtitle' }] }); }
|
||
},
|
||
|
||
/* ---------- print 印刷输出 ---------- */
|
||
{
|
||
id: 'poster-print-size', name: '印刷尺寸换算', category: 'print', emoji: '📏',
|
||
scene: '换算 A 系列/常见开本成品尺寸到指定 DPI 的像素值',
|
||
summary: '内置 A 系列与常见海报开本尺寸表,按 DPI 输出像素宽高与安全提示。',
|
||
params: { size: '尺寸代号(A4/A3/A2/A1/B2/6K/4K,默认 A4)', dpi: '分辨率(默认 300)' },
|
||
source: '自研(ISO 216 / 印刷开本规范)',
|
||
run(args) {
|
||
const MM = { A4: [210, 297], A3: [297, 420], A2: [420, 594], A1: [594, 841], B2: [500, 707], '6K': [360, 390], '4K': [390, 540] };
|
||
const k = String(args.size || 'A4').toUpperCase();
|
||
const mm = MM[k];
|
||
if (!mm) return { ok: false, tip: '支持尺寸:' + Object.keys(MM).join(' / ') };
|
||
const dpi = Number(args.dpi) || 300;
|
||
const px = (m) => Math.round(m / 25.4 * dpi);
|
||
return {
|
||
ok: true, size: k, mm: mm[0] + '×' + mm[1] + 'mm',
|
||
px: px(mm[0]) + '×' + px(mm[1]) + 'px @' + dpi + 'dpi',
|
||
tip: '海报源文件建议 ≥300dpi;如仅屏幕展示 72dpi 即可;导出 PDF 选「印刷质量」'
|
||
};
|
||
},
|
||
test() { return this.run({ size: 'A3', dpi: 300 }); }
|
||
},
|
||
|
||
/* ---------- creative 创意手法 ---------- */
|
||
{
|
||
id: 'poster-moodboard', name: '情绪风格映射', category: 'creative', emoji: '🧭',
|
||
scene: '根据情绪关键词输出风格手法、配色倾向与字体建议',
|
||
summary: '将情绪词(活力/高级/可爱/冷酷…)映射为设计手法清单,作为创意起点。',
|
||
params: { mood: '情绪关键词(活力/高级/可爱/冷酷/治愈/复古)' },
|
||
source: '自研(情绪板方法论)',
|
||
run(args) {
|
||
const m = String(args.mood || '高级');
|
||
const map = {
|
||
'活力': { color: '高饱和橙/黄 + 撞色', font: '超粗黑体 + 斜切', trick: '大色块 + 波点/放射线背景,元素可轻微旋转' },
|
||
'高级': { color: '黑白灰 + 单一强调色', font: '衬线/细黑体 + 宽字距', trick: '大留白 + 细线条点缀,克制用色' },
|
||
'可爱': { color: '马卡龙色系(粉/薄荷/淡黄)', font: '圆体/手写体', trick: '圆角元素 + 表情/贴纸点缀 + 柔和阴影' },
|
||
'冷酷': { color: '深黑 + 霓虹/金属色', font: '窄体无衬线', trick: '硬边几何 + 网格线 + 高反差打光' },
|
||
'治愈': { color: '低饱和莫兰迪色系', font: '细黑体/宋体', trick: '柔和渐变 + 自然元素(云/叶/光斑)' },
|
||
'复古': { color: '棕/米/芥末黄 + 旧红', font: '衬线/木刻体', trick: '做旧纹理 + 相框/邮戳元素 + 半调网点' }
|
||
};
|
||
const r = map[m] || map['高级'];
|
||
return { ok: true, mood: m, color: r.color, font: r.font, trick: r.trick };
|
||
},
|
||
test() { return this.run({ mood: '治愈' }); }
|
||
},
|
||
|
||
/* ---------- ai 智能工作流 ---------- */
|
||
{
|
||
id: 'poster-ai-prompt', name: 'AI 生成提示词', category: 'ai', emoji: '🤖',
|
||
scene: '把海报需求组织成结构化 AI 提示词,提升生成质量与可控性',
|
||
summary: '按「角色-主体-背景-配色-字体-构图-风格」输出可直接投喂 AI 的提示词模板。',
|
||
params: { brief: '海报主题描述', industry: '行业(可选)' },
|
||
source: '自研(AI 海报生成工作台提示词工程)',
|
||
run(args) {
|
||
const b = String(args.brief || '夏日饮品促销');
|
||
const ind = String(args.industry || '餐饮');
|
||
return {
|
||
ok: true,
|
||
prompt: `请为「${ind}」设计一张「${b}」主题的海报。要求:1) 主体清晰突出,背景与主体层次分明;2) 配色和谐并符合行业气质;3) 构图遵循视觉层级,信息主次有序;4) 预留标题区与行动信息区(时间地点);5) 输出可落地的设计决策 JSON(布局/配色/字体/元素清单)。`,
|
||
tip: '描述越具体(人群/场景/调性/禁用元素)生成越可控;可附加参考色板与参考图'
|
||
};
|
||
},
|
||
test() { return this.run({ brief: '秋季新品上市', industry: '时尚' }); }
|
||
},
|
||
|
||
/* ============================================================
|
||
* 第三批补充技能(2026-08-21 录入,每类 +1,run 纯函数 + test 自测)
|
||
* ============================================================ */
|
||
|
||
/* ---------- layout 版式构图 ---------- */
|
||
{
|
||
id: 'poster-thirds', name: '三分法构图', category: 'layout', emoji: '➗',
|
||
scene: '按三分法(Rule of Thirds)检查主视觉与交点分布,快速优化构图',
|
||
summary: '将画布横竖各分三份,检查关键元素是否落在 4 个交点附近,避免主体居中呆板。',
|
||
params: { elements: '元素数组(含 x/y/w/h 与 weight)', canvas: '{w,h}' },
|
||
source: '自研(Rule of Thirds 构图法)',
|
||
run(args) {
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
const w = (args.canvas && args.canvas.w) || 1000, h = (args.canvas && args.canvas.h) || 1500;
|
||
if (!els.length) return { ok: true, note: '无元素,无法评估' };
|
||
const ix = [w * 1 / 3, w * 2 / 3], iy = [h * 1 / 3, h * 2 / 3];
|
||
const near = (v, arr) => arr.some(a => Math.abs(v - a) < Math.max(w, h) * 0.12);
|
||
const onPoint = els.filter(e => near(e.x + e.w / 2, ix) && near(e.y + e.h / 2, iy)).length;
|
||
return {
|
||
ok: onPoint > 0,
|
||
thirds: `${ix.map(v => Math.round(v)).join('/')}(横)× ${iy.map(v => Math.round(v)).join('/')}(纵)`,
|
||
tip: onPoint ? '已有元素落在三分点附近,视觉停留感好' : '建议把主视觉/标题放到三分线交点上(左上或右下),营造动势与留白'
|
||
};
|
||
},
|
||
test() { return this.run({ canvas: { w: 1000, h: 1500 }, elements: [{ x: 667, y: 500, w: 100, h: 100 }] }); }
|
||
},
|
||
|
||
/* ---------- color 配色 ---------- */
|
||
{
|
||
id: 'poster-harmony', name: '色彩和谐', category: 'color', emoji: '🌈',
|
||
scene: '基于主色生成相邻色/互补色和谐色板,保证画面协调',
|
||
summary: '输入主色 hex,输出色相偏移 ±30° 的相邻色与 ±180° 的互补色建议,附和谐度提示。',
|
||
params: { primary: '主色 hex', scheme: '方案:analogous(相邻)/complementary(互补),默认 analogous' },
|
||
source: '自研(色相环配色法则)',
|
||
run(args) {
|
||
const hex = String(args.primary || '#4f46e5').replace('#', '');
|
||
const r = parseInt(hex.slice(0, 2), 16), g = parseInt(hex.slice(2, 4), 16), b = parseInt(hex.slice(4, 6), 16);
|
||
const toHsl = () => {
|
||
const rn = r / 255, gn = g / 255, bn = b / 255;
|
||
const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
|
||
let h = 0, s = 0, l = (max + min) / 2;
|
||
if (max !== min) {
|
||
const d = max - min;
|
||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||
if (max === rn) h = (gn - bn) / d + (gn < bn ? 6 : 0);
|
||
else if (max === gn) h = (bn - rn) / d + 2;
|
||
else h = (rn - gn) / d + 4;
|
||
h *= 60;
|
||
}
|
||
return { h, s, l };
|
||
};
|
||
const hslToHex = (h, s, l) => {
|
||
const fn = (p, q, t) => {
|
||
if (t < 0) t += 1; if (t > 1) t -= 1;
|
||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||
if (t < 1 / 2) return q;
|
||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||
return p;
|
||
};
|
||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||
const p = 2 * l - q;
|
||
const c = [fn(p, q, h / 360 + 1 / 3), fn(p, q, h / 360), fn(p, q, h / 360 - 1 / 3)]
|
||
.map(v => Math.round(v * 255).toString(16).padStart(2, '0'));
|
||
return '#' + c.join('');
|
||
};
|
||
const { h, s, l } = toHsl();
|
||
const scheme = String(args.scheme || 'analogous');
|
||
const pairs = scheme === 'complementary'
|
||
? [[(h + 150 + 360) % 360, '相邻左'], [(h + 180) % 360, '互补'], [(h + 210) % 360, '相邻右']]
|
||
: [[(h - 30 + 360) % 360, '相邻左'], [(h + 30) % 360, '相邻右'], [(h + 180) % 360, '互补(点缀)']];
|
||
return {
|
||
ok: true, scheme: scheme === 'complementary' ? '互补配色' : '相邻配色',
|
||
colors: pairs.map(([hh, label]) => ({ color: hslToHex(hh, s, l), label })),
|
||
tip: scheme === 'complementary' ? '互补色对比强,建议主色占 60%、互补色仅作点缀' : '相邻色协调柔和,建议用明度差(l ± 0.15)建立层次'
|
||
};
|
||
},
|
||
test() { return this.run({ primary: '#4f46e5', scheme: 'complementary' }); }
|
||
},
|
||
|
||
/* ---------- typography 字体排版 ---------- */
|
||
{
|
||
id: 'poster-cjk-latin', name: '中英混排', category: 'typography', emoji: '🈴',
|
||
scene: '检查中文与英文/数字混排的对齐、字距与行高问题',
|
||
summary: '解析文本中的中英文片段占比,输出混排行高、字距与中西文间距(U+2005 薄空格)建议。',
|
||
params: { text: '待排版文案' },
|
||
source: '自研(中西文混排排版规范)',
|
||
run(args) {
|
||
const t = String(args.text || '');
|
||
const cn = (t.match(/[\u4e00-\u9fa5]/g) || []).length;
|
||
const en = (t.match(/[A-Za-z0-9]/g) || []).length;
|
||
const total = Math.max(cn + en, 1);
|
||
const hasMix = cn > 0 && en > 0;
|
||
return {
|
||
ok: true, mix: hasMix,
|
||
cn, en, cnRatio: (cn / total * 100).toFixed(0) + '%',
|
||
tip: hasMix
|
||
? '建议行高 1.6-1.8(纯中文 1.5-1.6 需提高以容纳西文上升部);中西文间加 U+2005 薄空格;西文数字用等宽或半角,避免与中文基线错位'
|
||
: '单语种文案,保持常规行高(中文 1.5 / 西文 1.4)即可'
|
||
};
|
||
},
|
||
test() { return this.run({ text: '活动时间 2026.09.01,欢迎参加 Summer Party' }); }
|
||
},
|
||
|
||
/* ---------- grid 网格系统 ---------- */
|
||
{
|
||
id: 'poster-symmetry', name: '对称检查', category: 'grid', emoji: '🪞',
|
||
scene: '判断版面是否刻意对称,并提示对称/不对称的适用场景',
|
||
summary: '按元素左右镜像位置计算对称偏差,输出对称类型(镜面对称/局部对称/非对称)建议。',
|
||
params: { elements: '元素数组(含 x/y/w/h)', canvas: '{w,h}' },
|
||
source: '自研(对称构图法)',
|
||
run(args) {
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
const w = (args.canvas && args.canvas.w) || 1000;
|
||
if (!els.length) return { ok: true, note: '无元素,无法评估' };
|
||
const tolerance = w * 0.06;
|
||
const mirrored = els.map(e => ({ x2: w - e.x - e.w, y: e.y, w: e.w, h: e.h }))
|
||
.map(m => els.some(e => Math.abs(e.x - m.x2) <= tolerance && Math.abs(e.y - m.y) <= tolerance && Math.abs(e.w - m.w) <= tolerance));
|
||
const symN = mirrored.filter(Boolean).length;
|
||
const type = symN === els.length ? '严格镜面对称' : (symN > 0 ? '局部对称' : '非对称(自由)');
|
||
return {
|
||
ok: true, type, symCount: symN + '/' + els.length,
|
||
tip: type === '严格镜面对称' ? '正式/庄重场景合适,注意避免呆板(可加不对称点缀)' : (type === '局部对称' ? '对称与自由混搭,动感与秩序兼得' : '非对称构图更有张力,需保证视觉重心平衡')
|
||
};
|
||
},
|
||
test() { return this.run({ canvas: { w: 1000, h: 1500 }, elements: [{ x: 100, y: 100, w: 200, h: 100 }, { x: 700, y: 100, w: 200, h: 100 }] }); }
|
||
},
|
||
|
||
/* ---------- hierarchy 视觉层级 ---------- */
|
||
{
|
||
id: 'poster-priority', name: '信息优先级', category: 'hierarchy', emoji: '🏷️',
|
||
scene: '为海报信息元素排出视觉优先级并映射到字号/色彩/位置',
|
||
summary: '输入信息条目与角色,输出优先级排序及对应视觉强化手段(字号/颜色/留白/位置)。',
|
||
params: { items: '信息数组:{id, role: title/subtitle/body/cta/detail}' },
|
||
source: '自研(信息优先级方法论)',
|
||
run(args) {
|
||
const rank = { title: 1, cta: 2, subtitle: 3, detail: 4, body: 5 };
|
||
const items = (args.items || []).filter(i => rank[i.role]);
|
||
if (!items.length) return { ok: false, tip: '请为信息标注 role(title/cta/subtitle/detail/body)' };
|
||
const sorted = items.slice().sort((a, b) => rank[a.role] - rank[b.role]);
|
||
return {
|
||
ok: true,
|
||
order: sorted.map(i => i.id || i.role).join(' > '),
|
||
suggestion: {
|
||
title: '最大字号 + 高对比色,居上或视觉中心',
|
||
cta: '次大字号 + 强调色/描边,放右下或底部易点击区',
|
||
subtitle: '比标题小 40%-50%,靠近标题',
|
||
detail: '中号字,弱化(低对比/小号)',
|
||
body: '最小字号,最后阅读'
|
||
},
|
||
tip: '优先级最高只保留 1-2 个;其余信息通过字号差 ≥2 级、颜色降饱和、位置下移逐级弱化'
|
||
};
|
||
},
|
||
test() { return this.run({ items: [{ id: '主标题', role: 'title' }, { id: '报名按钮', role: 'cta' }, { id: '详情', role: 'detail' }] }); }
|
||
},
|
||
|
||
/* ---------- print 印刷输出 ---------- */
|
||
{
|
||
id: 'poster-cmyk', name: '印刷色域', category: 'print', emoji: '🖍️',
|
||
scene: '将 RGB 颜色换算为 CMYK 并提示印刷色差风险',
|
||
summary: '按标准公式把 hex 转为 CMYK 四色值,对高饱和/金属色给出偏色与备选建议。',
|
||
params: { hex: '颜色 hex' },
|
||
source: '自研(CMYK 转换公式,灰成分替代法)',
|
||
run(args) {
|
||
const hex = String(args.hex || '#e63946').replace('#', '');
|
||
const r = parseInt(hex.slice(0, 2), 16) / 255, g = parseInt(hex.slice(2, 4), 16) / 255, b = parseInt(hex.slice(4, 6), 16) / 255;
|
||
const k = 1 - Math.max(r, g, b);
|
||
const c = k < 1 ? (1 - r - k) / (1 - k) : 0;
|
||
const m = k < 1 ? (1 - g - k) / (1 - k) : 0;
|
||
const y = k < 1 ? (1 - b - k) / (1 - k) : 0;
|
||
const toPct = v => Math.round(v * 100);
|
||
const vivid = (1 - k) > 0.85;
|
||
return {
|
||
ok: true, cmyk: `C${toPct(c)} M${toPct(m)} Y${toPct(y)} K${toPct(k)}`,
|
||
risk: vivid ? '高饱和色在四色印刷中易偏灰/偏暗,建议降低饱和度或用专色(PANTONE)' : '常规印刷色域内,风险低',
|
||
tip: '屏幕色(RGB)与印刷色(CMYK)存在色差,重要物料建议打样确认;纯黑文字用 K100,避免套印发虚'
|
||
};
|
||
},
|
||
test() { return this.run({ hex: '#e63946' }); }
|
||
},
|
||
|
||
/* ---------- creative 创意手法 ---------- */
|
||
{
|
||
id: 'poster-rhythm', name: '节奏韵律', category: 'creative', emoji: '🥁',
|
||
scene: '通过元素重复/渐变/对比建立版面节奏,避免信息平铺',
|
||
summary: '分析元素间距序列的规律性,输出重复/渐变/放射等节奏手法建议。',
|
||
params: { elements: '元素数组(含 x/y/w/h 或 weight)' },
|
||
source: '自研(版面节奏设计法)',
|
||
run(args) {
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
if (els.length < 3) return { ok: true, tip: '元素较少,可尝试将同类元素组复制成 3-5 组形成重复节奏' };
|
||
const xs = els.map(e => e.x).sort((a, b) => a - b);
|
||
const gaps = xs.slice(1).map((v, i) => v - xs[i]);
|
||
const same = gaps.every(g => Math.abs(g - gaps[0]) <= (Math.abs(gaps[0]) || 1) * 0.2);
|
||
return {
|
||
ok: same,
|
||
rhythm: same ? '重复节奏(等距)' : '渐变/自由节奏',
|
||
tip: same ? '等距重复稳重大气,可叠加尺寸/颜色渐变打破单调' : '间距不等已形成变化,建议按 1:1.6:2.6 之类比例递增,制造韵律而不凌乱'
|
||
};
|
||
},
|
||
test() { return this.run({ elements: [{ x: 0, w: 50 }, { x: 100, w: 50 }, { x: 200, w: 50 }, { x: 300, w: 50 }] }); }
|
||
},
|
||
|
||
/* ---------- ai 智能工作流 ---------- */
|
||
{
|
||
id: 'poster-ai-style', name: '风格迁移参考', category: 'ai', emoji: '🎭',
|
||
scene: '把目标风格关键词转译为 AI 可理解的设计参数(配色/字体/纹理/构图)',
|
||
summary: '输入风格名(极简/孟菲斯/波普/新丑风/赛博朋克…),输出该风格的可落地参数供 AI 生成。',
|
||
params: { style: '风格关键词(极简/孟菲斯/波普/新丑风/赛博朋克/酸性/手绘)' },
|
||
source: '自研(风格参数化方法)',
|
||
run(args) {
|
||
const s = String(args.style || '极简');
|
||
const map = {
|
||
'极简': { color: '黑白灰 + 1 个强调色', font: '细黑体/无衬线,大字号', texture: '大留白、细线框', compose: '居中或留白构图,元素 ≤3 个' },
|
||
'孟菲斯': { color: '高饱和撞色(粉/蓝/黄/绿)', font: '圆体/几何无衬线', texture: '波点、锯齿、几何碎片', compose: '不规则网格、错位排列' },
|
||
'波普': { color: '原色碰撞(红/蓝/黄/黑)', font: '粗衬线/木刻体', texture: '半调网点(Ben-Day Dots)', compose: '重复放大、漫画分格' },
|
||
'新丑风': { color: '荧光色 + 深底', font: '超粗黑体 + 描边', texture: '噪点、错位、倾斜', compose: '撞版、文字叠压、夸张比例' },
|
||
'赛博朋克': { color: '霓虹紫/青 + 黑', font: '窄体无衬线', texture: '故障(Glitch)、扫描线', compose: '透视网格、光效溢出' },
|
||
'酸性': { color: '荧光渐变(青/紫/粉)', font: '扭曲/液态文字', texture: '金属质感、折射光', compose: '流体造型、迷幻背景' },
|
||
'手绘': { color: '低饱和 + 纸张底色', font: '手写体/圆体', texture: '铅笔噪点、水彩边缘', compose: '自由随性、歪斜排列' }
|
||
};
|
||
const r = map[s] || map['极简'];
|
||
return { ok: true, style: s, color: r.color, font: r.font, texture: r.texture, compose: r.compose, tip: '把上述参数并入 AI 提示词,并在负面提示中禁止与该风格冲突的元素' };
|
||
},
|
||
test() { return this.run({ style: '赛博朋克' }); }
|
||
},
|
||
|
||
/* ---------- 第四批:layout 画框构图 ---------- */
|
||
{
|
||
id: 'poster-frame', name: '画框构图', category: 'layout', emoji: '🖼️',
|
||
scene: '检查元素是否侵入画布安全边距,建立「画框式」留白构图',
|
||
summary: '输入画布与元素,计算元素与画布边缘的最小间距,输出安全边距建议与越界元素清单。',
|
||
params: { canvas: '{w,h}', margin: '安全边距(默认 48px)', elements: '元素数组(含 x/y/w/h)' },
|
||
source: '自研(画框式构图法)',
|
||
run(args) {
|
||
const cw = (args.canvas && args.canvas.w) || 1000;
|
||
const ch = (args.canvas && args.canvas.h) || 1500;
|
||
const m = Number(args.margin) || 48;
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
if (!els.length) return { ok: true, note: '无元素,默认四周留白 ≥ ' + m + 'px 的「画框」式构图' };
|
||
const bad = els.filter(e => e.x < m || e.y < m || e.x + e.w > cw - m || e.y + e.h > ch - m);
|
||
return { ok: bad.length === 0, safeMargin: m, overflow: bad.map(e => e.id || e.name || '?'), tip: bad.length ? '有 ' + bad.length + ' 个元素侵入安全区,建议内移或缩放到 ' + m + 'px 边距内' : '所有元素均在安全边距内,画框式留白成立' };
|
||
},
|
||
test() { return this.run({ canvas: { w: 1000, h: 1500 }, margin: 48, elements: [{ id: 'a', x: 20, y: 50, w: 200, h: 100 }, { id: 'b', x: 400, y: 500, w: 200, h: 100 }] }); }
|
||
},
|
||
|
||
/* ---------- 第四批:color 渐变配色 ---------- */
|
||
{
|
||
id: 'poster-gradient', name: '渐变配色', category: 'color', emoji: '🌅',
|
||
scene: '基于主色生成海报背景渐变(线性/径向)',
|
||
summary: '输入主色 hex,按 RGB 明度插值生成 2-6 段渐变序列,输出适合海报背景的配色与点缀互补色。',
|
||
params: { primary: '主色 hex', steps: '渐变段数(默认 4)' },
|
||
source: '自研(亮度插值法)',
|
||
run(args) {
|
||
const hex = String(args.primary || '#4f46e5');
|
||
const steps = Math.min(Math.max(Number(args.steps) || 4, 2), 6);
|
||
const m = hex.match(/^#?([0-9a-f]{6})$/i);
|
||
if (!m) return { error: '无效 hex: ' + hex };
|
||
const n = parseInt(m[1], 16);
|
||
const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
|
||
const hexs = (x, y, z) => '#' + [x, y, z].map(v => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0')).join('');
|
||
const seq = [];
|
||
for (let i = 0; i < steps; i++) {
|
||
const t = steps === 1 ? 1 : i / (steps - 1);
|
||
const f = 0.65 + 0.35 * t; // 从暗到亮的整体提亮
|
||
seq.push(hexs(r * f, g * f, b * f));
|
||
}
|
||
const pair = hexs(255 - r, 255 - g, 255 - b);
|
||
return { ok: true, gradient: seq.join(' → '), accent: pair, tip: '背景建议用 ' + seq[0] + ' → ' + seq[seq.length - 1] + ' 做线性渐变;点缀色可用互补色 ' + pair };
|
||
},
|
||
test() { return this.run({ primary: '#4f46e5', steps: 4 }); }
|
||
},
|
||
|
||
/* ---------- 第四批:typography 行长断行 ---------- */
|
||
{
|
||
id: 'poster-line-length', name: '行长断行', category: 'typography', emoji: '📐',
|
||
scene: '按可用宽度建议字号与行长,保证标题/正文可读性',
|
||
summary: '根据可用宽度与用途(标题/正文),建议最佳字号与每行字符数(西文 45-75 字符),并给出断行提示。',
|
||
params: { width: '可用宽度 px', isTitle: '是否标题(默认 false)' },
|
||
source: '自研(阅读排版规范)',
|
||
run(args) {
|
||
const w = Number(args.width) || 640;
|
||
const isTitle = !!args.isTitle;
|
||
const best = isTitle ? Math.min(w / 14, 40) : Math.min(w / 12, 24);
|
||
const lineChars = Math.max(8, Math.floor(w / best));
|
||
return { ok: true, fontSize: Math.round(best), charsPerLine: lineChars, tip: isTitle ? '标题单行 ≤ ' + lineChars + ' 字,超长建议断成 2 行并保留主谓完整、避免孤字' : '西文行长建议 45-75 字符(当前约 ' + lineChars + '),中文可放宽至 20-35 字/行' };
|
||
},
|
||
test() { return this.run({ width: 640, isTitle: false }); }
|
||
},
|
||
|
||
/* ---------- 第四批:grid 对角线构图 ---------- */
|
||
{
|
||
id: 'poster-diagonal', name: '对角线构图', category: 'grid', emoji: '📉',
|
||
scene: '判断元素是否沿对角线排布,输出斜向构图参数',
|
||
summary: '把元素中心点投影到画布主对角线上,检查偏离度并给出沿对角线排列的调整建议。',
|
||
params: { canvas: '{w,h}', elements: '元素数组(含 x/y/w/h)' },
|
||
source: '自研',
|
||
run(args) {
|
||
const cw = (args.canvas && args.canvas.w) || 1000;
|
||
const ch = (args.canvas && args.canvas.h) || 1500;
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
if (els.length < 2) return { ok: true, tip: '元素过少,可将 3-5 个元素沿主对角线(左上→右下)排布制造动势' };
|
||
const devs = els.map(e => {
|
||
const cx = e.x + e.w / 2, cy = e.y + e.h / 2;
|
||
const proj = (cx / cw + cy / ch) / 2; // 对角投影 0..1
|
||
return Math.abs(proj - 0.5) * 2; // 偏离主对角线程度 0..1
|
||
});
|
||
const avg = devs.reduce((a, b) => a + b, 0) / devs.length;
|
||
const ok = avg < 0.35;
|
||
return { ok, deviation: (avg * 100).toFixed(0) + '%', tip: ok ? '元素基本贴合对角线,动势良好' : '偏离度较高,建议把元素中心对齐到「x/w = y/h」的斜线上' };
|
||
},
|
||
test() { return this.run({ canvas: { w: 1000, h: 1500 }, elements: [{ x: 50, y: 80, w: 100, h: 100 }, { x: 300, y: 450, w: 100, h: 100 }, { x: 600, y: 900, w: 100, h: 100 }] }); }
|
||
},
|
||
|
||
/* ---------- 第四批:hierarchy 色彩层级 ---------- */
|
||
{
|
||
id: 'poster-color-hierarchy', name: '色彩层级', category: 'hierarchy', emoji: '🎚️',
|
||
scene: '用颜色与背景的对比度区分信息等级,强化主次',
|
||
summary: '输入背景色与元素(权重 + 颜色),按 WCAG 相对对比度检查是否与信息权重匹配,输出色彩层级建议。',
|
||
params: { bg: '背景色 hex', elements: '元素数组(含 weight、color hex)' },
|
||
source: '自研(WCAG 对比度法)',
|
||
run(args) {
|
||
const bg = String(args.bg || '#ffffff');
|
||
const els = (args.elements || []).filter(e => e.color && e.weight);
|
||
if (!els.length) return { ok: true, note: '提供含 color 与 weight 的元素后可评估' };
|
||
const rel = c => {
|
||
const m = String(c).match(/^#?([0-9a-f]{6})$/i);
|
||
if (!m) return null;
|
||
const n = parseInt(m[1], 16);
|
||
const lin = v => { const x = ((n >> (v * 8)) & 255) / 255; return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); };
|
||
return 0.2126 * lin(2) + 0.7152 * lin(1) + 0.0722 * lin(0);
|
||
};
|
||
const L1 = rel(bg);
|
||
if (L1 == null) return { error: '无效背景色: ' + bg };
|
||
const rows = els.map(e => { const L2 = rel(e.color); if (L2 == null) return null; const hi = Math.max(L1, L2), lo = Math.min(L1, L2); return { e, ratio: (hi + 0.05) / (lo + 0.05) }; }).filter(Boolean);
|
||
if (!rows.length) return { ok: true, note: '元素颜色无效,无法评估' };
|
||
const byWeight = rows.slice().sort((a, b) => b.e.weight - a.e.weight);
|
||
const weakest = byWeight[byWeight.length - 1];
|
||
const mismatch = rows.filter(r => r.e.weight > weakest.e.weight * 2 && r.ratio < weakest.ratio);
|
||
return { ok: mismatch.length === 0, tip: mismatch.length ? '有 ' + mismatch.length + ' 个高权重元素对比度反而低于低权重元素,建议加强其颜色/字号' : '信息权重与色彩对比度匹配,层级分明' };
|
||
},
|
||
test() { return this.run({ bg: '#ffffff', elements: [{ weight: 5, color: '#111111' }, { weight: 3, color: '#666666' }, { weight: 1, color: '#aaaaaa' }] }); }
|
||
},
|
||
|
||
/* ---------- 第四批:print 输出分辨率 ---------- */
|
||
{
|
||
id: 'poster-dpi', name: '输出分辨率', category: 'print', emoji: '🖨️',
|
||
scene: '把海报物理尺寸换算成像素分辨率(印刷/屏幕)',
|
||
summary: '输入宽高(cm)与用途,输出所需像素尺寸与 dpi 建议,避免印刷糊图或导出失真。',
|
||
params: { width: '宽度 cm', height: '高度 cm', use: 'print | screen(默认 print)' },
|
||
source: '自研(印刷工艺标准)',
|
||
run(args) {
|
||
const w = Number(args.width) || 42, h = Number(args.height) || 59.4;
|
||
const dpi = args.use === 'screen' ? 96 : 300;
|
||
const px = (cm, d) => Math.round(cm / 2.54 * d);
|
||
return { ok: true, dpi, widthPx: px(w, dpi), heightPx: px(h, dpi), tip: args.use === 'screen' ? '屏幕输出 96dpi 即可(' + px(w, 96) + '×' + px(h, 96) + 'px);矢量素材可任意缩放不糊图' : '印刷 300dpi:' + px(w, 300) + '×' + px(h, 300) + 'px;大幅面喷绘可用 150dpi,超大尺寸再降' };
|
||
},
|
||
test() { return this.run({ width: 42, height: 59.4 }); }
|
||
},
|
||
|
||
/* ---------- 第四批:creative 破格张力 ---------- */
|
||
{
|
||
id: 'poster-grid-break', name: '破格张力', category: 'creative', emoji: '💥',
|
||
scene: '故意打破网格制造视觉张力,让海报跳出规整感',
|
||
summary: '在规整网格中推荐 1-2 个元素做破格(旋转/溢出/压叠),并给出幅度与数量建议。',
|
||
params: { grid: '{cols,rows}', elements: '元素数组' },
|
||
source: '自研(反网格设计法)',
|
||
run(args) {
|
||
const cols = Number(args.grid && args.grid.cols) || 3;
|
||
const els = (args.elements || []).length;
|
||
if (els < 3) return { ok: true, tip: '元素少于 3 个时破格效果弱,建议先做 5 个以上元素再破格 1 个' };
|
||
return { ok: true, breakNum: 1, rotate: '推荐将主标题旋转 5-8° 或溢出画布边缘 10-20%', overflow: '让 1 个强调元素溢出网格边界,其余保持对齐', tip: '破格元素不超过总数的 1/4,否则版面失控;用「对齐的主体 + 一个反叛元素」制造张力' };
|
||
},
|
||
test() { return this.run({ grid: { cols: 3 }, elements: [1, 2, 3, 4, 5] }); }
|
||
},
|
||
|
||
/* ---------- 第四批:ai 迭代优化 ---------- */
|
||
{
|
||
id: 'poster-ai-iterate', name: 'AI 迭代优化', category: 'ai', emoji: '🔁',
|
||
scene: '根据初稿与反馈生成下一轮 AI 生成指令',
|
||
summary: '输入初稿提示词与修改反馈(; 分隔),输出保留/调整清单与结构化迭代提示词。',
|
||
params: { draft: '初稿提示词', feedback: '修改反馈(多条,; 分隔)' },
|
||
source: '自研(迭代优化工作流)',
|
||
run(args) {
|
||
const d = String(args.draft || '');
|
||
const fb = String(args.feedback || '');
|
||
const items = fb.split(/[;;\n]/).map(s => s.trim()).filter(Boolean);
|
||
if (!items.length) return { ok: true, tip: '无反馈,可先跑一版初稿再收集意见(建议收集 3-5 条)' };
|
||
const change = items.filter(s => !/保留|挺好|ok|一致|满意/i.test(s));
|
||
const keep = items.filter(s => !change.includes(s));
|
||
return {
|
||
ok: true,
|
||
keep: keep.length ? keep : '无明确保留项',
|
||
adjust: change.length ? change : '无调整项',
|
||
prompt: ('基于初稿「' + d.slice(0, 40) + (d.length > 40 ? '…' : '') + '」' + (change.length ? '调整:' + change.join(';') : '微调并保持原风格')).slice(0, 200),
|
||
tip: '把 keep 保留进下一轮,change 转成具体指令(如「主色改 #123456」「标题字号加大一级」),每轮反馈不超过 5 条避免风格漂移'
|
||
};
|
||
},
|
||
test() { return this.run({ draft: '极简风格科技海报,深蓝背景', feedback: '保留配色;标题不够突出,加大并加粗;去掉底部装饰' }); }
|
||
},
|
||
|
||
/* ---------- 第五批:layout 非对称平衡 ---------- */
|
||
{
|
||
id: 'poster-asymmetry', name: '非对称平衡', category: 'layout', emoji: '🧱',
|
||
scene: '用非对称布局制造动感又不失平衡(以留白/暗色块/元素移动补偿)',
|
||
summary: '输入元素坐标与面积,计算左右视觉重量差,输出补偿建议(留白/暗度/位移)。',
|
||
params: { canvas: '{w,h}', elements: '元素数组(含 x/y/w/h、weight 可选)' },
|
||
source: '自研(视觉重量补偿法)',
|
||
run(args) {
|
||
const cw = (args.canvas && args.canvas.w) || 1000;
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
if (!els.length) return { ok: true, note: '无元素,可尝试「大留白 + 偏移主视觉」的非对称构图' };
|
||
let L = 0, R = 0;
|
||
for (const e of els) {
|
||
const w = e.weight || e.w * e.h;
|
||
if (e.x + e.w / 2 < cw / 2) L += w; else R += w;
|
||
}
|
||
const total = L + R;
|
||
const d = total ? (R - L) / total : 0;
|
||
const ok = Math.abs(d) < 0.25;
|
||
return { ok, left: (L / total * 100).toFixed(0) + '%', right: (R / total * 100).toFixed(0) + '%', tip: ok ? '非对称布局成立(左右差距 ≤ 25%)' : (d > 0 ? '右侧偏重,建议左侧补元素或加大左侧元素面积/暗度' : '左侧偏重,建议右侧补元素或加大右侧元素面积/暗度') };
|
||
},
|
||
test() { return this.run({ canvas: { w: 1000, h: 1500 }, elements: [{ x: 100, y: 200, w: 400, h: 400 }, { x: 700, y: 900, w: 200, h: 200 }] }); }
|
||
},
|
||
|
||
/* ---------- 第五批:color 品牌色适配 ---------- */
|
||
{
|
||
id: 'poster-brand-color', name: '品牌色适配', category: 'color', emoji: '🏷️',
|
||
scene: '基于品牌主色生成海报全套配色(主色/浅调/深调/中性/点缀)',
|
||
summary: '输入品牌主色 hex,输出同色系浅调、深调、中性灰与点缀色建议,保证品牌识别与海报视觉统一。',
|
||
params: { brand: '品牌主色 hex' },
|
||
source: '自研(品牌色延展法)',
|
||
run(args) {
|
||
const hex = String(args.brand || '#0a84ff');
|
||
const m = hex.match(/^#?([0-9a-f]{6})$/i);
|
||
if (!m) return { error: '无效 hex: ' + hex };
|
||
const n = parseInt(m[1], 16);
|
||
const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
|
||
const c = (x, y, z) => '#' + [x, y, z].map(v => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0')).join('');
|
||
const tint = c(r + (255 - r) * 0.8, g + (255 - g) * 0.8, b + (255 - b) * 0.8);
|
||
const shade = c(r * 0.55, g * 0.55, b * 0.55);
|
||
return { ok: true, primary: hex.toUpperCase(), tint, shade, neutral: '#f5f5f7', accent: c(255 - r, 255 - g, 255 - b), tip: '品牌主色用于标题/CTA,浅调 ' + tint + ' 做背景分区,深调 ' + shade + ' 做正文,中性 #f5f5f7 铺底,点缀色控制在 10% 以内' };
|
||
},
|
||
test() { return this.run({ brand: '#0a84ff' }); }
|
||
},
|
||
|
||
/* ---------- 第五批:typography 竖排文字 ---------- */
|
||
{
|
||
id: 'poster-vertical-text', name: '竖排文字', category: 'typography', emoji: '🀄',
|
||
scene: '中文竖排(直排)排版参数建议,适合文艺/传统风格海报',
|
||
summary: '输入文本长度与单列宽,输出竖排字号、行距、字距与换列建议(自上而下、从右向左)。',
|
||
params: { textLength: '文本字符数', columnWidth: '单列宽 px' },
|
||
source: '自研(中文直排规范)',
|
||
run(args) {
|
||
const len = Math.max(1, Number(args.textLength) || 12);
|
||
const cw = Number(args.columnWidth) || 60;
|
||
const fontSize = Math.round(cw * 0.72);
|
||
const lineHeight = Math.round(fontSize * 1.55);
|
||
const perCol = Math.floor(1500 / lineHeight);
|
||
const cols = Math.ceil(len / perCol);
|
||
return { ok: true, fontSize, lineHeight, letterSpacing: '字距建议 0.05-0.12em(竖排偏紧)', columns: cols, tip: '竖排自右向左换列,列距建议 1-1.5 个汉字宽;标点放字右下方,首列顶部可留 1-2 字天头' };
|
||
},
|
||
test() { return this.run({ textLength: 24, columnWidth: 60 }); }
|
||
},
|
||
|
||
/* ---------- 第五批:grid 错位网格 ---------- */
|
||
{
|
||
id: 'poster-offset-grid', name: '错位网格', category: 'grid', emoji: '🧩',
|
||
scene: '用错位/交错网格替代标准网格,营造活泼版面',
|
||
summary: '输入标准网格参数,输出错位偏移量与交错节奏建议(错位仅用于装饰块,文字仍对齐主轴)。',
|
||
params: { cols: '列数', cellW: '单元宽 px' },
|
||
source: '自研(交错网格法)',
|
||
run(args) {
|
||
const cols = Math.max(2, Number(args.cols) || 3);
|
||
const cell = Number(args.cellW) || 120;
|
||
const offset = Math.round(cell * 0.5);
|
||
const stagger = Math.round(cell * 0.35);
|
||
return { ok: true, cols, offset, stagger, tip: '奇数行整体右移 ' + offset + 'px、每行递增 ' + stagger + 'px 形成错落;错位仅用于装饰块,文字仍对齐主轴' };
|
||
},
|
||
test() { return this.run({ cols: 3, cellW: 120 }); }
|
||
},
|
||
|
||
/* ---------- 第五批:hierarchy 强调手法 ---------- */
|
||
{
|
||
id: 'poster-emphasis', name: '强调手法', category: 'hierarchy', emoji: '⭐',
|
||
scene: '为关键信息分配强调手段(放大/加粗/变色/描边/反白)',
|
||
summary: '输入信息层级数,按层级分配不冲突的强调手法,避免「全场都在强调」。',
|
||
params: { levels: '信息层级数(默认 3)' },
|
||
source: '自研(强调手段分配法)',
|
||
run(args) {
|
||
const n = Math.min(Math.max(Number(args.levels) || 3, 1), 6);
|
||
const methods = ['放大 + 加粗 + 主色', '增大字号或加粗', '变色(强调色)', '描边/反白', '加下划线或引号', '灰化弱化'];
|
||
const assign = [];
|
||
for (let i = 0; i < n; i++) assign.push('L' + (i + 1) + ' → ' + (methods[i] || '弱化'));
|
||
return { ok: true, assign, tip: '同一元素只叠加 ≤2 种手法,全页强调元素 ≤ 3 个,其余靠层级自然弱化' };
|
||
},
|
||
test() { return this.run({ levels: 3 }); }
|
||
},
|
||
|
||
/* ---------- 第五批:print 纸张规格 ---------- */
|
||
{
|
||
id: 'poster-paper-weight', name: '纸张规格', category: 'print', emoji: '📄',
|
||
scene: '为海报选择合适纸张克重与表面工艺(印刷)',
|
||
summary: '输入用途(宣传单/艺术海报/户外/吊旗),输出推荐纸张克重、表面处理与印刷方式建议。',
|
||
params: { use: '宣传单 | 艺术海报 | 户外 | 吊旗(默认宣传单)' },
|
||
source: '自研(印刷工艺标准)',
|
||
run(args) {
|
||
const map = {
|
||
'宣传单': { weight: '157g 铜版纸', surface: '光面/哑膜', print: '四色胶印', tip: '双面 157g 铜版最均衡,哑膜防指纹' },
|
||
'艺术海报': { weight: '250g 艺术纸/硫酸纸', surface: '哑面纹理', print: '四色 + 局部 UV', tip: '250g 以上挺括不易卷边,局部 UV 提亮主视觉' },
|
||
'户外': { weight: 'PVC 喷绘布', surface: '哑面抗 UV', print: '喷绘 720dpi', tip: '户外面料耐候抗晒,需留出血与安装孔位' },
|
||
'吊旗': { weight: '220g 旗帜布', surface: '双面印', print: '数码直喷', tip: '双面内容方向相反,顶部预留旗杆穿口' }
|
||
};
|
||
const u = String(args.use || '宣传单');
|
||
const r = map[u] || map['宣传单'];
|
||
return Object.assign({ ok: true, use: u }, r);
|
||
},
|
||
test() { return this.run({ use: '艺术海报' }); }
|
||
},
|
||
|
||
/* ---------- 第五批:creative 像素风设计 ---------- */
|
||
{
|
||
id: 'poster-pixel-style', name: '像素风设计', category: 'creative', emoji: '👾',
|
||
scene: '复古游戏/像素风海报的参数建议(对齐网格、配色、字体、边缘)',
|
||
summary: '输出像素风设计的网格对齐、颜色数量、字体与边缘参数建议(与 Pixelarticons 像素图标搭配使用)。',
|
||
params: { size: '像素尺寸(默认 24)' },
|
||
source: '自研(像素艺术规范)',
|
||
run(args) {
|
||
const s = Math.max(4, Number(args.size) || 24);
|
||
return { ok: true, grid: '元素对齐到 ' + s + 'px 整数倍网格', colors: '主色 ≤ 4 色(1 深色描边 + 2 主色 + 1 点缀)', font: '推荐像素字体或等宽字(如 JetBrains Mono)', edge: '描边 1px 硬边、禁用抗锯齿/柔边投影', tip: '背景可用深色 + 像素图标点缀,阴影用 2-4px 硬偏移模拟立体感' };
|
||
},
|
||
test() { return this.run({ size: 24 }); }
|
||
},
|
||
|
||
/* ---------- 第五批:ai 自检清单 ---------- */
|
||
{
|
||
id: 'poster-ai-review', name: 'AI 自检清单', category: 'ai', emoji: '🧾',
|
||
scene: 'AI 生成海报后的结构自检(构图/配色/字体/层级/安全区)',
|
||
summary: '输入设计需求与元素清单,返回逐项自检清单,把不通过项转成迭代指令交给 AI 修改。',
|
||
params: { brief: '设计需求一句话', elements: '元素清单(逗号分隔)' },
|
||
source: '自研(AI 质量检查法)',
|
||
run(args) {
|
||
const b = String(args.brief || '');
|
||
const els = String(args.elements || '').split(/[,,]/).map(s => s.trim()).filter(Boolean);
|
||
const checks = [
|
||
'构图:是否有明确主视觉焦点(建议 1 个)',
|
||
'配色:是否 ≤ 4 个主色 + 文字对比度达 WCAG AA',
|
||
'字体:标题/正文是否成体系(建议 1-2 款字体)',
|
||
'层级:信息主次是否一目了然(标题 > 时间/地点 > 详情)',
|
||
'安全区:关键内容是否在画布 10% 边距内'
|
||
];
|
||
return { ok: true, brief: b || '(未填写)', elements: els.length ? els : '(未提供)', checks, tip: '逐项自检后,把「不通过」项转成迭代指令交给 AI 修改' };
|
||
},
|
||
test() { return this.run({ brief: '高校迎新晚会海报', elements: '主标题,时间,地点,报名二维码' }); }
|
||
},
|
||
|
||
/* ---------- 第六批:layout 留白呼吸 ---------- */
|
||
{
|
||
id: 'poster-white-space', name: '留白呼吸', category: 'layout', emoji: '🌬️',
|
||
scene: '评估海报留白比例,避免版面过挤或过于空旷',
|
||
summary: '按元素占用面积与画布面积计算留白率,给出舒适区间建议与调整方向。',
|
||
params: { canvas: '{w,h}', elements: '元素数组(含 x/y/w/h)' },
|
||
source: '自研(留白比例法)',
|
||
run(args) {
|
||
const cw = (args.canvas && args.canvas.w) || 1000;
|
||
const ch = (args.canvas && args.canvas.h) || 1500;
|
||
const els = (args.elements || []).filter(e => e.w > 0 && e.h > 0);
|
||
const used = els.reduce((s, e) => s + e.w * e.h, 0);
|
||
const area = cw * ch;
|
||
const ratio = area > 0 ? used / area : 1;
|
||
const white = (1 - ratio) * 100;
|
||
let tip;
|
||
if (white < 15) tip = '留白过少,版面拥挤:建议删除次要装饰或增大元素间距';
|
||
else if (white > 70) tip = '留白过多,版面空旷:建议补充主视觉元素或放大标题';
|
||
else tip = '留白比例舒适,保持现有呼吸感';
|
||
return { ok: true, whiteRate: white.toFixed(1) + '%', level: white < 15 ? '拥挤' : (white > 70 ? '空旷' : '舒适'), tip };
|
||
},
|
||
test() { return this.run({ canvas: { w: 1000, h: 1500 }, elements: [{ x: 100, y: 100, w: 500, h: 300 }, { x: 100, y: 500, w: 500, h: 200 }] }); }
|
||
},
|
||
|
||
/* ---------- 第六批:color 色彩情绪 ---------- */
|
||
{
|
||
id: 'poster-color-psychology', name: '色彩情绪', category: 'color', emoji: '💭',
|
||
scene: '根据海报主题匹配色彩情绪,让配色传递正确氛围',
|
||
summary: '将主色按色相归类为红/橙/黄/绿/蓝/紫/中性,对照主题情绪词表给出匹配度与替换建议。',
|
||
params: { primary: '主色 hex', theme: '海报主题词(如 sale/tech/food)' },
|
||
source: '自研(色彩心理学)',
|
||
run(args) {
|
||
const c = String(args.primary || '#e53935');
|
||
const theme = String(args.theme || '').toLowerCase();
|
||
const hex = c.replace('#', '');
|
||
const r = parseInt(hex.slice(0, 2) || '0', 16) || 0;
|
||
const g = parseInt(hex.slice(2, 4) || '0', 16) || 0;
|
||
const b = parseInt(hex.slice(4, 6) || '0', 16) || 0;
|
||
let hue;
|
||
const mx = Math.max(r, g, b), mn = Math.min(r, g, b);
|
||
if (mx === mn) hue = 'neutral';
|
||
else {
|
||
const d = mx - mn;
|
||
let h;
|
||
if (mx === r) h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
|
||
else if (mx === g) h = ((b - r) / d + 2) * 60;
|
||
else h = ((r - g) / d + 4) * 60;
|
||
hue = h < 15 || h >= 345 ? 'red' : h < 45 ? 'orange' : h < 70 ? 'yellow' : h < 160 ? 'green' : h < 200 ? 'cyan' : h < 270 ? 'blue' : h < 330 ? 'purple' : 'red';
|
||
}
|
||
const mood = {
|
||
red: ['热情', '紧迫', '促销', '危险'], orange: ['活力', '温暖', '食欲', '促销'],
|
||
yellow: ['醒目', '欢乐', '年轻', '警告'], green: ['自然', '健康', '成长', '环保'],
|
||
cyan: ['清新', '科技', '冷静', '治愈'], blue: ['专业', '信任', '理性', '商务'],
|
||
purple: ['高端', '神秘', '创意', '奢华'], neutral: ['极简', '高级', '稳重', '文艺']
|
||
};
|
||
const want = { sale: 'red/orange', tech: 'blue/cyan', food: 'orange/red', health: 'green/cyan', luxury: 'purple/black', kids: 'yellow/orange', edu: 'blue/green', sport: 'red/orange' }[theme];
|
||
return { ok: true, hue, mood: mood[hue], theme: theme || '(未指定)', suggest: want ? `主题 ${theme} 建议 ${want}` : '请指定主题词(sale/tech/food/health/luxury/kids/edu/sport)获得匹配建议', tip: '情绪联想是配色第一步:先定情绪,再定色相,最后用明度/饱和度微调' };
|
||
},
|
||
test() { return this.run({ primary: '#e53935', theme: 'sale' }); }
|
||
},
|
||
|
||
/* ---------- 第六批:typography 首字下沉 ---------- */
|
||
{
|
||
id: 'poster-initial-letter', name: '首字下沉', category: 'typography', emoji: '🔡',
|
||
scene: '西文海报/杂志风正文使用装饰首字母(Drop Cap)的参数建议',
|
||
summary: '根据正文行高推导首字下沉的行数与字号,让首字母融入正文块且视觉协调。',
|
||
params: { fontSize: '正文字号 px(默认 16)', lineHeight: '行高倍数(默认 1.6)' },
|
||
source: '自研(Drop Cap 排印规范)',
|
||
run(args) {
|
||
const fs = Number(args.fontSize) || 16;
|
||
const lh = Number(args.lineHeight) || 1.6;
|
||
const lines = 3; // 经典三行下沉
|
||
const capSize = Math.round(fs * lh * lines);
|
||
return { ok: true, fontSize: fs, lineHeight: lh, lines, capSize: capSize + 'px', tip: `首字下沉 ${lines} 行,字号约为 ${capSize}px(= 正文 × 行高 × ${lines});衬线字体效果最佳,下沉字母左对齐正文首行` };
|
||
},
|
||
test() { return this.run({ fontSize: 16, lineHeight: 1.6 }); }
|
||
},
|
||
|
||
/* ---------- 第六批:grid 基线网格 ---------- */
|
||
{
|
||
id: 'poster-baseline-grid', name: '基线网格', category: 'grid', emoji: '📏',
|
||
scene: '让海报中所有文字行落在统一基线网格上,版面整齐',
|
||
summary: '以正文行距为网格单位推导基线网格,输出标题/正文字号与行距的整数倍对齐规则。',
|
||
params: { base: '基准正文字号 px(默认 16)' },
|
||
source: '自研(Baseline Grid 规范)',
|
||
run(args) {
|
||
const base = Number(args.base) || 16;
|
||
const grid = Math.round(base * 1.5); // 1.5 倍行距作为网格单位
|
||
return { ok: true, grid: grid + 'px', rule: `所有文本行高取 ${grid}px 整数倍;标题字号 ${base * 2}px 行高 ${grid * 2}px`, tip: '正文 16px/行距 24px,标题 32px/行距 48px,多行文本行距 = 网格 × 行数,元素顶底对齐网格线' };
|
||
},
|
||
test() { return this.run({ base: 16 }); }
|
||
},
|
||
|
||
/* ---------- 第六批:hierarchy 信息密度 ---------- */
|
||
{
|
||
id: 'poster-info-density', name: '信息密度', category: 'hierarchy', emoji: '📊',
|
||
scene: '评估海报承载的信息量是否过载,决定取舍与分层',
|
||
summary: '按画布面积、文本字符数与元素数计算信息密度指数,给出过载/合适/空洞判断与精简建议。',
|
||
params: { canvas: '{w,h}', chars: '文本总字符数', elements: '视觉元素数' },
|
||
source: '自研(信息密度评估)',
|
||
run(args) {
|
||
const cw = (args.canvas && args.canvas.w) || 1000;
|
||
const ch = (args.canvas && args.canvas.h) || 1500;
|
||
const chars = Number(args.chars) || 0;
|
||
const els = Number(args.elements) || 0;
|
||
const area = cw * ch / 1e6; // 百万像素
|
||
const density = chars / Math.max(area, 1) + els * 3;
|
||
let tip, level;
|
||
if (density > 8) { level = '过载'; tip = '信息过载:建议精简为「1 标题 + 1 主信息 + 1 行动指令」,正文压缩或转二维码'; }
|
||
else if (density < 2) { level = '空洞'; tip = '信息不足:补充时间/地点/报名方式等必要信息,或加大标题与主视觉'; }
|
||
else { level = '合适'; tip = '信息密度合适,保持「标题一眼看懂、细节按需阅读」的层级'; }
|
||
return { ok: true, density: density.toFixed(1), level, tip };
|
||
},
|
||
test() { return this.run({ canvas: { w: 1000, h: 1500 }, chars: 120, elements: 6 }); }
|
||
},
|
||
|
||
/* ---------- 第六批:print 折页线规避 ---------- */
|
||
{
|
||
id: 'poster-fold', name: '折页线规避', category: 'print', emoji: '📃',
|
||
scene: '三折页/对折宣传单设计时避开折痕位置放置关键内容',
|
||
summary: '按页面宽高与折法计算折痕位置与安全距离,提醒关键文字/图像避开折线。',
|
||
params: { width: '展开宽度 mm(默认 210)', height: '高度 mm(默认 297)', folds: '折数(2=对折 / 3=三折,默认 3)' },
|
||
source: '自研(折页设计规范)',
|
||
run(args) {
|
||
const w = Number(args.width) || 210;
|
||
const h = Number(args.height) || 297;
|
||
const folds = Math.min(Math.max(Number(args.folds) || 3, 2), 4);
|
||
const panel = w / folds;
|
||
const creases = [];
|
||
for (let i = 1; i < folds; i++) creases.push(Math.round(panel * i));
|
||
return { ok: true, panel: panel.toFixed(0) + 'mm', creases: creases.map(v => v + 'mm').join(' / '), tip: `折痕位于 ${creases.join('、')}mm 处,距折痕两侧 ≥ 10mm 禁放关键文字/二维码/连续图案;封面面板宽度 ${panel.toFixed(0)}mm 内优先放主视觉` };
|
||
},
|
||
test() { return this.run({ width: 210, height: 297, folds: 3 }); }
|
||
},
|
||
|
||
/* ---------- 第六批:creative 文字海报 ---------- */
|
||
{
|
||
id: 'poster-typographic-poster', name: '文字海报', category: 'creative', emoji: '🔠',
|
||
scene: '纯文字排版海报(Typographic Poster)的构图手法建议',
|
||
summary: '根据标题长度与信息条目,推荐大标题 + 网格 + 对比的文字海报构图方案。',
|
||
params: { title: '主标题文本', info: '辅助信息条目数(默认 4)' },
|
||
source: '自研(文字海报构图法)',
|
||
run(args) {
|
||
const title = String(args.title || 'YOUR TITLE HERE');
|
||
const info = Math.max(1, Number(args.info) || 4);
|
||
const long = title.length > 12;
|
||
return {
|
||
ok: true,
|
||
layout: long ? '标题拆两行大字撑满版面,辅以细线分割信息区' : '标题居中或顶格大字,字号占画布宽 60-80%',
|
||
pair: '压缩大写标题(如 Bebas Neue/Oswald)配正文无衬线(如 Inter/Montserrat),对比强烈',
|
||
hierarchy: `辅助信息 ${info} 条按网格排布,用字号/字重/字色区分主次,可加序号或引导线`,
|
||
tip: '文字本身就是图形:尝试字重对比、大小对比、字距拉大、字母变形与文字溢出网格'
|
||
};
|
||
},
|
||
test() { return this.run({ title: 'MIDNIGHT JAZZ', info: 4 }); }
|
||
},
|
||
|
||
/* ---------- 第六批:ai 多方案生成 ---------- */
|
||
{
|
||
id: 'poster-ai-variant', name: '多方案生成', category: 'ai', emoji: '🃏',
|
||
scene: 'AI 生成海报前先产出多个差异化方向,再择优深化',
|
||
summary: '根据主题与风格数量生成 N 个差异化方案(构图/配色/字体方向),供挑选后深入迭代。',
|
||
params: { theme: '海报主题', styles: '方案风格数组或逗号分隔风格词', count: '方案数量(默认 3)' },
|
||
source: '自研(AI 多方案法)',
|
||
run(args) {
|
||
const theme = String(args.theme || '(未指定主题)');
|
||
const n = Math.min(Math.max(Number(args.count) || 3, 2), 5);
|
||
const dirs = ['大字居中 + 对比色', '非对称构图 + 大留白', '网格拼贴 + 多色块', '上下分区 + 渐变背景', '居中对称 + 装饰元素'];
|
||
const palettes = ['互补色(红/绿)', '类比色(蓝/青)', '同色系深浅', '黑金高反差', '黑白灰极简'];
|
||
const fonts = ['压缩大写标题 + 无衬线正文', '古典衬线标题 + 衬线正文', '几何无衬线全系列', '手写标题 + 无衬线正文', '等宽字体科技风'];
|
||
const plans = [];
|
||
for (let i = 0; i < n; i++) {
|
||
plans.push(`方案 ${i + 1}:${dirs[i % dirs.length]};配色 ${palettes[(i * 2) % palettes.length]};字体 ${fonts[(i * 3) % fonts.length]}`);
|
||
}
|
||
return { ok: true, theme, plans, tip: '先让 AI 各出一版缩略图/线稿对比,选 1-2 个方向深入迭代,避免在单方案上反复改' };
|
||
},
|
||
test() { return this.run({ theme: '咖啡新品上市', count: 3 }); }
|
||
},
|
||
|
||
/* ============================================================
|
||
* 第七批补充技能(2026-08-22 录入,每类 +1,run 纯函数 + test 自测)
|
||
* ============================================================ */
|
||
|
||
/* ---------- 第七批:layout 页边距系统 ---------- */
|
||
{
|
||
id: 'poster-margin', name: '页边距系统', category: 'layout', emoji: '📐',
|
||
scene: '海报版面统一页边距,保证不同尺寸下信息区与视觉呼吸',
|
||
summary: '按画布尺寸推导紧凑/标准/宽松三档页边距,并给出四边对齐与装订侧加量建议。',
|
||
params: { width: '画布宽 px(默认 1000)', height: '画布高 px(默认 1500)' },
|
||
source: '自研(版面边距系统)',
|
||
run(args) {
|
||
const w = Number(args.width) || 1000;
|
||
const h = Number(args.height) || 1500;
|
||
const short = Math.min(w, h);
|
||
const compact = Math.round(short * 0.04);
|
||
const standard = Math.round(short * 0.06);
|
||
const generous = Math.round(short * 0.09);
|
||
return { ok: true, compact: compact + 'px', standard: standard + 'px', generous: generous + 'px', tip: `海报推荐「标准」边距 ${standard}px(≈短边 6%);紧凑档 ${compact}px 适合信息密集海报,宽松档 ${generous}px 适合留白美学;四周边距保持一致,装订/打孔侧另加 15-25mm` };
|
||
},
|
||
test() { return this.run({ width: 1000, height: 1500 }); }
|
||
},
|
||
|
||
/* ---------- 第七批:color 用色配比 ---------- */
|
||
{
|
||
id: 'poster-color-ratio', name: '用色配比', category: 'color', emoji: '⚖️',
|
||
scene: '限制海报用色数量并按比例分配,避免画面杂乱',
|
||
summary: '按 60/30/10 法则将颜色划分为主色/辅色/点缀色三档,输出面积占比建议。',
|
||
params: { primary: '主色 hex(默认 #1a1a2e)', secondary: '辅色 hex(默认 #e94560)', accent: '点缀色 hex(默认 #f5f5f5)' },
|
||
source: '自研(60-30-10 配色法则)',
|
||
run(args) {
|
||
const p = String(args.primary || '#1a1a2e');
|
||
const s = String(args.secondary || '#e94560');
|
||
const a = String(args.accent || '#f5f5f5');
|
||
return { ok: true, primary: { color: p, ratio: '60%' }, secondary: { color: s, ratio: '30%' }, accent: { color: a, ratio: '10%' }, tip: `主色 ${p} 占 60%(背景/大面积),辅色 ${s} 占 30%(信息块/图形),点缀色 ${a} 占 10%(按钮/强调);点缀色优先用高饱和或补色` };
|
||
},
|
||
test() { return this.run({ primary: '#1a1a2e', secondary: '#e94560', accent: '#f5f5f5' }); }
|
||
},
|
||
|
||
/* ---------- 第七批:typography 字距节奏 ---------- */
|
||
{
|
||
id: 'poster-tracking', name: '字距节奏', category: 'typography', emoji: '↔️',
|
||
scene: '标题字距(Tracking)调整建议:大字距优雅、紧字距有力',
|
||
summary: '根据字号与文本长度推荐字距方向与增量,提升标题气质。',
|
||
params: { fontSize: '标题字号 px(默认 48)', chars: '标题字符数(默认 8)' },
|
||
source: '自研(标题字距规范)',
|
||
run(args) {
|
||
const fs = Number(args.fontSize) || 48;
|
||
const n = Number(args.chars) || 8;
|
||
const large = fs >= 60;
|
||
const short = n <= 10;
|
||
const track = large && short ? '宽' : (large ? '中' : '紧');
|
||
const val = track === '宽' ? Math.round(fs * 0.06) : (track === '中' ? Math.round(fs * 0.02) : 0);
|
||
return { ok: true, fontSize: fs + 'px', chars: n, track, tracking: (val ? '+' : '') + val + 'px', tip: `字号 ${fs}px + ${n} 字符 → 建议${track}字距(${val}px):${track === '宽' ? '大字距显优雅高级(时尚/奢侈风)' : track === '中' ? '适中字距平衡易读(多数标题)' : '紧字距显力量紧凑(压缩大写/运动风)'}` };
|
||
},
|
||
test() { return this.run({ fontSize: 48, chars: 8 }); }
|
||
},
|
||
|
||
/* ---------- 第七批:grid 同心圆构图 ---------- */
|
||
{
|
||
id: 'poster-concentric', name: '同心圆网格', category: 'grid', emoji: '⭕',
|
||
scene: '以同心圆/环形网格组织版面元素(徽章、唱片、雷达等主题)',
|
||
summary: '以画布中心为圆心生成同心圆网格,输出元素沿环分布的位置与数量建议。',
|
||
params: { size: '画布边长 px(默认 1000)', rings: '环数(默认 3)', items: '需分布的元素数(默认 8)' },
|
||
source: '自研(同心圆构图法)',
|
||
run(args) {
|
||
const size = Number(args.size) || 1000;
|
||
const rings = Math.min(Math.max(Number(args.rings) || 3, 1), 6);
|
||
const items = Math.min(Math.max(Number(args.items) || 8, 1), 24);
|
||
const radius = Math.floor(size / 2);
|
||
const ringStep = Math.floor(radius / rings);
|
||
const angle = 360 / items;
|
||
return { ok: true, radius: radius + 'px', rings: Array.from({ length: rings }, (_, i) => (i + 1) * ringStep + 'px').join(' / '), spacing: `每环元素间隔 ${angle.toFixed(0)}°`, tip: `圆心放主视觉/主体,${items} 个元素按每 ${angle.toFixed(0)}° 均匀分布;元素尺寸建议内小外大(沿半径递增)或统一` };
|
||
},
|
||
test() { return this.run({ size: 1000, rings: 3, items: 8 }); }
|
||
},
|
||
|
||
/* ---------- 第七批:hierarchy 层级自检 ---------- */
|
||
{
|
||
id: 'poster-hierarchy-check', name: '层级自检', category: 'hierarchy', emoji: '🪜',
|
||
scene: '检查海报文本层级字号是否清晰递减(标题→副题→正文→注释)',
|
||
summary: '输入各层字号,按相邻层级 ≥1.25× 的清晰度标准给出健康度与调整建议。',
|
||
params: { sizes: '层级字号数组,如 [64, 40, 24, 14]' },
|
||
source: '自研(视觉层级检查)',
|
||
run(args) {
|
||
const sizes = (args.sizes || []).map(Number).filter(Boolean);
|
||
if (sizes.length < 2) return { ok: true, tip: '至少输入 2 个层级字号才能自检(如 [64, 24])' };
|
||
const bad = [];
|
||
for (let i = 0; i < sizes.length - 1; i++) {
|
||
if (sizes[i] < sizes[i + 1] * 1.25) bad.push(`第 ${i + 1} 级(${sizes[i]}px) 与第 ${i + 2} 级(${sizes[i + 1]}px) 差距不足 1.25×`);
|
||
}
|
||
return { ok: true, levels: sizes.length, healthy: bad.length === 0, issues: bad.length ? bad : '无', tip: bad.length ? `建议增大相邻层级差至 ≥1.25×(标题 ${Math.round(sizes[1] * 1.5)}px+ 更醒目)` : '层级清晰:每级字号差 ≥1.25×,扫视即可分辨主次' };
|
||
},
|
||
test() { return this.run({ sizes: [64, 40, 24, 14] }); }
|
||
},
|
||
|
||
/* ---------- 第七批:print 印后工艺 ---------- */
|
||
{
|
||
id: 'poster-finishing', name: '印后工艺', category: 'print', emoji: '✨',
|
||
scene: '烫金 / 局部 UV / 模切等印后工艺对设计文件的要求与提醒',
|
||
summary: '选择印后工艺,输出对应的文件规范(出血、专色、镂空、对位精度)与设计注意项。',
|
||
params: { craft: '工艺类型:gold(烫金) / uv(局部UV) / diecut(模切) / emboss(压纹)(默认 gold)' },
|
||
source: '自研(印刷后期工艺规范)',
|
||
run(args) {
|
||
const craft = String(args.craft || 'gold');
|
||
const table = {
|
||
gold: { name: '烫金/烫银', file: '矢量稿需留专色通道,图案与底色至少 0.5mm 分离', note: '细线烫金 ≤0.1mm 会糊,建议线条 ≥0.3mm;大面积烫金易氧化需覆膜' },
|
||
uv: { name: '局部 UV', file: 'UV 区域单独建图层并加 0.3-0.5mm 扩边', note: 'UV 与底色色差小需打样确认;哑膜 + 局部亮 UV 效果最佳' },
|
||
diecut: { name: '模切/异形', file: '预留 ≥3mm 刀模线让位,关键元素距刀口 ≥5mm', note: '异形模切越大越稳,细窄卡榫易断;出血需覆盖刀模线外 3mm' },
|
||
emboss: { name: '压纹/击凸', file: '压纹图案与印刷图案对位公差 ±0.3mm', note: '击凸区域慎用深底色(压凹处颜色会偏移)' }
|
||
};
|
||
const t = table[craft] || table.gold;
|
||
return { ok: true, craft: t.name, file: t.file, note: t.note, tip: `印后工艺需与印刷厂提前确认:${t.name} 一般额外加收制版费,工期多 2-3 天;先打样 2-3 版再定稿` };
|
||
},
|
||
test() { return this.run({ craft: 'uv' }); }
|
||
},
|
||
|
||
/* ---------- 第七批:creative 双色调 ---------- */
|
||
{
|
||
id: 'poster-duotone', name: '双色调', category: 'creative', emoji: '🖤',
|
||
scene: '双色调(Duotone)处理:灰度图映射双色,复古/潮流海报常用',
|
||
summary: '输入主色/阴影色与灰度源建议,输出双色调参数(通道映射与混合模式)。',
|
||
params: { primary: '主色 hex(默认 #ff4d00)', shadow: '阴影色 hex(默认 #1a0033)', mood: '风格:retro/tech/film(默认 retro)' },
|
||
source: '自研(Duotone 双色调处理)',
|
||
run(args) {
|
||
const p = String(args.primary || '#ff4d00');
|
||
const s = String(args.shadow || '#1a0033');
|
||
const m = String(args.mood || 'retro');
|
||
const mix = m === 'tech' ? '硬边 + 网格纹理叠加' : (m === 'film' ? '低对比 + 胶片颗粒' : '高对比 + 半调网点');
|
||
return { ok: true, primary: p, shadow: s, mood: m, mode: '高光→主色,阴影→阴影色;混合模式 multiply/overlay 常用', mix, tip: `双色调三要素:① 灰度源图(对比强效果越好);② 主色 ${p} 映射高光、阴影色 ${s} 映射暗部;③ 可叠加半调网点/噪点增强印刷质感` };
|
||
},
|
||
test() { return this.run({ primary: '#ff4d00', shadow: '#1a0033', mood: 'retro' }); }
|
||
},
|
||
|
||
/* ---------- 第七批:ai 素材组合建议 ---------- */
|
||
{
|
||
id: 'poster-ai-combine', name: '素材组合建议', category: 'ai', emoji: '🧩',
|
||
scene: '根据海报主题推荐内置素材库的组合搭配(背景/装饰/字体/色板)',
|
||
summary: '输入主题与行业,推荐背景、装饰元素、字体与色板的内置素材组合方案。',
|
||
params: { theme: '海报主题', industry: '行业(可选:科技/餐饮/时尚)' },
|
||
source: '自研(内置素材组合引擎)',
|
||
run(args) {
|
||
const t = String(args.theme || '新品发布');
|
||
const ind = String(args.industry || '');
|
||
const map = {
|
||
'科技': { bg: '渐变/网格背景', deco: '几何线条 + 像素图标', font: '几何无衬线(Montserrat/Poppins)', palette: '深蓝 + 青 + 白(科技感)' },
|
||
'餐饮': { bg: '暖色渐变/纹理', deco: '圆润图形 + 食物 emoji', font: '圆润无衬线 + 手写标题', palette: '橙红 + 米黄 + 深棕(食欲感)' },
|
||
'时尚': { bg: '大色块/留白', deco: '线条装饰 + 双色调图', font: '高对比衬线(Playfair Display)+ 压缩大写', palette: '黑白 + 高饱和点缀' }
|
||
};
|
||
const hit = map[ind] || { bg: '纯色/渐变背景', deco: '主题相关装饰图标', font: '标题衬线 + 正文无衬线', palette: '主色 60% + 辅色 30% + 点缀 10%' };
|
||
return { ok: true, theme: t, bg: hit.bg, decoration: hit.deco, font: hit.font, palette: hit.palette, tip: '素材中心已按「类型 × 场景」双维度筛选,可按此组合直接检索内置库素材;AI 生成失败时此组合可作为降级方案' };
|
||
},
|
||
test() { return this.run({ theme: '咖啡新品上市', industry: '餐饮' }); }
|
||
},
|
||
|
||
/* ============================================================
|
||
* 第八批补充技能(2026-08-23 录入,每类 +1,run 纯函数 + test 自测)
|
||
* ============================================================ */
|
||
|
||
/* ---------- 第八批:layout 横竖版式 ---------- */
|
||
{
|
||
id: 'poster-orientation', name: '横竖版式', category: 'layout', emoji: '🖼️',
|
||
scene: '根据投放场景推荐海报横竖版式与画布比例',
|
||
summary: '按投放位置(门头/户外大牌/手机端/易拉宝/A4 纸)推荐横版/竖版/方图及宽高比,并给出该比例的构图要点。',
|
||
params: { placement: '投放场景:billboard/phone/banner/rollup/a4(默认 a4)', ratio: '自定义宽高比(可选)' },
|
||
source: '自研(海报版式比例规范)',
|
||
run(args) {
|
||
const p = String(args.placement || 'a4');
|
||
const ratio = args.ratio ? Number(args.ratio) : null;
|
||
const map = {
|
||
billboard: { orientation: '横版', ratio: '3:1(如 900×300)', note: '户外大牌视线停留短,信息越少越好,主视觉 + 一句标语即可' },
|
||
phone: { orientation: '竖版', ratio: '9:16(如 1080×1920)', note: '手机端信息流,顶部 1/3 放主视觉,关键信息居中偏下防被角标遮挡' },
|
||
banner: { orientation: '横版', ratio: '21:9(如 1680×720)', note: '官网横幅/开屏,左右留呼吸、主体居中偏左,CTA 靠右' },
|
||
rollup: { orientation: '竖版', ratio: '1:2.2(如 800×1760)', note: '易拉宝顶部 60cm 为视线区,标题与主视觉放上半部,底部留白放详情' },
|
||
a4: { orientation: '竖版', ratio: 'A4(210×297mm)', note: '通用打印版,四周安全边距 ≥ 15mm,标题层级用 1.25 倍字体比例' }
|
||
};
|
||
const hit = map[p] || map.a4;
|
||
return { ok: true, placement: p, orientation: hit.orientation, ratio: ratio ? ratio + ':1' : hit.ratio, note: hit.note, tip: '先定投放场景再定比例:竖版适合手机/易拉宝,横版适合门头/横幅;确定比例后再建网格(见「分栏网格」技能)' };
|
||
},
|
||
test() { return { a4: this.run({ placement: 'a4' }).orientation, phone: this.run({ placement: 'phone' }).ratio }; }
|
||
},
|
||
|
||
/* ---------- 第八批:color 冷暖色温 ---------- */
|
||
{
|
||
id: 'poster-temperature', name: '冷暖色温', category: 'color', emoji: '🌡️',
|
||
scene: '按主题与受众判断海报应使用暖色系 / 冷色系 / 中性色',
|
||
summary: '解析主题描述中的情绪关键词,输出冷暖判定、推荐色系与典型 hex,并提醒同类色系内微调色温保持层次。',
|
||
params: { brief: '主题/受众描述', explicit: '直接指定:warm/cool/neutral(可选,优先)' },
|
||
source: '自研(色彩温度感知规则)',
|
||
run(args) {
|
||
const b = String(args.brief || '');
|
||
const ex = String(args.explicit || '');
|
||
const warm = /餐饮|美食|促销|狂欢|节日|母婴|火锅|圣诞|新春|温暖|热情/.test(b);
|
||
const cool = /科技|医疗|金融|办公|专业|理性|冷静|夏季|海洋/.test(b);
|
||
const mood = ex === 'warm' ? 'warm' : (ex === 'cool' ? 'cool' : (ex === 'neutral' ? 'neutral' : (warm && !cool ? 'warm' : (cool && !warm ? 'cool' : 'neutral'))));
|
||
const table = {
|
||
warm: { name: '暖色系', palette: ['#E8590C 橙', '#D9480F 深橙', '#F5B041 金黄', '#B91C1C 红'], tip: '暖色增强食欲与亲近感;大面积用中高明度暖色,点缀用深暖色压场' },
|
||
cool: { name: '冷色系', palette: ['#1B4F72 深蓝', '#2E86C1 天蓝', '#6C3483 紫', '#148F77 青'], tip: '冷色传递专业与科技感;避免纯冷色堆砌,加 10% 中性灰平衡' },
|
||
neutral: { name: '中性色系', palette: ['#FFFFFF 白', '#E0E0E0 浅灰', '#616161 深灰', '#212121 黑'], tip: '中性色干净高级,适合极简/时尚/高端;用 1 个低饱和主色作强调' }
|
||
};
|
||
const t = table[mood];
|
||
return { ok: true, mood: t.name, palette: t.palette, clue: b ? '判定依据:' + b : '未提供描述', tip: t.tip };
|
||
},
|
||
test() { return { warm: this.run({ brief: '秋季餐饮促销' }).mood, cool: this.run({ brief: '企业金融年会' }).mood }; }
|
||
},
|
||
|
||
/* ---------- 第八批:typography 避头尾标点 ---------- */
|
||
{
|
||
id: 'poster-punctuation', name: '避头尾标点', category: 'typography', emoji: '🀄',
|
||
scene: '中文排版禁则处理:行首/行尾不可出现的标点检查',
|
||
summary: '检查多行文字的行首/行尾是否出现禁则标点(行首禁:,。;:、!?)》等;行尾禁:『「(《等),输出违规位置与换行建议。',
|
||
params: { lines: '文字行数组(支持 \\n 自动拆分)' },
|
||
source: '自研(中文排版禁则规范)',
|
||
run(args) {
|
||
const src = Array.isArray(args.lines) ? args.lines : [String(args.lines || '')];
|
||
const lines = src.join('\n').split('\n').map(s => s.trim()).filter(Boolean);
|
||
if (!lines.length) return { ok: true, note: '无文字内容' };
|
||
const banStart = ',。;:、!?)》】』」…—';
|
||
const banEnd = '(《【『「·—';
|
||
const issues = [];
|
||
lines.forEach((ln, i) => {
|
||
const first = ln[0], last = ln[ln.length - 1];
|
||
if (banStart.includes(first)) issues.push({ line: i + 1, pos: '行首', char: first });
|
||
if (banEnd.includes(last)) issues.push({ line: i + 1, pos: '行尾', char: last });
|
||
});
|
||
return {
|
||
ok: issues.length === 0, issues, lineCount: lines.length,
|
||
rule: '行首禁:' + banStart.split('').join(' ') + ';行尾禁:' + banEnd.split('').join(' '),
|
||
tip: issues.length ? '调整断行位置,把禁则标点让位到行尾或并入前一行;标题场景可手工插入软换行控制标点落位' : '标点落位符合中文禁则,排版规范'
|
||
};
|
||
},
|
||
test() { return { bad: this.run({ lines: ['。行首禁则测试', '行尾禁则测试('] }).issues.length, good: this.run({ lines: ['行首正常,行尾正常。'] }).ok }; }
|
||
},
|
||
|
||
/* ---------- 第八批:grid 网格对齐检查 ---------- */
|
||
{
|
||
id: 'poster-grid-align', name: '网格对齐', category: 'grid', emoji: '📏',
|
||
scene: '检查海报元素边缘是否对齐到网格线,消除悬空元素',
|
||
summary: '按分栏网格(列数/栏距/边距)计算网格线,检查元素左/右边缘是否贴齐网格线(容差 8px),输出未对齐元素清单。',
|
||
params: { elements: '元素数组(含 x/w)', grid: '{cols, gutter, margin, width}(可选,默认 3 栏)' },
|
||
source: '自研(网格对齐检查)',
|
||
run(args) {
|
||
const els = (args.elements || []).filter(e => e.w > 0);
|
||
const g = args.grid || {};
|
||
const cols = Number(g.cols) || 3;
|
||
const gutter = Number(g.gutter) || 32;
|
||
const margin = Number(g.margin) || 48;
|
||
const width = Number(g.width) || 1000;
|
||
if (!els.length) return { ok: true, note: '无元素' };
|
||
const innerW = width - margin * 2;
|
||
const colW = (innerW - gutter * (cols - 1)) / cols;
|
||
const lines = [];
|
||
for (let i = 0; i <= cols; i++) lines.push(margin + i * (colW + gutter));
|
||
const near = (v, line) => Math.abs(v - line) <= 8;
|
||
const issues = [];
|
||
for (const e of els) {
|
||
const left = e.x, right = e.x + e.w;
|
||
const onGrid = lines.some(ln => near(left, ln)) || lines.some(ln => near(right, ln));
|
||
if (!onGrid) issues.push({ id: e.id || '元素', x: Math.round(left), tip: '左右边缘均未贴齐网格线,建议吸附到 ' + lines[0] + '~' + lines[lines.length - 1] + 'px 网格线' });
|
||
}
|
||
return { ok: issues.length === 0, issues, cols, colWidth: Math.round(colW), lines: lines.map(Math.round), tip: issues.length ? '元素悬空会显杂乱:让每个元素左或右边缘贴齐网格线(栏位起点),大元素跨栏时贴齐栏边界' : '元素均已对齐网格线,结构稳定' };
|
||
},
|
||
test() { return { bad: this.run({ elements: [{ id: 'a', x: 100, w: 300 }, { id: 'b', x: 570, w: 300 }] }).issues.length, ok: this.run({ elements: [{ id: 'a', x: 48, w: 288 }] }).ok }; }
|
||
},
|
||
|
||
/* ---------- 第八批:hierarchy CTA 引导 ---------- */
|
||
{
|
||
id: 'poster-cta-focus', name: 'CTA 引导', category: 'hierarchy', emoji: '🔘',
|
||
scene: '检查活动海报的行动号召(CTA)是否足够突出',
|
||
summary: '定位 CTA 元素(type=cta 或 ctaId 指定),将其视觉权重与最大非 CTA 元素对比:权重比 ≥ 1.6 才算突出,并校验 CTA 与底色对比。',
|
||
params: { elements: '元素数组(含 id/type/weight 或 fontSize/w/h)', ctaId: 'CTA 元素 id(可选,默认 type=cta)' },
|
||
source: '自研(行动号召突出度检查)',
|
||
run(args) {
|
||
const els = (args.elements || []).filter(e => e.w > 0 || e.fontSize || e.weight);
|
||
const ctaId = String(args.ctaId || '');
|
||
const score = e => (e.weight || 0) || (e.fontSize || 0) || (e.w * e.h);
|
||
const cta = ctaId ? els.find(e => String(e.id) === ctaId) : els.find(e => e.type === 'cta');
|
||
if (!cta) return { ok: false, note: '未找到 CTA 元素:请传入 ctaId 或给元素标 type=cta', tip: '活动海报必须有一个明确的行动号召(扫码/购票/报名/领券),否则转化率大降' };
|
||
const ctaScore = score(cta);
|
||
const others = els.filter(e => e !== cta).map(score);
|
||
const maxOther = others.length ? Math.max(...others) : 1;
|
||
const ratio = ctaScore / Math.max(maxOther, 1);
|
||
const ok = ratio >= 1.6;
|
||
return {
|
||
ok, ctaId: cta.id || 'CTA', ratio: Math.round(ratio * 100) / 100,
|
||
contrast: cta.contrast != null ? cta.contrast : '(未提供,建议 ≥ 3:1)',
|
||
tip: ok ? 'CTA 权重突出(' + ratio + ' 倍于次强元素),位置建议放视线终点(右下/居中偏下)' : 'CTA 权重仅 ' + ratio + ' 倍,建议放大字号/加深底色或周边留白,让行动号召一眼可见'
|
||
};
|
||
},
|
||
test() { return { ok: this.run({ elements: [{ id: 'title', fontSize: 90 }, { id: 'cta', type: 'cta', fontSize: 200 }] }).ok, weak: this.run({ elements: [{ id: 'title', fontSize: 180 }, { id: 'cta', type: 'cta', fontSize: 100 }] }).ok === false }; }
|
||
},
|
||
|
||
/* ---------- 第八批:print 专色印刷 ---------- */
|
||
{
|
||
id: 'poster-spot-color', name: '专色印刷', category: 'print', emoji: '🖍️',
|
||
scene: '判断品牌色是否该用专色印刷,及专色文件的制作要点',
|
||
summary: '按品牌色属性(大面积底色/极亮极暗色/品牌核心标识)输出是否启用专色(PMS)的建议,并给出专色通道、叠印与印刷厂沟通要点。',
|
||
params: { color: '品牌色 hex', scene: '使用场景:bg 大面积底色 / logo 标识 / text 文字(默认 logo)' },
|
||
source: '自研(专色 vs 四色印刷规范)',
|
||
run(args) {
|
||
const c = String(args.color || '').replace('#', '');
|
||
const scene = String(args.scene || 'logo');
|
||
const valid = /^[0-9a-fA-F]{6}$/.test(c);
|
||
// 内联亮度(海报技能不依赖 HGB_Skills.utils)
|
||
const l = valid ? (function () {
|
||
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;
|
||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||
})() : 0.5;
|
||
const needSpot = scene === 'bg' || l > 0.92 || l < 0.08;
|
||
return {
|
||
ok: true,
|
||
color: valid ? '#' + c : '(无效 hex)',
|
||
scene: scene === 'bg' ? '大面积底色' : (scene === 'text' ? '文字' : '品牌标识'),
|
||
useSpot: needSpot,
|
||
rule: needSpot ? '建议启用专色(PMS):大面积底色/极亮极暗色用四色叠印难稳定还原,专色可保证批量印刷一致' : '可用四色印刷(CMYK 模拟),品牌核心场景建议仍准备专色版本',
|
||
file: '① 专色单独建图层并命名 PMS 色号;② 大面积专色与底色叠印需做「叠印预览」检查;③ 专色另转灰度稿供低价印刷场景使用',
|
||
tip: '专色文件务必与印刷厂确认色号(Pantone 需附官方色卡编号,勿仅给 hex);金属色与高饱和橙红四色印刷易偏色,优先专色'
|
||
};
|
||
},
|
||
test() { return { bg: this.run({ color: '#E8590C', scene: 'bg' }).useSpot, logo: this.run({ color: '#E8590C', scene: 'logo' }).useSpot === false }; }
|
||
},
|
||
|
||
/* ---------- 第八批:creative 拼贴手法 ---------- */
|
||
{
|
||
id: 'poster-collage', name: '拼贴手法', category: 'creative', emoji: '🧻',
|
||
scene: '摄影/杂志/纸张拼贴海报的创意手法与构图建议',
|
||
summary: '按主题推荐拼贴风格(摄影拼贴/杂志拼贴/纸张拼贴),给出素材处理(裁切/叠加/混合模式)与构图要点。',
|
||
params: { theme: '海报主题', style: 'collage 风格:photo/magazine/paper(可选)' },
|
||
source: '自研(拼贴艺术手法)',
|
||
run(args) {
|
||
const t = String(args.theme || '');
|
||
const s = String(args.style || '');
|
||
const map = {
|
||
photo: { name: '摄影拼贴', fit: ['音乐节', '旅行', '潮流服饰'], tech: ['多图自由裁切堆叠', '混合模式 multiply/screen 制造层次', '描边或投影统一图片边界'] },
|
||
magazine: { name: '杂志拼贴', fit: ['时尚', '艺术展览', '文化沙龙'], tech: ['文字与图片穿插出血', '标题文字压在图片上', '复古半调网点/胶片颗粒'] },
|
||
paper: { name: '纸张拼贴', fit: ['儿童活动', '手作市集', '环保主题'], tech: ['不规则纸张裁切 + 阴影', '手写字体与涂鸦', '纸胶带/别针等道具元素'] }
|
||
};
|
||
const hit = map[s] || (t.includes('儿童') || t.includes('市集') ? map.paper : (t.includes('时尚') || t.includes('艺术') ? map.magazine : map.photo));
|
||
return { ok: true, theme: t || '(未提供)', style: hit.name, fit: hit.fit, techniques: hit.tech, tip: '拼贴的灵魂是「统一感」:先定 1 个主色调与 1 种图形语言(裁切方式/描边/纹理),再堆叠素材,避免素材风格打架' };
|
||
},
|
||
test() { return { paper: this.run({ theme: '儿童手作市集' }).style, magazine: this.run({ style: 'magazine' }).techniques.length }; }
|
||
},
|
||
|
||
/* ---------- 第八批:ai 海报文案框架 ---------- */
|
||
{
|
||
id: 'poster-copywriting', name: '海报文案框架', category: 'ai', emoji: '✍️',
|
||
scene: '为活动/促销海报生成标题、副标题、正文与行动号召的文案框架',
|
||
summary: '按「事件 + 利益 + 悬念」公式生成主标题候选,再补副标题/正文要点/CTA,输出可直接填内容的文案骨架。',
|
||
params: { theme: '活动主题/事件', benefit: '用户利益点(可选)', event: '活动类型:sale/event/launch(可选)' },
|
||
source: '自研(AIDA 文案框架)',
|
||
run(args) {
|
||
const theme = String(args.theme || '');
|
||
const benefit = String(args.benefit || '');
|
||
const event = String(args.event || '');
|
||
const ev = event === 'sale' ? '促销' : (event === 'launch' ? '新品发布' : '主题活动');
|
||
const b = benefit || '专属优惠';
|
||
return {
|
||
ok: true, theme: theme || '(未提供)', event: ev,
|
||
headline: theme ? [theme + '|' + b, b + ',' + theme + '限时开启', '错过再等一年:' + theme] : ['主标题(事件 + 利益)', '副标题(补足细节)'],
|
||
subhead: theme ? theme + ',' + b + ',即刻出发' : '副标题:一句话说清时间/地点/对象',
|
||
body: ['正文要点 1(时间地点)', '正文要点 2(活动内容)', '正文要点 3(规则说明)'],
|
||
cta: 'CTA:立即报名 / 扫码领券 / 马上抢购',
|
||
tip: '文案骨架按「标题抓眼球 → 副标题补信息 → 正文给细节 → CTA 促行动」四层;标题字数 ≤ 12 字(中文)避免换行破版'
|
||
};
|
||
},
|
||
test() { const r = this.run({ theme: '夏季美食节', benefit: '全场 5 折' }); return { headlines: r.headline.length, cta: r.cta.includes('CTA') }; }
|
||
}
|
||
]);
|