铸渊 549c51dab0 🎨 神笔马良模式 - 冰朔说内容,铸渊出图
彻底重构图片工作室:
- fromText() 入口:冰朔给自由文本,系统自动分析内容特征
- 自动检测内容类型(notice/recruit/quote/list/article...)
- 自动推荐配色方案(根据情绪和类型)
- 自动推荐布局和尺寸
- 新增 dynamic.js 万能兜底模板
- 保留底层渲染引擎不变

使用方式:
  node generate.js --text "你的内容" --for "小红书/即刻/海报"
或者对话中我直接调用 fromText()
2026-05-22 23:51:28 +00:00

193 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ═══════════════════════════════════════════════════
* 万能动态模板 · 自适应任何内容
* ═══════════════════════════════════════════════════
*
* 不是固定类型。是根据冰朔的文字内容,现场设计。
*/
import { STYLES } from '../config.js'
/**
* 万能动态卡片
* @param {object} data
* @param {string} data.title - 提炼的标题
* @param {string} data.body - 正文
* @param {string[]} data.lines - 原文行数组
* @param {string} data.type - 内容类型analyzeText 的结果)
*/
export function dynamicCard(data) {
const { title, body, lines = [], type = 'general' } = data
const C = STYLES.colors
const F = STYLES.fonts
const T = STYLES.typography
const W = 1080
const H = 1080
// 动态计算字号:内容越多字越小
const totalChars = (title + body).length
const titleSize = totalChars < 20 ? 52 : totalChars < 50 ? 44 : 36
const bodySize = totalChars < 50 ? 32 : totalChars < 150 ? 26 : 22
// 检测有没有列表
const hasList = lines.some(l => /^[-·*]\s/.test(l.trim()))
// 检测是不是一句话
const isSingleLine = lines.length <= 2 && totalChars < 40
/* ── 生成内容 HTML ── */
let contentHtml = ''
if (isSingleLine) {
/* 一句话 · 大字居中 */
contentHtml = `
<div class="single-line">${escapeHtml(title)}</div>
`
} else if (hasList) {
/* 列表内容 */
const items = lines
.filter(l => /^[-·*]\s/.test(l.trim()))
.map(l => `<li>${escapeHtml(l.replace(/^[-•·*]\s*/, ''))}</li>`)
.join('\n')
contentHtml = `
<div class="title" style="font-size:${titleSize}px">${escapeHtml(title)}</div>
<ul class="dynamic-list">${items}</ul>
`
} else {
/* 一般段落 */
const paragraphs = lines
.filter(Boolean)
.map(l => `<p>${escapeHtml(l)}</p>`)
.join('\n')
contentHtml = `
<div class="title" style="font-size:${titleSize}px">${escapeHtml(title)}</div>
<div class="body-text" style="font-size:${bodySize}px">${paragraphs}</div>
`
}
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
body {
width: ${W}px;
height: ${H}px;
overflow: hidden;
background: ${C.bg};
font-family: ${F.body};
display: flex;
position: relative;
}
/* 动态背景装饰 */
.bg-circle {
position: absolute;
border-radius: 50%;
opacity: 0.04;
}
.bg-c1 {
width: 400px; height: 400px;
background: ${C.accent};
top: -100px; right: -100px;
}
.bg-c2 {
width: 250px; height: 250px;
background: ${C.highlight};
bottom: -60px; left: -60px;
}
.container {
flex: 1;
padding: 56px 60px;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
z-index: 1;
}
.single-line {
font-family: ${F.title};
font-size: ${titleSize}px;
font-weight: 600;
line-height: 1.5;
color: ${C.primary};
text-align: center;
letter-spacing: 3px;
}
.title {
font-family: ${F.title};
font-weight: 700;
line-height: 1.35;
color: ${C.primary};
margin-bottom: 24px;
letter-spacing: 1px;
}
.body-text {
line-height: 1.7;
color: ${C.text};
}
.body-text p {
margin-bottom: 10px;
}
.dynamic-list {
list-style: none;
padding: 0;
}
.dynamic-list li {
font-size: ${bodySize}px;
line-height: 1.5;
color: ${C.text};
padding: 12px 20px;
margin-bottom: 8px;
background: ${C.warm}40;
border-radius: 8px;
border-left: 3px solid ${C.accent};
}
/* 底部品牌 */
.brand-bar {
position: absolute;
bottom: 24px;
left: 0;
right: 0;
text-align: center;
font-size: 14px;
color: ${C.textMuted};
opacity: ${STYLES.brand.opacity};
z-index: 1;
}
</style>
</head>
<body>
<div class="bg-circle bg-c1"></div>
<div class="bg-circle bg-c2"></div>
<div class="container">
${contentHtml}
</div>
<div class="brand-bar">${STYLES.brand.text}</div>
</body>
</html>`
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
}