root 651cc9e1fd D121: xiaohongshu-cover module v5 complete
MODULE-COVER-001: full package for external AI consumption
- server.js + renderer.js + generate.js + config.js
- templates/ (5 templates: xiaohongshu + dynamic + jike + poster + registry)
- package.json + README.md + MODULE.hdlp
- INDEX.hdlp + SYSTEM.hdlp (HLDP declarations)
- Fonts: Noto Sans CJK SC priority (no tofu blocks)
- No domain watermark
- Updated module-registry.json
- Copyright 2026-A-00037559
2026-06-01 16:36:00 +08: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;')
}