铸渊 c1b3c02ef6 🎨 铸渊图片工作室 - 小红书/即刻/海报配图生成工具
为冰朔定制专属配图生成系统,支持三种卡片类型:
- 小红书竖版卡片 (1080×1440, 3:4)
- 即刻方卡 (1080×1080, 1:1)
- 海报 (1080×1920, 9:16)

特性:多套配色方案、多种布局风格、
      Puppeteer + HTML/CSS 渲染引擎、
      可部署 Web 服务 + 预览页面

使用方式:冰朔在对话里告诉铸渊内容和类型,
         铸渊运行脚本生成,冰朔直接下载使用。
2026-05-22 23:49:02 +00:00

317 lines
7.4 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.

/**
* ═══════════════════════════════════════════════════
* 小红书卡片模板 · 1080×1440 (3:4)
* ═══════════════════════════════════════════════════
*
* 支持:
* - 单页卡片(封面/金句/干货)
* - 多页轮播(长内容自动分页)
*/
import { STYLES } from '../config.js'
const W = STYLES.sizes.xiaohongshu.width
const H = STYLES.sizes.xiaohongshu.height
/**
* 生成小红书卡片 HTML
* @param {object} data
* @param {string} data.title - 标题
* @param {string} data.body - 正文(支持 \n 换行)
* @param {string} data.subtitle - 副标题(可选)
* @param {string} data.tag - 标签(可选,如「干货·教程」)
* @param {string} data.quote - 金句/引用(可选)
* @param {string} data.footer - 底部文字(可选,如账号名)
* @param {string} data.layout - 布局: 'default' | 'quote' | 'list' | 'minimal'
* @param {number} data.pageNum - 当前页码(多页时)
* @param {number} data.totalPages - 总页数(多页时)
*/
export function xiaohongshuCard(data) {
const {
title = '',
body = '',
subtitle = '',
tag = '',
quote = '',
footer = '',
layout = 'default',
pageNum = 1,
totalPages = 1,
} = data
const C = STYLES.colors
const F = STYLES.fonts
const T = STYLES.typography
// 正文按换行分段
const bodyLines = body.split('\n').filter(Boolean)
const bodyHtml = bodyLines.map(line => {
// 检测是否以 - 开头(列表项)
if (line.trimStart().startsWith('- ')) {
return `<li>${line.trimStart().slice(2)}</li>`
}
// 检测是否以 # 开头(小标题)
if (line.trimStart().startsWith('#')) {
return `<h3 class="sub-heading">${line.trimStart().replace(/^#+\s*/, '')}</h3>`
}
return `<p>${line}</p>`
}).join('\n')
const hasList = bodyLines.some(l => l.trimStart().startsWith('- '))
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;
flex-direction: column;
position: relative;
}
/* ── 装饰角标 ── */
.corner-tl, .corner-br {
position: absolute;
width: 60px;
height: 60px;
border-color: ${C.accent};
opacity: 0.15;
}
.corner-tl {
top: 24px; left: 24px;
border-top: 2px solid;
border-left: 2px solid;
}
.corner-br {
bottom: 24px; right: 24px;
border-bottom: 2px solid;
border-right: 2px solid;
}
.container {
flex: 1;
padding: ${STYLES.spacing.paddingY}px ${STYLES.spacing.paddingX}px;
display: flex;
flex-direction: column;
justify-content: center;
}
/* ── 标签 ── */
.tag {
display: inline-block;
padding: 8px 20px;
background: ${C.accent};
color: ${C.textLight};
font-size: ${T.captionSize}px;
border-radius: 20px;
margin-bottom: 24px;
align-self: flex-start;
letter-spacing: 2px;
}
/* ── 标题 ── */
.title {
font-family: ${F.title};
font-size: ${T.titleSize}px;
font-weight: 700;
line-height: 1.35;
color: ${C.primary};
margin-bottom: ${subtitle ? 16 : 32}px;
letter-spacing: 2px;
}
.subtitle {
font-size: ${T.subtitleSize}px;
font-weight: 300;
color: ${C.textMuted};
margin-bottom: 32px;
letter-spacing: 1px;
}
/* ── 正文 ── */
.body {
flex: 1;
font-size: ${T.bodySize}px;
line-height: ${T.lineHeight};
color: ${C.text};
}
.body p {
margin-bottom: 12px;
}
.body ul, .body ol {
padding-left: 32px;
margin-bottom: 12px;
}
.body li {
font-size: ${T.bodySize}px;
line-height: ${T.lineHeight};
margin-bottom: 8px;
color: ${C.text};
list-style: none;
position: relative;
padding-left: 24px;
}
.body li::before {
content: "◆";
position: absolute;
left: 0;
color: ${C.highlight};
font-size: 14px;
top: 6px;
}
.sub-heading {
font-family: ${F.title};
font-size: ${T.subtitleSize}px;
font-weight: 600;
color: ${C.accent};
margin: 24px 0 12px 0;
letter-spacing: 1px;
}
/* ── 金句布局 ── */
.quote-section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 0 40px;
}
.quote-mark {
font-size: 80px;
font-family: ${F.title};
color: ${C.highlight};
opacity: 0.3;
line-height: 1;
margin-bottom: 16px;
}
.quote-text {
font-family: ${F.title};
font-size: 40px;
font-weight: 600;
line-height: 1.5;
color: ${C.primary};
letter-spacing: 3px;
}
.quote-author {
margin-top: 32px;
font-size: ${T.smallSize}px;
color: ${C.textMuted};
}
/* ── 极简布局 ── */
.minimal-title {
font-family: ${F.title};
font-size: 48px;
font-weight: 300;
line-height: 1.4;
color: ${C.primary};
letter-spacing: 4px;
text-align: center;
}
.minimal-divider {
width: 60px;
height: 1px;
background: ${C.gold};
margin: 32px auto;
}
/* ── 底部 ── */
.footer {
padding: 24px ${STYLES.spacing.paddingX}px;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid ${C.divider};
font-size: ${T.captionSize}px;
color: ${C.textMuted};
}
.brand {
opacity: ${STYLES.brand.opacity};
}
.pagination {
font-size: ${T.captionSize}px;
color: ${C.textMuted};
}
</style>
</head>
<body>
<div class="corner-tl"></div>
<div class="corner-br"></div>
<div class="container">
${tag ? `<div class="tag">${tag}</div>` : ''}
${layout === 'quote' ? `
<div class="quote-section">
<div class="quote-mark">"</div>
<div class="quote-text">${quote || title}</div>
${subtitle ? `<div class="quote-author">${subtitle}</div>` : ''}
</div>
` : layout === 'minimal' ? `
<div style="flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;">
<div class="minimal-title">${title}</div>
<div class="minimal-divider"></div>
${body ? `<div style="font-size:${T.bodySize}px;color:${C.textMuted};text-align:center;max-width:80%;">${body}</div>` : ''}
</div>
` : `
<div class="title">${title}</div>
${subtitle ? `<div class="subtitle">${subtitle}</div>` : ''}
<div class="body">
${bodyHtml}
</div>
`}
</div>
<div class="footer">
<span class="brand">${STYLES.brand.text}</span>
${totalPages > 1 ? `<span class="pagination">${pageNum} / ${totalPages}</span>` : ''}
</div>
</body>
</html>`
}
/**
* 生成小红书轮播(多页)
* @param {object} data - 同 xiaohongshuCard
* @param {string[]} pages - 每页正文内容数组
* @returns {string[]} HTML 数组
*/
export function xiaohongshuCarousel(data, pages) {
return pages.map((body, i) => xiaohongshuCard({
...data,
body,
pageNum: i + 1,
totalPages: pages.length,
}))
}