
Ljg Card
- 6.3k installs
- 6.7k repo stars
- Updated August 4, 2026
- lijigang/ljg-skills
ljg-card is a content caster skill that transforms text or URLs into PNG visuals using seven HTML template molds and Playwright capture.
About
The ljg-card skill (铸) transforms source content into polished PNG visual deliverables using seven selectable molds. Default -l renders a 1080px-wide long reading card; -i builds content-driven infographics; -m splits material into 1080x1440 multi-cards; -v produces editorial sketchnotes tracing problem, failure, pivot, insight, and naming; -c outputs manga-style black-and-white comics; -w draws marker whiteboard diagrams with structured boxes and arrows; -b creates 1080x1440 big-font attachment cards with weathered stone styling for Xiaohongshu posts. Input arrives from WebFetch URLs, pasted text, or Read file paths. Before any mold, agents Read references/taste.md to enforce anti-AI-slop rules: no Inter fonts, pure black fills, three-column cards, centered hero layouts, hollow AI copy, or fake data. HTML templates render through node assets/capture.js with Playwright Chromium, saving titled PNGs to ~/Downloads/. Optional {{SOURCE_LINE}} footers credit authors on applicable molds. Replaces ljg-cards and ljg-infograph. Invoke when users say 铸, cast, 做成图, sketchnote, comic, whiteboard, or 小红书卡片.
- Seven molds: long card, infograph, multi-card, sketchnote, comic, whiteboard, and big-font attachment.
- Accepts URL WebFetch, pasted text, or Read file paths with title-based PNG filenames.
- references/taste.md enforces anti-AI visual quality rules across every mold.
- Playwright capture.js renders HTML templates to ~/Downloads/ at 1080px width.
- Optional {{SOURCE_LINE}} footer credits content sources on supported molds.
Ljg Card by the numbers
- 6,344 all-time installs (skills.sh)
- +99 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #96 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
ljg-card capabilities & compatibility
- Capabilities
- seven visual molds with flag specific html templ · url, paste, and file path content ingestion · playwright chromium png capture via assets/captu · taste.md anti ai slop quality enforcement · optional source attribution footer on supported · title based png naming up to 20 characters
- Works with
- playwright · chrome
- Use cases
- image generation · copywriting · marketing · presentations
- Platforms
- macOS · Linux · WSL
- Runs
- Runs locally
- Pricing
- Free
What ljg-card says it does
将内容铸成可见的形态。内容进去,PNG 出来。模具决定形状。
Content caster (铸). Transforms content into PNG visuals.
反 AI 生成痕迹——禁 Inter 字体、禁纯黑、禁三等分卡片、禁居中 Hero、禁 AI 文案腔、禁假数据。
npx skills add https://github.com/lijigang/ljg-skills --skill ljg-cardAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6.3k |
|---|---|
| repo stars | ★ 6.7k |
| Security audit | 2 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | lijigang/ljg-skills ↗ |
How do I turn an article, note, or URL into a polished reading card, infographic, sketchnote, comic, or Xiaohongshu attachment image?
Cast URLs or text into PNG reading cards, infographics, sketchnotes, comics, whiteboards, or Xiaohongshu attachment visuals via seven molds.
Who is it for?
Creators converting long-form content into visual cards, infographics, sketchnotes, comics, or Xiaohongshu attachments.
Skip if: Skip when output must stay plain text, Org-mode, Denote, or ASCII-only per L0 constraints.
When should I use this skill?
User says 铸, cast, 做成图, 做成卡片, sketchnote, comic, whiteboard, big fonts, or 小红书卡片.
What you get
A titled PNG file in ~/Downloads/ rendered from the chosen mold with taste.md quality guardrails applied.
- PNG file saved to ~/Downloads/ named from content title, rendered in the selected layout mode
By the numbers
- 7 layout modes available (-l, -i, -m, -v, -c, -w, -b)
- Multi-card and big-font modes output at fixed 1080x1440 pixels
- Filename capped at 20 characters derived from content
Files
ljg-card: 铸
将内容铸成可见的形态。内容进去,PNG 出来。模具决定形状。
参数
| 参数 | 模具 | 尺寸 | 说明 |
|---|---|---|---|
-l(默认) | 长图 | 1080 x auto | 单张阅读卡,内容自动撑高 |
-i | 信息图 | 1080 x auto | 内容驱动的自适应视觉布局 |
-m | 多卡 | 1080 x 1440 | 自动切分为多张阅读卡片 |
-v | 视觉笔记 | 1080 x auto | 编辑式杂志专题:问题→失败→转折→顿悟→命名(6 layout 模具 / 4 字族对比 / 探案档案细节) |
-c | 漫画 | 1080 x auto | 日式黑白漫画风格,动态选择漫画家视觉语言 |
-w | 白板 | 1080 x auto | 白板马克笔风格,结构化框图+箭头+彩色标记 |
-b | 大字 | 1080 x 1440 | 碑刻大字 + 和紙 + 外阴影,小红书附件风格(单句/短段) |
约束
本 skill 输出为视觉文件(PNG),不适用 L0 中的 Org-mode、Denote 和 ASCII-only 规范。
共享基础
获取内容
- URL --> WebFetch 获取
- 粘贴文本 --> 直接使用
- 文件路径 --> Read 获取
文件命名
从内容提取标题或核心思想作为 {name}(中文直接用,去标点,≤ 20 字符)。
截图工具
node assets/capture.js <html> <png> <width> <height> [fullpage]从 skill 根目录运行。依赖 skill 根目录下的 node_modules/ 中的 playwright。如报错:
npm install playwright && npx playwright install chromiumFooter
- 左侧:logo + 李继刚(已硬编码在模板中)
- 右侧:内容来源(可选)——有明确来源时显示(如作者名、arxiv ID、网站名等),无来源时留空。使用
{{SOURCE_LINE}}变量:有来源时填<span class="info-source">来源文字</span>,否则空字符串。适用于-l、-i、-v、-c、-w模具(-m多卡无 footer,不适用)。
交付
1. 报告文件路径
品味准则
所有模具共享。执行任何模具前,先 Read references/taste.md,作为视觉质量底线贯穿全流程。
核心:反 AI 生成痕迹——禁 Inter 字体、禁纯黑、禁三等分卡片、禁居中 Hero、禁 AI 文案腔、禁假数据。
执行
根据参数选择模具,Read references/taste.md + 对应的 mode 文件,按步骤执行:
-l(默认):长图
Read references/mode-long.md,按其步骤执行。
模板:assets/long_template.html
-i:信息图
Read references/mode-infograph.md,按其步骤执行。
模板:assets/infograph_template.html
-m:多卡
Read references/mode-poster.md,按其步骤执行。
模板:assets/poster_template.html
-v:视觉笔记
Read references/mode-sketchnote.md,按其步骤执行。
模板:assets/sketchnote_template.html
-c:漫画
Read references/mode-comic.md,按其步骤执行。
模板:assets/comic_template.html
-w:白板
Read references/mode-whiteboard.md,按其步骤执行。
模板:assets/whiteboard_template.html
-b:大字
Read references/mode-big.md,按其步骤执行。
模板:assets/big_template.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700;900&family=Ma+Shan+Zheng&family=ZCOOL+QingKe+HuangYou&family=DM+Sans:wght@400;500;700&display=swap');
:root {
--bg-dark: #2B2520;
--paper: #F4EFE6;
--paper-edge: #ECE4D4;
--ink: #2C2826;
--ink-soft: #6A5F52;
--ink-lighter: #9A8E7E;
--shu: #C03C28;
--serif: 'Noto Serif SC', 'Songti SC', 'STSong', 'SimSun', serif;
--brush: 'Ma Shan Zheng', 'Noto Serif SC', 'STKaiti', cursive;
--block: 'ZCOOL QingKe HuangYou', 'Noto Serif SC', serif;
--sans: 'DM Sans', 'PingFang SC', system-ui, sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 1080px;
height: 1440px;
background: var(--bg-dark);
overflow: hidden;
}
body {
display: flex;
align-items: center;
justify-content: center;
padding: 22px;
}
/* The paper card — suspended, weathered, breathing */
.card {
width: 1036px;
height: 1396px;
background:
radial-gradient(ellipse 420px 320px at 18% 22%, rgba(90,70,55,0.08) 0%, transparent 60%),
radial-gradient(ellipse 520px 380px at 82% 38%, rgba(75,60,48,0.06) 0%, transparent 55%),
radial-gradient(ellipse 380px 280px at 42% 72%, rgba(100,80,65,0.07) 0%, transparent 58%),
radial-gradient(ellipse 460px 340px at 88% 85%, rgba(85,68,52,0.05) 0%, transparent 55%),
radial-gradient(ellipse 300px 220px at 10% 90%, rgba(95,75,60,0.04) 0%, transparent 55%),
linear-gradient(135deg, var(--paper) 0%, var(--paper-edge) 100%);
border-radius: 3px;
box-shadow:
0 0 0 1px rgba(80,60,40,0.18),
0 16px 48px rgba(0,0,0,0.45),
0 4px 12px rgba(0,0,0,0.3),
inset 0 0 140px rgba(160,135,100,0.1);
position: relative;
overflow: hidden;
}
/* Paper grain: subtle noise texture */
.card::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.15 0 0 0 0 0.12 0 0 0 0 0.1 0 0 0 0.06 0'/></filter><rect width='200' height='200' filter='url(%23n)'/></svg>");
opacity: 0.55;
mix-blend-mode: multiply;
pointer-events: none;
z-index: 1;
}
/* Ink cloud stains — painterly smudges, hand-placed via varied opacity */
.card::after {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 280px 180px at 12% 8%, rgba(60,48,38,0.09) 0%, transparent 70%),
radial-gradient(ellipse 320px 200px at 90% 18%, rgba(55,45,35,0.07) 0%, transparent 72%),
radial-gradient(ellipse 240px 160px at 68% 92%, rgba(70,55,42,0.08) 0%, transparent 70%);
pointer-events: none;
z-index: 2;
}
.stage {
position: relative;
width: 100%;
height: 100%;
padding: 96px 88px 180px 88px;
display: flex;
flex-direction: column;
justify-content: flex-start;
z-index: 3;
}
/* Main text — carved, weathered, massive */
.main-text {
font-family: var(--serif);
font-weight: 900;
color: var(--ink);
font-size: {{FONT_SIZE}}px;
line-height: 1.38;
letter-spacing: 0.04em;
text-align: left;
filter: url(#weathered);
/* subtle emboss for carved feel */
text-shadow:
0 1px 0 rgba(255,250,240,0.3),
0 -1px 2px rgba(40,30,22,0.15);
}
.main-text .shu {
color: var(--shu);
}
.main-text .sub {
font-size: 0.42em;
font-weight: 400;
color: var(--ink-soft);
letter-spacing: 0.1em;
display: block;
margin-top: 0.6em;
filter: none;
}
/* Signature — understated, bottom-left */
.signature {
position: absolute;
left: 88px;
bottom: 64px;
display: flex;
align-items: center;
gap: 16px;
z-index: 4;
}
.signature img {
width: 52px;
height: 52px;
border-radius: 50%;
object-fit: cover;
opacity: 0.9;
filter: sepia(0.15) saturate(0.85);
}
.signature .name {
font-family: var(--serif);
font-weight: 500;
font-size: 30px;
color: var(--ink-soft);
letter-spacing: 0.08em;
}
/* Source line — tiny, bottom-right, optional */
.source-line {
position: absolute;
right: 88px;
bottom: 74px;
font-family: var(--sans);
font-size: 22px;
color: var(--ink-lighter);
letter-spacing: 0.05em;
z-index: 4;
}
{{CUSTOM_CSS}}
</style>
</head>
<body>
<svg width="0" height="0" style="position:absolute">
<defs>
<filter id="weathered" x="-5%" y="-5%" width="110%" height="110%">
<feTurbulence type="fractalNoise" baseFrequency="0.82" numOctaves="2" seed="7" result="noise"/>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="1.8" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs>
</svg>
<div class="card">
<div class="stage">
<div class="main-text">{{MAIN_TEXT}}</div>
</div>
<div class="signature">
<img src="{{LOGO}}" alt="">
<span class="name">李继刚</span>
</div>
{{SOURCE_LINE}}
</div>
</body>
</html>
#!/usr/bin/env node
const path = require('path');
const fs = require('fs');
async function main() {
const args = process.argv.slice(2);
const htmlPath = args[0];
const outputPath = args[1];
const width = parseInt(args[2]) || 1200;
const height = parseInt(args[3]) || 1600;
const fullpage = args[4] === 'fullpage';
if (!htmlPath || !outputPath) {
console.error('Usage: node capture.js <html> <png> [width] [height] [fullpage]');
process.exit(1);
}
const resolvedHtml = path.resolve(htmlPath);
const logoUrl = 'file://' + path.resolve(__dirname, 'logo.png');
let content = fs.readFileSync(resolvedHtml, 'utf8');
if (content.includes('{{LOGO}}')) {
content = content.replace(/\{\{LOGO\}\}/g, logoUrl);
fs.writeFileSync(resolvedHtml, content, 'utf8');
}
let chromium;
try {
chromium = require('playwright').chromium;
} catch {
console.error('Playwright not found. Run: npx playwright install chromium');
process.exit(1);
}
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setViewportSize({ width, height: fullpage ? 800 : height });
const fileUrl = 'file://' + resolvedHtml;
await page.goto(fileUrl, { waitUntil: 'networkidle' });
await page.waitForTimeout(500);
if (fullpage) {
const bodyHeight = await page.evaluate(() => document.body.scrollHeight);
await page.setViewportSize({ width, height: bodyHeight });
await page.waitForTimeout(300);
await page.screenshot({
path: path.resolve(outputPath),
type: 'png',
clip: { x: 0, y: 0, width, height: bodyHeight }
});
} else {
await page.screenshot({
path: path.resolve(outputPath),
type: 'png',
clip: { x: 0, y: 0, width, height }
});
}
await browser.close();
console.log('OK: ' + path.resolve(outputPath));
}
main().catch(err => {
console.error(err.message);
process.exit(1);
});
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700;900&family=DM+Sans:wght@400;500;700&display=swap');
:root {
--bg: #FAFAFA;
--ink: #1a1a1a;
--ink-mid: #555;
--ink-light: #999;
--white: #FAFAFA;
--accent: #1a1a1a;
--tone: #E8E8E8;
--serif: 'Noto Serif SC', 'KingHwa_OldSong', Georgia, serif;
--sans: 'DM Sans', 'PingFang SC', system-ui, sans-serif;
--mono: 'SF Mono', 'Menlo', monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 1080px; background: var(--bg); }
.page {
width: 1080px;
background: var(--bg);
position: relative;
}
/* ink spatter texture */
.page > .grain {
position: absolute;
inset: 0;
filter: url(#inkgrain);
opacity: 0.03;
pointer-events: none;
z-index: 100;
}
.colophon {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 28px 60px 36px;
border-top: 3px solid var(--ink);
}
.colophon .who {
display: flex;
align-items: center;
gap: 14px;
}
.colophon .who img {
width: 40px; height: 40px;
border-radius: 50%; object-fit: cover;
}
.colophon .who span {
font: 400 24px/1 var(--sans);
color: var(--ink-mid);
}
.colophon .info-source {
font: 400 22px/1 var(--mono);
color: var(--ink-light);
}
{{CUSTOM_CSS}}
</style>
</head>
<body>
<svg width="0" height="0" style="position:absolute">
<!-- ink grain filter -->
<filter id="inkgrain">
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="4" stitchTiles="stitch"/>
<feColorMatrix type="saturate" values="0"/>
</filter>
<!-- halftone dot pattern -->
<filter id="halftone">
<feTurbulence type="turbulence" baseFrequency="0.05" numOctaves="1"/>
<feColorMatrix type="saturate" values="0"/>
<feComponentTransfer>
<feFuncA type="discrete" tableValues="0 1"/>
</feComponentTransfer>
</filter>
<!-- rough ink edge -->
<filter id="roughen">
<feTurbulence type="turbulence" baseFrequency="0.04" numOctaves="4" result="noise"/>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="2" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</svg>
<div class="page">
<div class="grain"></div>
{{CONTENT_HTML}}
<div class="colophon">
<div class="who">
<img src="{{LOGO}}" alt="">
<span>李继刚</span>
</div>
{{SOURCE_LINE}}
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:wght@400;500;700&display=swap');
:root {
--bg: #F2F2F2;
--green: #B8D8BE;
--pink: #E91E63;
--yellow: #FFF200;
--ink: #2D2926;
--ink-light: #5C5350;
--white: #FFFFFF;
--serif: 'DM Serif Display', 'KingHwa_OldSong', Georgia, 'Noto Serif SC', serif;
--sans: 'DM Sans', 'KingHwa_OldSong', -apple-system, 'PingFang SC', system-ui, sans-serif;
--mono: 'SF Mono', 'Menlo', monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 1080px; background: var(--bg); }
.page {
width: 1080px;
background: var(--bg);
position: relative;
}
.page > .grain {
position: absolute;
inset: 0;
filter: url(#noise);
opacity: 0.04;
pointer-events: none;
z-index: 100;
}
mark { background: rgba(255,242,0,0.45); color: inherit; padding: 4px 8px; }
.colophon {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28px 60px 36px;
border-top: 1px solid rgba(45,41,38,0.08);
}
.colophon .who {
display: flex;
align-items: center;
gap: 14px;
}
.colophon .who img {
width: 40px; height: 40px;
border-radius: 50%; object-fit: cover;
}
.colophon .who span {
font: 400 24px/1 var(--sans);
color: var(--ink-light);
}
.colophon .info-source {
font: 400 22px/1 var(--mono);
color: var(--ink-light);
}
{{CUSTOM_CSS}}
</style>
</head>
<body>
<svg width="0" height="0" style="position:absolute">
<filter id="noise">
<feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
<feColorMatrix type="saturate" values="0"/>
</filter>
</svg>
<div class="page">
<div class="grain"></div>
{{CONTENT_HTML}}
<div class="colophon">
<div class="who">
<img src="{{LOGO}}" alt="">
<span>李继刚</span>
</div>
{{SOURCE_LINE}}
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
:root {
--bg: {{BG_COLOR}};
--text: #1D1D1F;
--text-mid: #6E6E73;
--text-dim: #ACACB0;
--accent: {{ACCENT_COLOR}};
--rule: #E5E5EA;
--font: 'KingHwa_OldSong', 'PingFang SC', system-ui, sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 1080px;
background: var(--bg);
}
.card {
width: 1080px;
background: var(--bg);
padding: 64px 72px 52px;
display: flex;
flex-direction: column;
}
/* ── Title ── */
.title-area {
flex-shrink: 0;
margin-bottom: 52px;
}
.title-area h1 {
font: 700 84px/1.15 var(--font);
color: var(--text);
letter-spacing: -0.03em;
margin-bottom: 22px;
}
.title-area::after {
content: '';
display: block;
width: 52px;
height: 3px;
background: var(--accent);
}
/* ── Content ── */
.content {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.content p {
font: 400 36px/1.7 var(--font);
color: var(--text);
margin-bottom: 28px;
}
.content .highlight {
font: 500 40px/1.55 var(--font);
color: var(--text);
padding: 14px 0 14px 26px;
border-left: 3px solid var(--accent);
margin: 36px 0;
}
.content .prompt {
font: 500 38px/1.55 var(--font);
color: #1D3A1D;
background: #E8F1E0;
padding: 24px 30px 24px 36px;
border-left: 4px solid #5A8B5A;
margin: 32px 0;
letter-spacing: -0.01em;
}
.content .prompt strong {
color: #1D3A1D;
}
.content h2 {
font: 600 42px/1.4 var(--font);
color: var(--text);
margin: 44px 0 22px;
letter-spacing: -0.02em;
}
.content h2:first-child {
margin-top: 0;
}
.content .subtitle {
font: 400 22px/2 var(--font);
color: var(--text-dim);
letter-spacing: 0.15em;
text-transform: uppercase;
margin-bottom: 28px;
}
.content .item {
margin-bottom: 36px;
}
.content .item:last-child {
margin-bottom: 0;
}
.content .item .label {
font: 500 36px/1.5 var(--font);
color: var(--text);
margin-bottom: 6px;
}
.content .item p {
font: 400 32px/1.65 var(--font);
color: var(--text-mid);
margin-bottom: 0;
}
.content blockquote {
margin: 0 0 28px;
padding-left: 26px;
border-left: 3px solid var(--rule);
}
.content blockquote p {
font: 300 36px/1.7 var(--font);
color: var(--text-mid);
margin-bottom: 6px;
}
.content strong {
font-weight: 600;
color: var(--text);
}
.content .divider {
height: 1px;
background: var(--rule);
margin: 36px 0;
}
.content ul {
list-style: none;
margin-bottom: 28px;
}
.content ul li {
font: 400 36px/1.7 var(--font);
color: var(--text);
padding: 4px 0 4px 28px;
position: relative;
}
.content ul li::before {
content: '·';
position: absolute;
left: 0;
color: var(--text-mid);
}
/* ── Drop Cap ── */
.content .dropcap::first-letter {
font: 700 128px/0.82 'KingHwa_OldSong', Georgia, serif;
float: left;
margin: 4px 16px 0 -4px;
color: var(--accent);
}
/* ── End Mark ── */
.content::after {
content: '∎';
display: block;
text-align: right;
font-size: 16px;
color: var(--accent);
opacity: 0.4;
margin-top: 40px;
}
/* ── Footer ── */
.footer {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 52px;
padding-top: 22px;
border-top: 1px solid var(--rule);
}
.footer .author {
display: flex;
align-items: center;
gap: 14px;
}
.footer .avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.footer .author-name {
font: 400 24px/1.5 var(--font);
color: var(--text-mid);
letter-spacing: 0.03em;
}
.footer .info-source {
font: 400 22px/1.5 'Menlo', 'SF Mono', monospace;
color: var(--text-dim);
letter-spacing: 0.02em;
}
</style>
</head>
<body>
<div class="card">
{{TITLE_BLOCK}}
<div class="content">
{{BODY_HTML}}
</div>
<div class="footer">
<div class="author">
<img class="avatar" src="{{LOGO}}" alt="">
<span class="author-name">李继刚</span>
</div>
{{SOURCE_LINE}}
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
:root {
--bg: {{BG_COLOR}};
--text: #1D1D1F;
--text-mid: #6E6E73;
--text-dim: #ACACB0;
--accent: {{ACCENT_COLOR}};
--rule: #E5E5EA;
--font: 'KingHwa_OldSong', 'PingFang SC', system-ui, sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 1080px;
height: 1440px;
overflow: hidden;
background: var(--bg);
}
.card {
width: 1080px;
height: 1440px;
background: var(--bg);
padding: 64px 72px 52px;
display: flex;
flex-direction: column;
}
/* ── Running title (continuation cards) ── */
.header {
flex-shrink: 0;
margin-bottom: 40px;
}
.header .running-title {
font: 400 24px/1.5 var(--font);
color: var(--text-dim);
letter-spacing: 0.08em;
}
/* ── Title ── */
.title-area {
flex-shrink: 0;
margin-bottom: 52px;
}
.title-area h1 {
font: 700 84px/1.15 var(--font);
color: var(--text);
letter-spacing: -0.03em;
margin-bottom: 22px;
}
.title-area::after {
content: '';
display: block;
width: 52px;
height: 3px;
background: var(--accent);
}
/* ── Content ── */
.content {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.content p {
font: 400 36px/1.7 var(--font);
color: var(--text);
margin-bottom: 28px;
}
.content .highlight {
font: 500 40px/1.55 var(--font);
color: var(--text);
padding: 14px 0 14px 26px;
border-left: 3px solid var(--accent);
margin: 36px 0;
}
.content h2 {
font: 600 42px/1.4 var(--font);
color: var(--text);
margin: 44px 0 22px;
letter-spacing: -0.02em;
}
.content h2:first-child {
margin-top: 0;
}
.content .subtitle {
font: 400 22px/2 var(--font);
color: var(--text-dim);
letter-spacing: 0.15em;
text-transform: uppercase;
margin-bottom: 28px;
}
.content .item {
margin-bottom: 36px;
}
.content .item:last-child {
margin-bottom: 0;
}
.content .item .label {
font: 500 36px/1.5 var(--font);
color: var(--text);
margin-bottom: 6px;
}
.content .item p {
font: 400 32px/1.65 var(--font);
color: var(--text-mid);
margin-bottom: 0;
}
.content blockquote {
margin: 0 0 28px;
padding-left: 26px;
border-left: 3px solid var(--rule);
}
.content blockquote p {
font: 300 36px/1.7 var(--font);
color: var(--text-mid);
margin-bottom: 6px;
}
.content strong {
font-weight: 600;
color: var(--text);
}
.content .divider {
height: 1px;
background: var(--rule);
margin: 36px 0;
}
.content ul {
list-style: none;
margin-bottom: 28px;
}
.content ul li {
font: 400 36px/1.7 var(--font);
color: var(--text);
padding: 4px 0 4px 28px;
position: relative;
}
.content ul li::before {
content: '·';
position: absolute;
left: 0;
color: var(--text-mid);
}
/* ── Footer ── */
.footer {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
padding-top: 22px;
border-top: 1px solid var(--rule);
}
.footer .author {
display: flex;
align-items: center;
gap: 14px;
}
.footer .avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.footer .author-name {
font: 400 24px/1.5 var(--font);
color: var(--text-mid);
letter-spacing: 0.03em;
}
.footer .page-info {
font: 400 20px/1.5 var(--font);
color: var(--text-dim);
letter-spacing: 0.03em;
}
</style>
</head>
<body>
<div class="card">
{{HEADER_BLOCK}}
{{TITLE_BLOCK}}
<div class="content">
{{BODY_HTML}}
</div>
<div class="footer">
<div class="author">
<img class="avatar" src="{{LOGO}}" alt="">
<span class="author-name">李继刚</span>
</div>
<span class="page-info">{{PAGE_INFO}}</span>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@500;700;900&display=swap');
:root {
/* 基底 */
--bg: #FAF8F4; /* 暖米白底 */
--ink: #1F1F1F; /* off-black 正文 */
--ink-strong: #111111; /* 标题 */
--ink-light: #6B6B6B; /* 标注 */
/* 5 类学科语义色(deep = 色条/分类标签;light = 卡片底) */
--green-deep: #5B8A6F;
--green-light: #DCE3D5;
--blue-deep: #5979A6;
--blue-light: #DCE3EB;
--red-deep: #B85048;
--red-light: #F0DDD3;
--gray-deep: #7B7570;
--gray-light: #E5E2DC;
--amber-deep: #B8924A;
--amber-light:#F0E5D2;
/* 关系箭头 / 手写标注 */
--arrow: #D2693C;
/* 字族 */
--sans: 'Noto Sans SC', 'PingFang SC', system-ui, sans-serif;
--serif: 'Noto Serif SC', 'Songti SC', serif;
--hand: 'Caveat', '楷体', 'STKaiti', 'KaiTi', cursive;
--mono: 'SF Mono', 'Menlo', monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 1080px; background: var(--bg); }
body { font-family: var(--sans); color: var(--ink); }
.page {
width: 1080px;
background: var(--bg);
position: relative;
overflow: visible;
}
/* 极淡纸张噪点(不是点阵) */
.page > .grain {
position: absolute; inset: 0;
filter: url(#noise);
opacity: 0.025;
pointer-events: none;
z-index: 100;
mix-blend-mode: multiply;
}
/* 整页 SVG overlay:用来画跨卡片的弯曲连接箭头 */
.relations {
position: absolute; inset: 0;
width: 100%; height: 100%;
pointer-events: none;
z-index: 50;
overflow: visible;
}
/* 卡片基础样式:仅提供"骨架",具体颜色由 {{CUSTOM_CSS}} 中的变体类覆盖 */
.card {
position: relative;
padding: 28px 32px 32px 40px;
background: var(--bg);
border-left: 5px solid var(--ink);
}
.card .category {
display: inline-block;
font: 500 22px/1 var(--sans);
color: var(--ink-light);
margin-bottom: 6px;
letter-spacing: 0.02em;
}
.card .concept {
font: 900 40px/1.15 var(--sans);
color: var(--ink-strong);
margin-bottom: 18px;
letter-spacing: -0.01em;
}
.card .body {
font: 400 22px/1.65 var(--sans);
color: var(--ink);
}
.card .body p + p { margin-top: 10px; }
.card .diagram {
margin: 14px 0 18px;
}
/* 学科色变体 */
.card.green { background: var(--green-light); border-color: var(--green-deep); }
.card.green .category { color: var(--green-deep); }
.card.blue { background: var(--blue-light); border-color: var(--blue-deep); }
.card.blue .category { color: var(--blue-deep); }
.card.red { background: var(--red-light); border-color: var(--red-deep); }
.card.red .category { color: var(--red-deep); }
.card.gray { background: var(--gray-light); border-color: var(--gray-deep); }
.card.gray .category { color: var(--gray-deep); }
.card.amber { background: var(--amber-light); border-color: var(--amber-deep); }
.card.amber .category { color: var(--amber-deep); }
/* 中央核心卡:更大、信息更密、不强绑学科色(默认中性米色) */
.card.core {
background: #F5F1E8;
border-left-color: var(--ink-strong);
padding: 36px 44px 40px 52px;
}
.card.core .concept {
font-size: 44px;
font-family: var(--serif);
font-weight: 700;
margin-bottom: 14px;
}
.card.core .body { font-size: 23px; }
/* 关系箭头标签:橙色手写体,浮于 SVG 路径之上 */
.relation-label {
position: absolute;
font: 600 30px/1.1 var(--hand);
color: var(--arrow);
transform-origin: left center;
pointer-events: none;
z-index: 60;
white-space: nowrap;
}
/* 顶部标题区 */
.title-block {
padding: 60px 60px 30px;
}
.title-block h1 {
font: 900 88px/1.0 var(--sans);
color: var(--ink-strong);
letter-spacing: -0.02em;
margin-bottom: 14px;
}
.title-block .subtitle {
font: 400 26px/1.5 var(--sans);
color: var(--ink-light);
max-width: 800px;
}
/* Footer */
.colophon {
position: relative;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
padding: 32px 60px 40px;
margin-top: 30px;
border-top: 1px solid rgba(0,0,0,0.08);
}
.colophon .who {
display: flex;
align-items: center;
gap: 14px;
}
.colophon .who img {
width: 40px; height: 40px;
border-radius: 50%; object-fit: cover;
}
.colophon .who span {
font: 400 22px/1 var(--sans);
color: var(--ink-light);
}
.colophon .info-source {
font: 400 20px/1 var(--mono);
color: var(--ink-light);
}
{{CUSTOM_CSS}}
</style>
</head>
<body>
<svg width="0" height="0" style="position:absolute">
<filter id="noise">
<feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" stitchTiles="stitch"/>
<feColorMatrix type="saturate" values="0"/>
</filter>
<defs>
<!-- 橙色手绘箭头 marker -->
<marker id="arrow-orange" markerWidth="14" markerHeight="10" refX="13" refY="5" orient="auto">
<path d="M0,0 L14,5 L0,10 L4,5 Z" fill="#D2693C"/>
</marker>
<!-- 黑色细箭头 marker(卡片内部流程用) -->
<marker id="arrow-ink" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#1F1F1F"/>
</marker>
</defs>
</svg>
<div class="page">
<div class="grain"></div>
{{CONTENT_HTML}}
<div class="colophon">
<div class="who">
<img src="{{LOGO}}" alt="">
<span>李继刚</span>
</div>
{{SOURCE_LINE}}
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css2?family=Permanent+Marker&family=Kalam:wght@400;700&family=DM+Sans:wght@400;500;700&display=swap');
:root {
--bg: #EBE5DA;
--board: #F7F3EC;
--ink: #2C2826;
--ink-light: #8A8478;
--red: #A09888;
--blue: #7A8A98;
--green: #7A8A7A;
--yellow: #7A6B4E;
--orange: #907A60;
--shu: #C03C28;
--marker-bg: rgba(44, 40, 38, 0.04);
--hand: 'Kalam', 'PingFang SC', cursive;
--marker: 'Permanent Marker', 'PingFang SC', cursive;
--sans: 'DM Sans', 'PingFang SC', system-ui, sans-serif;
--mono: 'SF Mono', 'Menlo', monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 1080px; background: var(--bg); }
.board-frame {
width: 1080px;
padding: 1px;
background: rgba(180,170,155,0.15);
border-radius: 2px;
box-shadow: 0 2px 24px rgba(0,0,0,0.25);
position: relative;
}
.board {
width: 100%;
background: var(--board);
position: relative;
overflow: hidden;
}
/* 和紙の光 — subtle warm light on paper */
.board > .surface {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 25% 20%, rgba(200,180,150,0.06) 0%, transparent 50%),
radial-gradient(ellipse at 75% 55%, rgba(180,160,130,0.04) 0%, transparent 45%);
pointer-events: none;
z-index: 0;
}
.colophon {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 56px 28px;
border-top: 1px solid rgba(212,196,176,0.04);
}
.colophon .who {
display: flex;
align-items: center;
gap: 14px;
}
.colophon .who img {
width: 40px; height: 40px;
border-radius: 50%; object-fit: cover;
}
.colophon .who span {
font: 400 24px/1 var(--sans);
color: var(--ink-light);
}
.colophon .info-source {
font: 400 22px/1 var(--mono);
color: var(--ink-light);
}
{{CUSTOM_CSS}}
</style>
</head>
<body>
<svg width="0" height="0" style="position:absolute">
<defs>
<marker id="arrow-r" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--red)" />
</marker>
<marker id="arrow-b" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--blue)" />
</marker>
<marker id="arrow-w" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--ink)" />
</marker>
<marker id="arrow-g" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--green)" />
</marker>
<marker id="arrow-y" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--yellow)" />
</marker>
<marker id="arrow-s" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--shu)" />
</marker>
</defs>
</svg>
<div class="board-frame">
<div class="board">
<div class="surface"></div>
{{CONTENT_HTML}}
<div class="colophon">
<div class="who">
<img src="{{LOGO}}" alt="">
<span>李继刚</span>
</div>
{{SOURCE_LINE}}
</div>
</div>
</div>
</body>
</html>
{
"name": "ljg-card",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"playwright": "^1.58.2"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.58.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
}
}
}
{
"dependencies": {
"playwright": "^1.58.2"
}
}
模具:大字(-b)
核心信条
一句话砸在纸上——碑刻于和紙,悬空如附件。
神:一句话要有份量。字足够大,让读者一眼被砸中。纸足够旧,像从某本古籍中揭下的一页。阴影足够深,让这张纸像独立的、悬在暗处的附件。
形:和紙奶白为底,墨色碑刻大字为体,左下一印一名为签。外有暗框如夜里托案,内有云晕如水渍未干。字不规整——有刀刻的毛边、水浸的斑驳——那是岁月,不是装饰。
审美三柱:
- 「重」——字号大到每行 2-5 字,一眼就能看完;每一笔都有墨迹的厚重
- 「旧」——纸有云晕、字有毛边;不是 Photoshop 的做旧滤镜,是实际的 SVG 湍流位移
- 「悬」——卡片有暗底外框、深层阴影、薄层内晕;像悬在空气中的一片纸
步骤 1:读取模板
Read assets/big_template.html
模板提供:
- 1080x1440 固定画布(小红书 3:4 HD 规范)
- 外层暗底
--bg-dark自动创造阴影边框(22px 内缩) - 和紙卡片 1036x1396 + 多重 radial-gradient 云晕 + SVG noise 纸张颗粒
<filter id="weathered">feTurbulence + feDisplacementMap 做旧滤镜(已配置 baseFrequency=0.82, scale=1.8).main-text默认衬线碑刻字(Noto Serif SC 900 + 刻痕 text-shadow).signature左下署名栏(logo + 李继刚)- 模板变量:
{{FONT_SIZE}}{{MAIN_TEXT}}{{SOURCE_LINE}}{{CUSTOM_CSS}}
步骤 2:理解内容
2.1 内容形态
-b 模具专用于单句/短段输出,不适合长文。典型输入:
- 一句观点/判断(15-30 字)
- 一个反问(10-20 字)
- 一条金句/标语(8-25 字)
- 最多两句(总 30-60 字)
如输入超过 60 字,提醒用户:内容过长,-b 模具失效,建议用 -l 长图或 -m 多卡。
2.2 计算字号
字符数 n(计入中文、英文、标点、空格,全角/半角各算 1):
| 字符数 | {{FONT_SIZE}} | 每行字数参考 |
|---|---|---|
| ≤ 10 | 220 | 3-4 |
| 11-16 | 190 | 4-5 |
| 17-24 | 160 | 5-6 |
| 25-34 | 135 | 6-7 |
| 35-46 | 115 | 7-9 |
| 47-60 | 98 | 8-10 |
这是基线——根据断行实际效果可上下浮动 10-15%。
2.3 手动断行(关键)
不要依赖浏览器自动换行。通过在 {{MAIN_TEXT}} 中插入 <br> 控制每行的语义。
断行原则:
- 按语义单元切,不在词中切
- 每行字数相近(差异 ≤ 2 字),视觉稳定
- 标点放在行末,不起新行
- 关键词/反问词起新行能加强冲击
参考图断行示例("人人拥有「寫輪眼」的时代,稀缺性在哪里?"):
人人拥有
「寫輪眼」
的时代,
稀缺性在
哪里?每行 3-4 字,最后两行构成反问高潮。
2.4 高亮(可选)
如需强调某个词/短语:用 <span class="shu">关键词</span> 染朱砂红。每张图 ≤ 1 处。用于结论句或反问重点。
步骤 3:写 HTML
3.1 主文本
第一行文字<br>
第二行文字<br>
<span class="shu">高亮</span>第三行<br>
第四行繁简混排原样保留(如参考图「寫輪眼」保繁体)。
3.2 可选:副标题/小字
如需在主文下方加一行小注(来源/作者/上下文),用 .sub 类:
<div class="main-text">
主要的一句话<br>
分成几行<br>
<span class="sub">—— 某某某</span>
</div>.sub 自动缩小到主字号的 42%,颜色变柔,不应用做旧滤镜(保持小字可读)。
3.3 来源线(可选)
如有来源,填充 {{SOURCE_LINE}}:
<div class="source-line">来源于《某某某》</div>无来源时留空字符串。
3.4 替换变量汇总
| 变量 | 内容 |
|---|---|
{{FONT_SIZE}} | 字号数值(不带 px),如 160 |
{{MAIN_TEXT}} | 主文 HTML(含 <br> 断行) |
{{SOURCE_LINE}} | 来源线 HTML 或空 |
{{CUSTOM_CSS}} | 额外自定义 CSS(一般留空,模板已足够) |
写入:/tmp/ljg_cast_big_{name}.html
步骤 4:自检
- [ ] 字号选择匹配字符数,每行 2-10 字?
- [ ] 断行在语义单元上,不在词中间切?
- [ ] 主文应用了
filter: url(#weathered)做旧? - [ ] 字体是 Noto Serif SC 900(不是 Inter、不是 PingFang 默认)?
- [ ] 颜色是
--ink(#2C2826) 墨色,不是纯黑? - [ ] 和紙底色
--paper(#F4EFE6),有云晕纹理? - [ ] 外层暗底 22px 创造阴影边框?
- [ ] 左下署名栏存在(logo + 李继刚)?
- [ ] 朱砂高亮 ≤ 1 处?
- [ ] 整体视觉:重、旧、悬 三感齐备?
步骤 5:截图
node assets/capture.js \
/tmp/ljg_cast_big_{name}.html \
~/Downloads/{name}.png \
1080 1440不用 fullpage——-b 模具是固定画布 1080x1440。
步骤 6:交付
报告文件路径。一句话说明断行和字号决策。
禁忌
- 禁:超过 60 字的输入(改用 -l 或 -m)
- 禁:默认浏览器换行(必须手动
<br>) - 禁:字号 < 90px(失去"大字"意义)
- 禁:朱砂高亮 > 1 处(失去点睛价值)
- 禁:移除做旧滤镜(失去碑刻感)
- 禁:改画布尺寸(1080x1440 是小红书规范,不可偏离)
模具:漫画(-c)
核心信条
黑与白之间的张力,就是全部。
漫画模具用日式黑白漫画的视觉语言讲故事。不是加了漫画边框的排版——是用分格节奏、黑白对比、集中线和留白来制造戏剧性。颜色几乎不用,最多一个灰色调(网点灰)。力量来自墨色的浓淡和构图的紧张感。
步骤 1:读取模板
Read assets/comic_template.html
模板提供:
- 字体加载(Noto Serif SC + DM Sans)
- CSS 变量(
--bg,--ink,--ink-mid,--ink-light,--white,--accent,--tone) - SVG 滤镜:
#inkgrain(墨迹纹理)、#halftone(网点)、#roughen(毛边) .colophon署名栏{{CUSTOM_CSS}}和{{CONTENT_HTML}}插槽
步骤 2:理解内容,选择风格
2.1 提取叙事要素
从内容中提取:
- 核心冲突/张力:这段内容里什么和什么在对抗?
- 3-5 个关键时刻:可以变成"格子"的场景或概念
- 情绪弧线:从什么状态到什么状态?
- 视觉锚点:最有画面感的那个概念
2.2 确定卡片数量
根据内容体量决定生成一张还是多张漫画卡:
| 内容体量 | 核心观点数 | 卡片数 | 说明 |
|---|---|---|---|
| 短文(< 1000 字) | 1-3 个 | 1 张 | 所有观点压入一页 |
| 中文(1000-3000 字) | 3-5 个 | 2-3 张 | 每张聚焦 1-2 个核心观点 |
| 长文(> 3000 字) | 5+ 个 | 3-5 张 | 每张聚焦 1-2 个核心观点 |
原则:
- 完整性优先:宁可多一张,不可丢观点。长文章的每个核心论点都必须被呈现
- 每张卡片自成一格:有独立标题、独立分格、独立叙事弧。不是把长内容硬切
- 系列编号:多卡时右上角标注
01/N~N/N - 首卡破题,末卡收束:第一张建立冲突/问题,最后一张给出结论/解法
2.3 提取原文配图
当原文包含图片时(WebFetch 返回的 markdown 中的  或 HTML <img> 标签):
- 收集所有图片 URL
- 判断哪些图片与核心观点相关(忽略 logo、广告、装饰图)
- 相关图片将在步骤 4 中嵌入分格
2.4 选择漫画风格
| 风格 | 视觉特征 | 触发信号 | CSS 变量覆盖 |
|---|---|---|---|
| 大友克洋 — 精密废墟 | 极细线条密集排列、机械/建筑细节、灰阶丰富、透视精确 | 技术/系统/架构/复杂机制、信息密度高 | --tone: #D0D0D0 |
| 井上雄彦 — 水墨留白 | 大面积留白、墨色浓淡渐变、笔触可见、极简构图 | 哲学/沉思/美学/人文、需要呼吸感 | --tone: #E8E0D8 |
| 三浦建太郎 — 暗黑压迫 | 大面积纯黑、极高对比、密集纹理、压迫感强 | 冲突/困境/黑暗面/挣扎、情绪强烈 | --bg: #F0F0F0; --tone: #C0C0C0 |
| 松本大洋 — 生猛粗线 | 粗细不均的线条、不规则构图、能量感、看似粗糙实则精确 | 运动/能量/创意/突破、有冲击力 | --tone: #E0E0E0 |
| 谷口治郎 — 静谧精描 | 建筑级精细线条、克制表情、银盐照片般的灰阶、安静 | 日常/观察/细节/安静的力量 | --tone: #E5E5E5 |
选择原则:
- 默认用「井上雄彦」——最通用的黑白美学
- 内容有技术/系统复杂度 → 大友克洋
- 内容有强烈冲突或黑暗面 → 三浦建太郎
- 内容有能量/突破/运动感 → 松本大洋
- 内容是安静的观察/日常 → 谷口治郎
系列一致性规则(同源多卡):当从同一个文本源(同一篇文章、同一本书、同一份报告)生成多张漫画卡片时,必须对整个文本源做一次整体判断,选定一个风格,所有卡片统一使用。不要按单个概念逐张选风格——风格跳跃会破坏阅读连续感。判断依据是文本源的整体气质,不是某个局部章节的情绪。
步骤 3:设计画面
3.1 漫画视觉元素工具箱
所有元素用 CSS + SVG 实现。黑白为主,灰色网点为辅,不用彩色。
分格系统(Panel Layout)
漫画的核心是分格。用 CSS Grid 实现不规则分格:
/* 基础分格 */
.panel {
border: 3px solid var(--ink);
background: var(--white);
position: relative;
overflow: hidden;
padding: 28px 32px;
}
/* 出血格(内容冲破边框) */
.panel-bleed {
border: none;
margin: -3px;
z-index: 2;
}
/* 斜切分格 */
.panel-slanted {
clip-path: polygon(0 0, 100% 8%, 100% 100%, 0 92%);
}集中线(Focus Lines)
用于强调关键概念,制造"震撼"感:
.focus-lines {
position: relative;
}
.focus-lines::before {
content: '';
position: absolute;
inset: 0;
background: repeating-conic-gradient(
var(--ink) 0deg 0.5deg,
transparent 0.5deg 5deg
) center/100% 100%;
opacity: 0.06;
pointer-events: none;
}速度线(Speed Lines)
表现动态、变化、冲击:
.speed-lines {
background-image: repeating-linear-gradient(
90deg,
transparent,
transparent 4px,
var(--ink) 4px,
var(--ink) 4.5px
);
opacity: 0.08;
}对话泡 / 思想泡
.speech-bubble {
background: var(--white);
border: 2.5px solid var(--ink);
border-radius: 20px;
padding: 16px 22px;
position: relative;
font: 700 32px/1.4 var(--serif);
}
.speech-bubble::after {
content: '';
position: absolute;
bottom: -16px;
left: 40px;
border: 8px solid transparent;
border-top-color: var(--ink);
}
/* 思想泡(圆形尾巴) */
.thought-bubble {
border-radius: 50% 50% 50% 50% / 40% 40% 60% 60%;
}
.thought-bubble::after {
width: 12px; height: 12px;
border-radius: 50%;
background: var(--ink);
border: none;
bottom: -20px;
}
/* 吼叫泡(锯齿边缘) */
.shout-bubble {
clip-path: polygon(
0% 20%, 5% 0%, 15% 15%, 25% 0%, 35% 10%,
50% 0%, 65% 10%, 75% 0%, 85% 15%, 95% 0%,
100% 20%, 95% 35%, 100% 50%, 95% 65%, 100% 80%,
95% 100%, 85% 85%, 75% 100%, 65% 90%, 50% 100%,
35% 90%, 25% 100%, 15% 85%, 5% 100%, 0% 80%,
5% 65%, 0% 50%, 5% 35%
);
background: var(--white);
padding: 28px 36px;
font: 900 38px/1.3 var(--serif);
}网点灰(Screentone)
漫画中灰色不是灰色,是网点:
.screentone {
background-image: radial-gradient(circle, var(--ink) 1px, transparent 1px);
background-size: 5px 5px;
opacity: 0.15;
}
/* 渐变网点 */
.screentone-gradient {
background-image: radial-gradient(circle, var(--ink) 1px, transparent 1px);
background-size: 5px 5px;
-webkit-mask-image: linear-gradient(to bottom, black, transparent);
mask-image: linear-gradient(to bottom, black, transparent);
opacity: 0.2;
}拟声词 / 效果字(Onomatopoeia)
大号倾斜的效果字,漫画的灵魂:
.sfx {
font: 900 80px/1 var(--serif);
color: var(--ink);
transform: rotate(-8deg) skewX(-5deg);
letter-spacing: -3px;
text-shadow: 3px 3px 0 var(--tone);
-webkit-text-stroke: 1px var(--ink);
}墨色浓淡(Ink Wash)
井上雄彦路线的核心——用 CSS gradient 模拟水墨:
.ink-wash {
background: linear-gradient(
135deg,
var(--ink) 0%,
rgba(26,26,26,0.6) 20%,
rgba(26,26,26,0.15) 50%,
transparent 70%
);
}3.2 排版原则
漫画排版不是文章排版。
- 文字竖排可选:日式漫画原本竖排阅读。关键文字可以用
writing-mode: vertical-rl制造漫画感 - 字号对比极端:标题 120px+、正文 32px、旁注 20px,比例 ≥ 6:1
- 粗体 = 重音:漫画中加粗是"说话声音变大了"
- 留白 = 沉默:大面积空白不是没画完,是刻意的停顿
- 黑色块 = 重压:纯黑区域制造压迫感和戏剧性
3.3 分格构图(按风格)
大友克洋 — 精密废墟
+------------------+--------+
| | 细节 |
| 全景大格 | 小格 |
| (技术架构图) +--------+
| | 数据 |
+--------+---------+ 小格 |
| 文字格 | 文字格 | |
+--------+---------+--------+特征:格子方正、线条精密、信息密度高
井上雄彦 — 水墨留白
+-------------------------+
| |
| 大面积留白 |
| 核心概念 |
| (墨色渐变背景) |
| |
+------------+------------+
| 窄长格 | 窄长格 |
| 观点 A | 观点 B |
+------------+------------+特征:大量留白、格子少、每格信息少但重
三浦建太郎 — 暗黑压迫
+--+--------------------+--+
|黑| |黑|
|边| 核心冲突大格 |边|
| | (大量纯黑+白字) | |
+--+---------+----------+--+
| 密集格 | 密集格 |
| (网点灰) | (纯黑白) |
+------------+-----------+特征:大面积纯黑、白字反转、压迫感
松本大洋 — 生猛粗线
+-------+
| 歪斜格 \
/ +--------+
+ 核心概念 |
| (大号粗字) |
+-----+ +------+
| 不规则 |
| 小格 |
+---------+特征:格子歪斜、边框粗细不均、充满动能
谷口治郎 — 静谧精描
+-------------------------+
| 精细场景描写格 |
| (长横格,电影宽银幕) |
+------------+------------+
| | |
| 正方格 | 正方格 |
| 细节 A | 细节 B |
| | |
+------------+------------+
| 底部叙事格 |
+-------------------------+特征:规整格子、宽银幕横格、安静均匀
3.4 原文配图处理
当步骤 2.3 收集到相关配图时:
- 优先使用原图:用
<img>标签直接引用原文图片 URL,放入漫画分格中 - 图片作为格子内容:原图占据一个完整分格,加 3px 边框,融入分格系统
- 黑白调性:
filter: grayscale(100%) contrast(1.2);保持漫画黑白感。如原图本身是黑白/线条风格,可保留原色 - 图片 CSS:
width: 100%; height: auto; object-fit: cover; display: block; - 图片不可用时(404 / CORS),跳过该图,不用占位图替代
步骤 4:写 CSS + HTML
所有 CSS 写入 {{CUSTOM_CSS}}。所有 HTML 写入 {{CONTENT_HTML}}。
CSS 从零写——class 名反映内容,不是通用名。
核心约束:
- 配色限于黑(
--ink)、白(--white/--bg)、灰(--tone)三值,偶尔用--ink-mid - 如需强调,用纯黑反白(白字黑底),不用彩色
- 边框统一 2.5-3px,模拟笔触
- 至少一个格子要"出血"(内容冲破常规边距)制造漫画感
替换变量:
| 变量 | 内容 |
|---|---|
{{CUSTOM_CSS}} | 全部 CSS(包括 :root 覆盖) |
{{CONTENT_HTML}} | 全部 HTML |
{{SOURCE_LINE}} | 内容来源(可选):<span class="info-source">来源文字</span>,无来源时空字符串 |
写入:
- 单张:
/tmp/ljg_cast_comic_{name}.html - 多张:
/tmp/ljg_cast_comic_{name}_{N}.html(N = 01, 02, ...)
步骤 5:自检
- [ ] 一眼看上去像漫画页面吗?如果看着像"加了边框的普通排版",重做
- [ ] 有没有至少 3 个分格?漫画没有分格就不是漫画
- [ ] 黑白对比是否强烈?有没有纯黑区域?
- [ ] 有没有至少 1 个漫画特有元素(集中线/速度线/对话泡/效果字/网点灰)?
- [ ] 格子大小是否有对比?(一个大格 + 几个小格,不是全部等大)
- [ ] 是否避免了彩色?灰色只用网点灰
--tone - [ ] 正文字号 ≥ 32px?标题 ≥ 72px?
- [ ] 有没有一个格子让人第一眼被吸进去?
- [ ] 是否避免了等分、对称、均匀间距?
步骤 6:截图
单张:
node assets/capture.js /tmp/ljg_cast_comic_{name}.html ~/Downloads/{name}.png 1080 800 fullpage多张:逐张截图,文件名带序号
node assets/capture.js /tmp/ljg_cast_comic_{name}_01.html ~/Downloads/{name}_01.png 1080 800 fullpage
node assets/capture.js /tmp/ljg_cast_comic_{name}_02.html ~/Downloads/{name}_02.png 1080 800 fullpage
# ... 逐张执行模具:信息图(-i)
核心信条
样式为思想而服务。
不存在"默认布局"。每一张信息图的视觉形式,都从这个思想的形状中生长出来。模板只提供画布材质(字体、颜色、噪点、署名)。构图、排版、布局——全部由你根据内容从零设计。
步骤 1:读取模板
Read assets/infograph_template.html
模板极简,只提供:
- 字体加载(DM Serif Display + DM Sans + KingHwa_OldSong)
- CSS 变量(
--bg,--green,--pink,--yellow,--ink,--ink-light,--white,--serif,--sans,--mono) - SVG 噪点纹理(自动叠加)
.colophon署名栏{{CUSTOM_CSS}}和{{CONTENT_HTML}}插槽
没有 header、没有 canvas、没有 utility class。 所有 CSS 都写在 {{CUSTOM_CSS}},所有 HTML 都写在 {{CONTENT_HTML}}。
步骤 2:理解思想
2.1 提取元信息
- 标题:≤ 15 字
- 副标题:一句话核心 ≤ 30 字
- 来源:内容的原始出处(作者、网站等),用于 footer 右侧(可选)
- REF 编码:
REF—{领域} / {主题}(写在画面上的位置由你决定)
2.2 三个维度
对内容做三个判断:
密度(决定画面的呼吸节奏):
| 密度 | 核心内容量 | 画面特征 |
|---|---|---|
| 稀 | ≤ 50 字可说清 | 一个巨大元素统治画面。留白 ≥ 60%。震撼来自克制。 |
| 中 | 50-200 字 | 有结构的布局。2-3 个主要区块。留白 30-50%。 |
| 密 | 200+ 字 | 多区块密集排布。标注、网格、分层。留白 ≤ 30%。实验室手册感。 |
结构(决定画面的几何):
| 结构 | 信号 | 视觉几何 |
|---|---|---|
| 单点 | 一个核心概念 | 一个锚点占据重心,其余退后 |
| 对比 | A vs B、旧 vs 新 | 分裂、对立、两极 |
| 层级 | 底层支撑上层 | 金字塔、阶梯、嵌套 |
| 流程 | 先后顺序 | 纵向瀑布、时间轴、管道 |
| 辐射 | 核心 + 衍生 | 中心放射、hub-spoke |
| 并列 | 多个并行概念 | 非对称网格(禁止等分) |
情绪(决定画面的温度):
| 情绪 | 排版风格 |
|---|---|
| 沉思的 | 大量留白,serif 主导,低对比 |
| 锐利的 | 强对比,大字,粉色弹点 |
| 温暖的 | 绿色为主,圆润布局,手写感 |
| 技术的 | mono 标注,网格底纹,数据密集 |
2.3 输出判断
密度:[稀/中/密]
结构:[单点/对比/层级/流程/辐射/并列]
情绪:[沉思/锐利/温暖/技术]
色调:[沉思/锐利/温暖/技术/科研/创意/商业/默认]
锚点:[画面中最大的那个元素是什么?放在哪里?]2.4 色调选择
根据内容主题选择最匹配的色调。色调决定三个核心变量,在 {{CUSTOM_CSS}} 中覆盖模板默认值。
| 色调 | --bg | --green(结构色) | --pink(弹点色) | 触发信号 |
|---|---|---|---|---|
| 沉思 | #F5F2ED | #C4B5A0 | #8B5E3C | 哲学、认知、本质、意义、存在 |
| 锐利 | #EDEDF0 | #6E6E80 | #D93025 | 批判、解构、争议、对立、辩论 |
| 温暖 | #F7F4EF | #C8B898 | #C17F4E | 人文、情感、生活、故事、成长 |
| 技术 | #F0F3F7 | #8EAAB8 | #1A936F | 架构、系统、算法、代码、工程 |
| 科研 | #F2F6F4 | #7DAE96 | #D68C45 | 论文、实验、数据、研究、发现 |
| 创意 | #F6F3F2 | #C0A89C | #B8432F | 艺术、设计、创作、美学、灵感 |
| 商业 | #F4F3F0 | #A8A498 | #2D6A4F | 商业、金融、市场、投资、战略 |
| 默认 | #F2F2F2 | #B8D8BE | #E91E63 | 无法归类时 |
密度修正(在基准色上微调):
- 稀:弹点色可提升 10-15% 饱和度。画面空旷,弹点需要更强存在感
- 中:使用基准值
- 密:结构色降低 10-15% 饱和度,避免密集排版时的视觉疲劳
选择原则:
- 扫描内容高频关键词和主题,匹配最贴近的色调
- 情绪维度和色调可以不同——「沉思的情绪 + 技术的色调」完全合法
- 宁可用默认也不要错配——错误的色调比无色调更糟
- 色调一旦选定,整张图统一使用
步骤 3:设计画面
3.1 材质系统
字体与墨色(所有色调共享):
| 变量 | 值 | 用途 |
|---|---|---|
--serif | DM Serif Display → KingHwa_OldSong | 标题、大字、金句 |
--sans | DM Sans → KingHwa_OldSong | 正文、标签 |
--mono | SF Mono | 数据标注、REF 编码 |
--ink | #2D2926 | 主文字色 |
--ink-light | #5C5350 | 次要文字 |
动态色调(由步骤 2.4 决定):
| 变量 | 角色 | 90/8/2 |
|---|---|---|
--bg | 画布底色 | 90% 中性面 |
--green | 结构色——色块、边框、分区 | 8% 结构 |
--pink | 弹点——整张图 1-2 处精确命中 | 2% 强调 |
覆盖方法——在 {{CUSTOM_CSS}} 最前面写:
:root {
--bg: #F0F3F7; /* 步骤 2.4 选定值 */
--green: #8EAAB8;
--pink: #1A936F;
}3.2 设计自由度
以下所有决策由你根据内容做出,没有默认值:
锚点位置 — 标题/核心元素可以在:
- 左上(传统)
- 正中(碑刻感)
- 右侧纵排(东亚美学)
- 底部(悬念揭示)
- 作为背景幽灵字(地形)
画面分割 — 可以是:
- 全画面不分割(稀密度)
- 横向分割(上下两个世界)
- 纵向分割(左右对比)
- 不规则分割(clip-path 斜切)
- 网格(密密度的实验室感)
文字大小 — 为手机端阅读优化(1080px 画布在手机上缩放约 2.8 倍):
- 最大元素和最小元素的比例 ≥ 10:1
- 可以用到 400px 的字(它不再是"文字",是"地形")
- 最小可读标注不低于 24px(手机上约 8.7px)
- 正文不低于 40px(手机上约 14.4px)
- 装饰性文字(REF 编码等不需细读的)最小 22px
色彩 — 90/8/2 法则(色值来自步骤 2.4 色调选择):
- 90% 中性色(
--bg+--white+--ink文字) - 8% 结构色(
--green一个区块) - 2% 弹点(
--pink一处精确命中)
3.3 密度指导
稀(≤ 50 字)
画面上 一个元素压倒一切。可能是:
- 一个 300-420px 的汉字/单词
- 一行金句横跨全宽
- 一个公式,周围是沉默
其余信息(词源、解释)以 24-28px 安静地待在角落或底部。不争夺注意力。
参考构图:
┌─────────────────────────┐
│ ref-code 22px │
│ │
│ │
│ 坐 │
│ 400px serif │
│ │
│ subtitle 36px │
│ │
│ ─── quote ─── │
│ 44px serif │
│ │
│ [colophon] │
└─────────────────────────┘中(50-200 字)
2-3 个区块,有主有次。锚点元素 120-180px,正文 40-44px,副标题 34-40px。
关键:不要把区块均匀排列。一个大的占 60%,其余挤在一起。或者一个全宽条带打断节奏。
参考构图(仅供启发,不是固定模板):
┌─────────────────────────┐
│ 标题 140px │
│ subtitle 36px │
├───────────┬─────────────│
│ │ │
│ 核心解释 │ 词源/数据 │
│ 40px │ 32px │
│ 2fr │ 1fr │
│ │ │
├───────────┴─────────────│
│ ██████ 深色全宽条带 ██████│
│ 核心公式 / 一句话 44px │
├─────────────────────────│
│ │
│ 金句 48px serif │
│ │
│ [colophon] │
└─────────────────────────┘密(200+ 字)
画面密集但有序。多个小区块。标注层、网格线可见。实验室手册感。
关键:密不等于挤。密是信息多但每条信息各就各位。用线条、编号、色块区分层级。正文 36-40px,标注 28-32px,标题 80-108px。
参考构图:
┌──────────┬──────────────┐
│ 标题 84px│ ref-code 22px│
│ sub 34px │ ×数据 28px │
├──────────┴──────────────│
│ ┌────┐ ┌────┐ ┌──────┐ │
│ │ 01 │ │ 02 │ │ │ │
│ │概念 │ │概念 │ │ 03 │ │
│ │36px│ │36px│ │ 大概念 │ │
│ └────┘ └────┘ │ 40px │ │
│ └──────┘ │
├─────────────────────────│
│ 标注区 · 28px mono │
│ 引用 · 数据来源 · 28px │
│ [colophon] │
└─────────────────────────┘3.4 反死亡清单
| 如果你发现自己在做这个... | 停下来 |
|---|---|
写了 .header { padding: 56px } | 你在用旧模板思维。从内容开始,不是从 header 开始。 |
| 每个区块都是白色背景 | 至少一个用 --green 或 --ink |
| 三列等宽 | 禁止。2fr 1fr、1fr 340px、一大两小。 |
| 标题居中 | 除非密度=稀且结构=单点。否则左对齐或非传统位置。 |
| 每张图都有"公式条带" | 这不是必须的。有些思想没有公式。 |
| 弹点色用了 3 处以上 | 回到 2 处。弹点是子弹。 |
| 没有一个元素超过 100px | 找到值得放大的那个。 |
| 所有文字都在 30-44px | 你没有制造张力。需要 ≥ 10:1 比例。 |
| 正文字号小于 36px | 手机上会不可读。最小正文 36px,最小标注 24px。 |
| 区块之间间距都一样 | 有意识地做疏密交替。 |
用 max-width 压短了本该一行放下的文字 | 画布 1080px。一句话能放一行就放一行。只在正文段落用 max-width 控制行宽(≤ 56ch),标题和金句不要限宽——让它自然呼吸到该停的地方。 |
步骤 4:写 CSS + HTML
所有 CSS 写入 {{CUSTOM_CSS}}。所有 HTML 写入 {{CONTENT_HTML}}。
CSS 从零写——不要复制之前任何版本的 class 名称或结构。每张图的 class 名应该反映这张图的内容(.etymology、.core-split、.timeline),不是通用名(.section、.panel、.label)。
替换变量:
| 变量 | 内容 |
|---|---|
{{CUSTOM_CSS}} | 这张图的全部 CSS |
{{CONTENT_HTML}} | 这张图的全部 HTML |
{{SOURCE_LINE}} | 内容来源(可选):<span class="info-source">来源文字</span>,无来源时空字符串 |
写入:/tmp/ljg_cast_infograph_{name}.html
步骤 5:自检
唯一不变的检查:
- [ ] 这张图的视觉形式,是从内容的形状中生长出来的吗?
- [ ] 如果换一段完全不同的内容,这个布局还说得通吗?如果是——你做的是模板,不是设计。
- [ ] 最大元素和最小元素的比例 ≥ 10:1?
- [ ] 弹点色 ≤ 2 处?
- [ ] 色调是否与内容主题匹配?换个主题,这组颜色还合适吗?
- [ ] 有没有一个元素让人第一眼就被抓住?
- [ ] 留白是有意为之,还是剩下来的?
- [ ] 如果告诉别人"这是 AI 做的",他们会立刻相信吗?如果会——重做。
- [ ] 手机端阅读检查:正文 ≥36px?标注 ≥24px?行高 ≥1.6?在手机上缩放 2.8 倍后文字仍可舒适阅读?
步骤 6:截图
node assets/capture.js /tmp/ljg_cast_infograph_{name}.html ~/Downloads/{name}.png 1080 800 fullpage模具:长图(-l / 默认)
步骤 1:读取模板
Read assets/long_template.html
步骤 2:内容预处理
- 识别标题行(
#/##/###开头,或独立短行) - 识别引用块(
>开头) - 识别加粗(
**text**) - 识别金句:独立成段的短句(通常 < 25 字),承载核心洞察,用
.highlight渲染 - 按空行分割为段落列表
- 不做切分:所有内容放在一张卡内
步骤 2.5:色调感知
根据内容气质选择一组背景底色 + 强调色,让每张卡片和内容产生共振:
| 内容气质 | {{BG_COLOR}} | {{ACCENT_COLOR}} | 触发信号 |
|---|---|---|---|
| 思辨/哲学 | #FAF8F4 | #7C6853 | 认知、思维、本质、意义、哲学 |
| 技术/工程 | #F5F7FA | #3D5A80 | 架构、模型、算法、系统、代码 |
| 文学/叙事 | #FBF9F1 | #6B4E3D | 故事、人物、写作、文字、诗 |
| 科学/研究 | #F4F8F6 | #2D6A4F | 实验、数据、发现、论文、研究 |
| 默认 | #FAFAF8 | #4A4A4A | 无法归类时 |
判断依据:扫描内容中的高频关键词和主题,匹配最贴近的一组。不需要精确——宁可用默认也不要错配。
步骤 3:格式化为 HTML
基础元素:
- 普通段落 →
<p>文本</p> - 章节标题(##/### 级别) →
<h2>标题</h2> - 引用 →
<blockquote><p>引用</p></blockquote> - 加粗 →
<strong>文本</strong> - 列表 →
<ul><li>...</li></ul>
金句(独立成段的核心洞察短句,视觉突出):
<p class="highlight">金句文本</p>判断标准:独立成段、< 25 字、承载关键洞察。用 .highlight 而非 <p><strong>。
重点提示(绿底高亮,用于关键问句/提示):
<p class="prompt">重点提示文本</p>判断标准:需要让读者停下来、用力看的"提示性"内容。典型场景:
- Q&A 中的 Question(Answer 用普通段落)
- 关键追问("那为什么 X 不是 Y?")
- 阅读引导提示("读到这里,请先停下想一想")
- 召唤行动的指令句
视觉上以淡绿底色 + 深绿左边线呈现,与 .highlight(左边线+大字号)形成区分:
.highlight= 作者下的金句结论.prompt= 抛给读者的问题/提示
一张卡内 .prompt 不超过 5 处,避免高亮泛滥。
首字下沉(第一个正文段落): 第一个普通段落(非 .subtitle、.highlight、.item)添加 dropcap 类:
<p class="dropcap">段落正文...</p>仅首个正文段落使用,营造经典编辑排版的开篇仪式感。
条目组(有标题+正文的并列条目):
<div class="item">
<p class="label">条目标题</p>
<p>条目正文</p>
</div>副标题标签:
<p class="subtitle">标签文字</p>分割线(章节之间):
<div class="divider"></div>步骤 4:渲染模板
替换模板变量:
| 变量 | 规则 |
|---|---|
{{BG_COLOR}} | 步骤 2.5 确定的背景底色 |
{{ACCENT_COLOR}} | 步骤 2.5 确定的强调色 |
{{TITLE_BLOCK}} | 有标题时:<div class="title-area"><h1>标题</h1></div>;无标题时:空字符串 |
{{BODY_HTML}} | 步骤 3 生成的全部 HTML |
{{SOURCE_LINE}} | 内容来源(可选):<span class="info-source">来源文字</span>,无来源时空字符串 |
写入:/tmp/ljg_cast_long_{name}.html
步骤 5:截图
node assets/capture.js /tmp/ljg_cast_long_{name}.html ~/Downloads/{name}.png 1080 800 fullpage模具:多卡(-c)
步骤 1:读取模板
Read assets/poster_template.html
步骤 1.5:色调感知
与长图模具共享同一套色调系统。根据内容气质选择 {{BG_COLOR}} 和 {{ACCENT_COLOR}}:
| 内容气质 | {{BG_COLOR}} | {{ACCENT_COLOR}} | 触发信号 |
|---|---|---|---|
| 思辨/哲学 | #FAF8F4 | #7C6853 | 认知、思维、本质、意义、哲学 |
| 技术/工程 | #F5F7FA | #3D5A80 | 架构、模型、算法、系统、代码 |
| 文学/叙事 | #FBF9F1 | #6B4E3D | 故事、人物、写作、文字、诗 |
| 科学/研究 | #F4F8F6 | #2D6A4F | 实验、数据、发现、论文、研究 |
| 默认 | #FAFAF8 | #4A4A4A | 无法归类时 |
步骤 2:内容预处理
- 识别标题行(
#/##/###开头,或独立短行) - 识别引用块(
>开头) - 识别加粗(
**text**) - 识别金句:独立成段的短句(通常 < 25 字),承载核心洞察,用
.highlight渲染 - 按空行分割为段落列表
步骤 3:计算视觉重量
模板在 1080x1440 全分辨率渲染,正文 36px,行高 1.7。
- 普通段落:字符数 × 1.4
- 标题行(h1 首卡 84px):字符数 × 6.0
- 金句(
.highlight40px + 左边框 + 上下留白):字符数 × 3.0 .item条目组(label + 正文):字符数 × 1.8- 引用块:字符数 × 1.7
- 分割线(divider):固定 60 权重
- 代码块:字符数 × 2.2
- Running title(续页头部):固定 70 权重
步骤 4:贪心切分
- 阈值:每卡约 380 字符等价视觉重量
- 逐段累加,超过阈值时在当前段之前切分
- 切分规则:
- 绝不在句子中间切
- 优先在段落/条目/章节边界切
- 标题不落单(必须跟至少一个内容元素在同一卡)
- 超长单段在句号处强制切
- 一个章节(h2 + 3 items)通常刚好一卡
特殊情况:
- 只有一张卡:不显示页码
- 多张卡:显示
1 / N格式页码
步骤 5:格式化为 HTML
基础元素:
- 普通段落 →
<p>文本</p> - 章节标题(##/### 级别) →
<h2>标题</h2> - 引用 →
<blockquote><p>引用</p></blockquote> - 加粗 →
<strong>文本</strong> - 列表 →
<ul><li>...</li></ul>
金句(独立成段的核心洞察短句,视觉突出):
<p class="highlight">金句文本</p>判断标准:独立成段、< 25 字、承载关键洞察。用 .highlight 而非 <p><strong>。
条目组(有标题+正文的并列条目):
<div class="item">
<p class="label">条目标题</p>
<p>条目正文</p>
</div>副标题标签:
<p class="subtitle">标签文字</p>分割线(章节之间):
<div class="divider"></div>步骤 6:渲染模板
对每张卡片,替换模板变量:
| 变量 | 规则 |
|---|---|
{{BG_COLOR}} | 步骤 1.5 确定的背景底色 |
{{ACCENT_COLOR}} | 步骤 1.5 确定的强调色 |
{{HEADER_BLOCK}} | 续页卡:<div class="header"><span class="running-title">文章标题</span></div>;首卡或单卡:空字符串 |
{{TITLE_BLOCK}} | 首卡有标题时:<div class="title-area"><h1>标题</h1></div>;续页卡或无标题时:空字符串 |
{{BODY_HTML}} | 步骤 5 生成的 HTML |
{{SOURCE_LINE}} | 内容来源(可选):<span class="info-source">来源文字</span>,无来源时空字符串 |
{{PAGE_INFO}} | 多卡时 1 / 3,单卡时空字符串 |
结尾标记:仅在最后一张卡的 {{BODY_HTML}} 末尾追加 <p style="text-align:right;font-size:16px;color:#ACACB0;margin-top:40px;">∎</p>。非末页不加。
写入:/tmp/ljg_cast_poster_{name}_{N}.html
步骤 7:截图
node assets/capture.js /tmp/ljg_cast_poster_{name}_{N}.html ~/Downloads/{name}_{N}.png 1080 1440多张卡片可并行截图。
交付时报告卡片数量 + 每张摘要(前 30 字)。
模具:视觉笔记(-v)
灵魂
把一个概念铸成一份编辑式的图文档案。
读者翻它,像翻一本期刊专题——从一个真问题(专题刊头)→ 失败的尝试(便签批注、档案标签)→ 一句"等等——"的转折(跨栏大标题)→ 看见那个东西(Hero 对开页)→ 名字(Closing 名牌)。
不是博物馆陈列,是杂志栏目。不是教科书定义,是探案档案。视觉与叙事一起负责——让读者自己经历"卡住—走不通—翻过去—看见了"的弧线,文字克制,不点题。
五条公理(不可削)
每张图都必须满足。削掉任何一条,灵魂就崩。
公理 1 · 有真问题在前
每张图必须以一个具体的、可触摸的、卡住的问题开始。
不是「什么是 X」(教科书式),是「当时的人们用 A、B、C 都不够,因为……」。
问题必须有裂缝,裂缝必须可感——读者自己能感到"嗯,这确实卡住了"。
削掉它:失去叙事的发动机。退化为时间线。
公理 2 · 必须有失败
路径中必须包含至少一次失败(或走偏、半对了)。线性推导("由此可得")会杀掉张力。
失败让读者明白:不是只有这一条路,是别的路都走不通。
削掉它:变成定理证明。读者点头但没占有。
公理 3 · 顿悟在前、命名在后
读者必须先「看到那个东西」(顿悟时刻),再被告诉"这个东西叫……"。
先抛出概念名再解释,等于剧透。命名必须是路径的终点,不是起点。
→ 标题不能出现概念名(「打不开的盒子」可以,「黑箱理论」不可以)。
削掉它:剧透杀死惊讶感。读者获得标签而非视力。
公理 4 · 「现在」视角,非「上帝」视角
每一站,时间锚点是「他/她在那一刻能看到什么」,不是「我们站在 100 年后回望」。
后世评价、连锁影响、与其他概念的对比——都是路径外的事,不进画面。
削掉它:读者重新成为博物馆游客。在场感破碎。
公理 5 · 文字克制,不点题
让叙事张力自己产生发明感,不靠元自指话术。
禁止句式:
- ✗「你以为你刚才学到了一个概念。其实你刚才不得不发明了它。」
- ✗「你重新分娩了它」「你亲身发明了它」「你见证了它的诞生」之类自指
- ✗ 任何「你 + 发明 / 创造 / 重新 X + 它」结构
允许的余韵(如果一定要 closing 加一句):
- ✓ 诗意而非元论:"于是,你看见了山。" / "门一旦开过一次,就不会再合上。"
- ✓ 完全不要 wink,让 closing 在 mega-name + byline + closing-body 后干净结束
读者的发明感来自叙事张力(失败可见 / 转折顿挫 / 视觉惊讶),不来自被告知"你正在发明"。告诉就破。
每一层叙事可以留缝隙(未完成句、开放设问、暗示性图示),但不能用元话语点题。
削掉它:自我陶醉的话术覆盖了真正的发明感。
公理 6 · 中文母语表达
文字必须像中文人说话,不像英文翻译过来的中文。
禁忌(典型翻译腔,命中即重写):
- ✗ 名词化的抽象("共相被肢解"、"现象学碎片化"、"普遍现象")
- ✗ 英式对仗("两个尺度,一个舞台"、"输入连续,输出不连续")
- ✗ 工业短句堆砌("球瞬间到位。地形慢慢变。两个尺度,一个舞台。")
- ✗ 形容词驱动("突然的、惊人的、深刻的、根本的")
- ✗ 被动滥用("被推到死"、"被肢解"、"被发现")
- ✗ "是...的"句式("这是值得思考的"——直接说"值得想想")
- ✗ "在...的背景下"、"基于...的考虑"、"对于...而言"——长定语后置全删
- ✗ 抽象主语("该方法"、"这种思路"、"上述论点"——换成具体的"它"或省略)
- ✗ "进行 + 名词"结构("进行讨论"→"聊"、"进行优化"→"调"、"进行处理"→"处理")
- ✗ "随着 X 的发展"、"在 Y 的过程中"、"通过对 Z 的分析"——学术腔模板
- ✗ 长复合句——超过 3 个逗号的句子,砍成短句
该用:
- ✓ 动词驱动("球稳坐"、"谷塌了"、"它跳了"、"推到塌")
- ✓ 具体物件("水"、"球"、"谷",而不是 "状态"、"系统"、"势函数"——除非确有必要)
- ✓ 口语节奏("它就这么……跳了"、"看着是这样,其实不是")
- ✓ 中文自然句式:长句不怕长,短句不怕短,怕的是机械对仗
逐句检查方法:每句默念出声。听起来像翻译的、像 wikipedia 中文版的、像论文摘要的——重写。
整卡检查(生成 HTML 后必走): 1. 把整张卡的中文文字全部扫一遍:h1 / deck / lead / body / head / pull-quote / closing-body / verdict / scribble / formula / caption 2. 逐句默念。听起来不像中文人说话的,重写 3. 重写不动的,把那一句拆成 2-3 个短句 4. 整段读完后回看:节奏是否有起伏?句长是否有变化?纯短句堆砌也是翻译腔(工业短句堆砌) 5. 任何一处英文术语不可避免(人名、产品名)—— 接受,但前后中文必须 native
对照:
- ✗ 「输入连续,输出不连续」 → ✓「慢慢加热,到 100 度突然就开了」
- ✗ 「共相被肢解」 → ✓「各管各的,谁也不挨谁」
- ✗ 「状态稳坐在谷里。谷在变。当谷消失,跳变发生。」 → ✓「球稳稳待在谷里。谷慢慢变。谷一塌,球就跳。」
- ✗ 「该方法在多个维度上都展现了一定优势」 → ✓「它在好几处都比老办法强」
- ✗ 「在 AI 工程实践中,并行处理是否合适?」 → ✓「并行海投错在哪?」
- ✗ 「这是一个值得我们深入思考的问题」 → ✓「这事得想想」
- ✗ 「不能预测什么时候发生,只能告诉你它的形态」 → 这句可以保留(Thom 原话),但改写时不要模仿这种 prescriptive 句式
削掉它:文字成了 wikipedia 中文版的腔调,读完不留痕迹。
---
步骤 1:读取模板
Read assets/sketchnote_template.html
模板提供(仅"基础设施"):
- 字体加载:
Noto Sans SC/Noto Serif SC/Caveat/JetBrains Mono - CSS 变量基底:基底色 / 字族
.colophon署名 + 来源{{CUSTOM_CSS}}{{CONTENT_HTML}}{{LOGO}}{{SOURCE_LINE}}插槽
具体的杂志骨架 layout 由本模式现场设计——模板只给颜料盒和画框。
注:旧版的 .station--start/--failed/--pivot/--insight/--name 已废弃——本模式用六种 layout 模具:.feature/.note/.archive/.cross/.hero/.closing。橙色弯曲箭头流程也已废弃。
---
步骤 2:寻找叙事弧线
读完原始内容后,先沉默 30 秒问自己一个问题:
这个概念是从哪个真问题里长出来的?如果回到它被发明前的那一刻,世界缺了什么、谁卡在哪里?
如果回答不了,回去重读直到能答。否则后面的图都是浮的。
2.1 找问题(站点 1:起点 / Feature)
不是抽象的「问题领域」,是一个具体的、可触摸的卡住的瞬间。
举例(黑箱理论):
- ✗「20 世纪复杂系统研究遇到的认识论困境」(教科书腔)
- ✓「缴获的电子设备里可能有炸药——开了就炸。活体的大脑——开了就死。雷达系统几千个反馈元件——开了你也理不清。」
裂缝感来自具体的物理约束。
输出:
- head:卡住的描述(Serif 大标题 56-60px)
- lead:italic 引言段(一句简洁的领题,红色左边线)
- body:2-4 个具体例子(drop cap 第一字下沉)
- ask:开放设问(手写体 Caveat,红色)
2.2 找失败(站点 2、3:Note 和 Archive)
至少一次,理想 1-2 次。每次失败要呈现:
- 当时的工具(已有的方法、本能反应)
- 为什么这个工具不够(具体的失败原因,不是泛泛"不行")
两个失败站点必须形态不同——一个 note(便签批注),一个 archive(档案标签),节奏才出来。
举例(黑箱理论):
- 站点 2 note(拆):还原论本能 → 活体不能拆 / 元件多到画图无意义
- 站点 3 archive(跳过):行为主义跳过内部 → "不管"不等于"回答"
输出:
- kicker:编号 + "第 N 次尝试"
- head:尝试的简短动作名("那就拆开看" / "那就别看里面了")
- body:思路 + 失败原因(紧凑 2-3 段)
- 失败标记:站点 2 用
.strike红笔删除线(划在关键词如「解析解」上)+ scribble 红笔批注;站点 3 用黑色.stamp印章(含 ✕ 大字)+ verdict 红色 italic 结案语
2.3 找转折(站点 4:Cross-page Mega)
转折不是"换一个角度",是反向使用约束。
举例:
- 不是"我打不开它"(被动)
- 而是"我故意不开它"(主动)
转折点必须有视角翻面的顿挫感。
mega 字(重要 · 不可写死):
mega 是这一刻视角翻转的爆点。它必须由具体转折决定,不要默认套"等等——"。
| 候选类型 | 例 | 适用场景 |
|---|---|---|
| 停顿词 | 等等—— / 不对 / 慢着 | 当转折是"先暂停,反思" |
| 动作词 | 翻过来 / 反着看 / 退一步 | 当转折是具体的姿态变化 |
| 视角词 | 看地形 / 别盯球 / 朝里看 | 当转折是看的对象变了 |
| 单字爆点 | 翻 / 山 / 反 | 当转折可以浓缩到一个核心字 |
| 概念词 | 几何—— / 地形—— | 当转折引入一个新词汇(但不能是最终命名) |
禁忌:
- ✗ 默认套"等等——"——这是 AI 偷懒的剧本套路
- ✗ mega 字与转折内容脱节(比如内容讲"换看的对象",mega 写"等等",对不上)
- ✗ mega 字泄露最终命名(比如概念是"突变理论",mega 不能写"突变")
检查方法:mega 字读完,读者应该感到这一刻的视角变化。如果它换成"等等"也成立、换成"不对"也成立、换成什么都成立——说明它没紧扣转折,得重写。
输出:
- mega:转折爆点(Serif 200px)+ amber 高亮 highlight 段(核心字 1-3 个)
- body:反向陈述(左栏)
- visual:反向姿态的小图(右栏,球滚山谷之类的简单类比)+ caption
2.4 找顿悟(站点 5:Hero Spread)
顿悟站点呈现转折姿态实施后意外发现的核心洞察。
举例:
- 故意不开 → 只看输入输出 → 竟然能完全刻画系统!
⚠ 站点 5 不能出现概念名!
输出:
- head:顿悟的姿态名("在状态空间画一座山" / "只看输入输出"),Serif 50px
- pull-quote:核心句子大引号(蓝色 Serif italic 38px,浮动
\201C大引号) - body:洞察的具体表述(drop cap)
- visual:可视化那个洞察(大幅 hero 图,左 7 分占满)+ caption
2.5 找命名(站点 6:Closing Page)
读者已经看到了那个东西。现在告诉他名字。
输出:
- approach:「这种 X 的研究对象,叫——」(italic 26px Serif)
- mega-name:大字呈现(中文 Serif 144px + 英文 Sans 38px)
- byline:Mono uppercase 14px,上下细黑线,间用
·分隔 - 格式:
<人名> · <年份> · <机构> · <文献> - closing-body:它打开了什么(2-3 段 Serif 22px)
- epilogue(可选):诗意余韵,不元自指
- ✓「于是,你看见了山。」
- ✗「你以为你刚才学到了一个概念……」
---
步骤 3:设计画面(杂志 × 探案档案风)
3.1 六种 layout 模具
每个站点必须用不同的 layout,让节奏出现:
| 站点 | layout 模具 | class | 视觉特征 |
|---|---|---|---|
| 1 起点 | feature spread | .feature | 米色底 + grid 6fr/6fr,左大图 / 右文字,kicker + Serif 大标题 + italic lead + drop cap body |
| 2 失败 | margin note | .note | 双栏 grid(左 sidekick 涂鸦区 + 右 540px 便签纸),便签微旋 0.5deg + 顶部虚线穿孔 + 红笔删除线 + scribble + footnote ¹;左侧放 SVG 速写或手写公式 |
| 3 失败 | archive label | .archive | 全宽 + 黑色印章 stamp(左 168px,含 ✕ 大字)+ 右栏 body + 网格图 + verdict 红色 italic |
| 4 转折 | cross-page mega | .cross | 全宽 + Serif 200px 由内容决定的转折爆点 + amber 高亮 + 二栏(文字 / 图) |
| 5 顿悟 | hero spread | .hero | 蓝色顶边 4px + grid 7fr/5fr,大图(左)+ 右栏 pull quote + drop cap body |
| 6 命名 | closing page | .closing | 米色底 + 双线顶边 + 中心对称 + 巨大 Serif 名 + byline 上下细线 + epilogue |
节奏(极重要):开阔(feature) → 紧(note 错位) → 紧(archive 横长) → 爆(cross 200px) → 开阔(hero) → 静(closing 中心对称)
不是均匀展开。是有呼吸的——开阔与紧凑交替,转折时大爆炸,最后回到中心对称。
3.2 字族对比(必须四种同时使用)
- Serif(Noto Serif SC):杂志主标题、命名 mega-name、引言 lead、引文 pull-quote
- Sans(Noto Sans SC):正文 body-sans、failed station head、kicker 后文字
- Mono(JetBrains Mono / SF Mono):编号 num、kicker label、byline、footnote 编号、stamp
- Hand(Caveat / 楷体):手写批注 scribble、ask 设问、caption
字族对比是杂志感的核心。任一缺席,视觉就回到 AI 单一字族的均质感。
3.3 装饰元素清单(按需取用)
| 元素 | 用法 | 实现 |
|---|---|---|
| kicker | 站点序号 + 类型小字 | Mono uppercase 13px + 黑底白字 num 方块 + 36px 短横线 rule |
| drop cap | body 第一段第一字 | ::first-letter float left, 96px Serif |
| lead | feature 引言 | italic 23px Serif + 红色左边线 2px |
| pull quote | hero 关键句 | italic 38px Serif + 蓝色边线 4px + 浮动 \201C 大引号 100px |
| strike | failed body 删除关键词 | text-decoration: line-through 红色 2.5px |
| scribble | note 红笔批注 | Caveat 24px + 6deg 旋转 + 红色 + 虚线红边框(注意:现在挂在 .note .paper 内,不挂在 .note 外层) |
| sidekick | note 左侧涂鸦区 | grid 左栏,可放 SVG 速写 / 手写公式(.formula)/ 箭头评注(.arrow) |
| stamp | archive 失败印章 | 黑底白字 12px Mono + ✕ Serif 64px |
| verdict | archive 结案语 | italic 19px Serif 红色 + 上虚线分隔 |
| footnote | note 脚注 | Mono 13px + ¹ 上标 + 上虚线分隔 |
| byline | closing 出处 | Mono 14px uppercase + letter-spacing 0.18em + 上下细黑线 |
| mega | cross 大字(由内容决定的转折爆点,禁止默认套「等等」) | Serif 200px + amber 渐变高亮 highlight 段(核心字 1-3 个) |
| epilogue | closing 余韵 | italic 26px Serif + — 红色破折号前缀 |
不要全用。但 kicker、drop cap、byline、stamp 是结构必须项。
3.3.1 sidekick 区怎么填(仅 note 模具)
note 模具是双栏 grid。左侧 sidekick 不能空——空栏会让"紧凑铺垫"变成"画廊空荡"。
三种填法(按内容选择,可组合,但不要堆满):
| 方式 | 何时用 | class | 实现 |
|---|---|---|---|
| SVG 速写 | 失败的本质能用 1-2 个图形姿态画出来 | (直接 <svg>) | viewBox 280×220 简笔画 + 红笔批注,纯 ASCII-style 轮廓 |
| 手写公式 | 失败的本质能用 1 行文字关系压成 | .formula | Caveat 22px + 虚线左边线 + 微旋 -1deg,最多 3 行 |
| 箭头评注 | 单点强调("整段塞进去 →"、"一气呵成") | .arrow | Caveat 26px + 微旋 6deg + 红色 |
约束:
- sidekick 是 指向便签的注脚,不抢戏。便签是主角
- 涂鸦感优先于精致——歪斜、虚线、留缝隙
- 颜色克制:黑灰 + 红一点足矣,不要用蓝/amber
- 内容密度低:宁可一个公式 + 一行 caption,不要塞三个图
不知道选哪个时:失败的关键词如果能压成等式(A ≠ B、X → Y),用 formula;如果能用一笔画出,用 svg;如果只是想加个注脚,用 arrow。
3.4 颜色系统(精简而克制)
| 角色 | 变量 | hex | 用法 |
|---|---|---|---|
| 暖米白底 | --bg | #FAF7EF | 主背景 |
| 米色卡 | --paper | #F5F1E5 | feature/closing 底 |
| 墨黑标题 | --ink-strong | #0F0F0F | 重要文字(避免 #000 纯黑) |
| 正文黑 | --ink | #1F1F1F | body |
| 灰文 | --ink-light | #6B6B6B | kicker、caption |
| 红 | --red | #B23A2C | 错误、批注、强调 |
| 蓝深 | --blue-deep | #3D5A80 | 顿悟视觉 |
| amber | --amber | #BB8A2B | 转折提示 |
| amber-soft | --amber-soft | #D7A85A | 高亮底色 |
≤ 4 主色(红 + 蓝 + amber + 中性)。不堆色。禁止 #000 纯黑。
3.5 漫画分镜式的节奏
借鉴漫画排版:留白不是均匀的——它跟着叙事节奏分配。漫画里关键瞬间放大留白,过渡部分紧凑收缩。AI 默认的"画廊均匀留白"刚好相反:每节都给同样的呼吸空间,结果整张图像 PowerPoint 模板,没有节奏。
节奏分级:
| 节 | 弧线角色 | 留白尺度 | 上下 padding | margin-top |
|---|---|---|---|---|
| feature | 开篇广角 | 中 | 38 / 44 | — |
| note | 紧凑铺垫 | 小 | 22 / 22 | 24 |
| archive | 紧凑铺垫 | 小 | 22 / 24 | 24 |
| cross | 转场爆点 | 大(mega 撑场) | 64 / 60 | 30 |
| hero | 顿悟特写 | 中偏小 | 52 / 48 | 32 |
| closing | 终格静默 | 大(让命名沉淀) | 60 / 64 | 32 |
原则:
- 节与节之间用 border-top + margin-top 区分,margin-top 必须因角色不同(不是统一 64px)
- block 内部 padding 全部偏紧——head-margin 12/16,body p+p 8,ask 14
- 留白集中在 cross + closing 两个"放大"瞬间,其他从快节奏滚过去
- magazine-head 也要紧凑:44 / 30,不要让大标题独占一屏
禁忌:6 个节都是 60-80px margin-top 的均匀间距。如果 self-check 时发现节奏图是直线,回去重排——这是画廊陈列,不是漫画分镜。
---
步骤 4:写 CSS + HTML
把全部 CSS 写入 {{CUSTOM_CSS}},全部 HTML 写入 {{CONTENT_HTML}}。
4.1 CSS 骨架(完整复用版)
:root {
--bg: #FAF7EF;
--paper: #F5F1E5;
--ink: #1F1F1F;
--ink-strong: #0F0F0F;
--ink-light: #6B6B6B;
--ink-fade: #9A958C;
--rule: rgba(15,15,15,0.16);
--red: #B23A2C;
--blue-deep: #3D5A80;
--amber: #BB8A2B;
--amber-soft: #D7A85A;
--sans: 'Noto Sans SC', 'PingFang SC', system-ui, sans-serif;
--serif: 'Noto Serif SC', 'Songti SC', serif;
--hand: 'Caveat', '楷体', 'STKaiti', cursive;
--mono: 'JetBrains Mono', 'SF Mono', monospace;
}
/* magazine head */
.magazine-head {
padding: 44px 64px 30px;
border-bottom: 2px solid var(--ink-strong);
position: relative;
}
.magazine-head::after {
content: ''; position: absolute;
left: 64px; right: 64px; bottom: -8px;
height: 1px; background: var(--ink-strong);
}
.top-bar {
display: flex; justify-content: space-between; align-items: center;
font: 600 12px/1 var(--mono);
color: var(--ink-light);
letter-spacing: 0.22em;
text-transform: uppercase;
margin-bottom: 24px; padding-bottom: 12px;
border-bottom: 1px solid var(--rule);
}
.top-bar .left { display: flex; align-items: center; gap: 18px; }
.top-bar .badge { background: var(--ink-strong); color: var(--bg); padding: 5px 11px; letter-spacing: 0.18em; }
.magazine-head h1 {
font: 900 110px/0.92 var(--serif);
color: var(--ink-strong);
letter-spacing: -0.03em;
margin-bottom: 24px;
}
.magazine-head .deck {
font: italic 400 26px/1.45 var(--serif);
color: var(--ink-light);
max-width: 820px;
border-left: 3px solid var(--ink-strong);
padding-left: 20px;
}
/* shared */
.kicker {
font: 600 13px/1 var(--mono);
color: var(--ink-light);
text-transform: uppercase;
letter-spacing: 0.22em;
display: inline-flex; align-items: center; gap: 12px;
}
.kicker .num {
background: var(--ink-strong); color: var(--bg);
padding: 5px 10px 4px; font-weight: 700; letter-spacing: 0.12em;
}
.kicker .rule { width: 36px; height: 1px; background: var(--ink-strong); }
.head-serif { font: 700 56px/1.05 var(--serif); margin: 12px 0 16px; letter-spacing: -0.018em; color: var(--ink-strong); }
.head-sans { font: 900 40px/1.12 var(--sans); margin: 10px 0 12px; letter-spacing: -0.012em; color: var(--ink-strong); }
.body-serif p { font: 400 22px/1.6 var(--serif); }
.body-serif p + p { margin-top: 8px; }
.body-sans p { font: 400 21px/1.6 var(--sans); }
.body-sans p + p { margin-top: 6px; }
.body em { font-style: normal; font-weight: 700; color: var(--ink-strong); }
.ask { font: 600 30px/1.3 var(--hand); color: var(--red); margin-top: 14px; display: block; }
.drop-cap > p:first-child::first-letter {
float: left;
font: 700 84px/0.84 var(--serif);
color: var(--ink-strong);
margin: 4px 12px -4px 0;
}
/* feature · 开篇广角,留白中等 */
.feature {
padding: 38px 64px 44px;
background: var(--paper);
border-bottom: 1px solid var(--rule);
display: grid;
grid-template-columns: 6fr 6fr;
gap: 32px;
align-items: start;
}
.feature .lead {
font: italic 400 22px/1.45 var(--serif);
margin-bottom: 16px;
padding: 0 0 0 18px;
border-left: 2px solid var(--red);
}
/* note · 紧凑铺垫,双栏 grid(左 sidekick + 右便签纸) */
.note {
margin: 24px 64px 0;
display: grid;
grid-template-columns: 1fr 540px;
gap: 32px;
align-items: start;
padding-top: 6px;
}
/* 左侧 sidekick:涂鸦/公式/箭头 */
.note .sidekick {
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: center;
gap: 14px;
padding: 8px 4px 8px 0;
}
.note .sidekick svg {
display: block;
max-width: 100%;
height: auto;
}
.note .sidekick .formula {
font: 600 22px/1.4 var(--hand);
color: var(--ink);
padding: 8px 14px;
border-left: 2px dashed var(--ink-fade);
transform: rotate(-1deg);
}
.note .sidekick .formula em {
color: var(--red);
font-style: normal;
font-weight: 700;
}
.note .sidekick .arrow {
font: 600 26px/1.1 var(--hand);
color: var(--red);
transform: rotate(6deg);
align-self: flex-end;
margin-right: -4px;
}
.note .sidekick .doodle-caption {
font: italic 400 14px/1.4 var(--serif);
color: var(--ink-light);
margin-top: 4px;
}
/* 右侧便签纸(原 .note 样式迁移到 .note .paper) */
.note .paper {
padding: 22px 26px 22px 28px;
background: #FFFBF1;
border: 1px solid rgba(120,110,80,0.32);
border-left: 4px solid var(--red);
box-shadow: 5px 9px 22px -12px rgba(0,0,0,0.22);
position: relative;
transform: rotate(0.5deg);
transform-origin: top right;
}
.note .paper::before {
content: ''; position: absolute;
top: -1px; left: -1px; right: -1px; height: 4px;
background: repeating-linear-gradient(90deg, transparent 0 6px, rgba(120,110,80,0.18) 6px 8px);
}
.note .paper .strike {
text-decoration: line-through;
text-decoration-color: var(--red);
text-decoration-thickness: 2.5px;
color: var(--ink-fade);
}
.note .paper .scribble {
position: absolute; right: -8px; top: 28px;
font: 600 24px/1.15 var(--hand); color: var(--red);
transform: rotate(6deg);
background: #FFFBF1;
padding: 6px 12px 4px;
border: 1px dashed var(--red);
white-space: nowrap;
}
.note .paper .footnote {
margin-top: 14px; padding-top: 12px;
border-top: 1px dashed rgba(120,110,80,0.4);
font: 500 13px/1.4 var(--mono);
color: var(--ink-light);
display: flex; gap: 8px;
}
.note .paper .footnote .mark { color: var(--red); font-weight: 700; }
/* archive · 紧凑铺垫 */
.archive {
margin: 24px 64px 0;
background: var(--bg);
border-top: 2px solid var(--ink-strong);
border-bottom: 1px solid var(--rule);
display: grid;
grid-template-columns: 156px 1fr;
align-items: stretch;
}
.archive .stamp {
background: var(--ink-strong); color: var(--bg);
padding: 22px 16px;
font: 700 12px/1.4 var(--mono);
letter-spacing: 0.16em;
text-transform: uppercase;
text-align: center;
display: flex; flex-direction: column; justify-content: center; gap: 8px;
}
.archive .stamp .label { border: 1px solid rgba(255,255,255,0.45); padding: 4px 6px; }
.archive .stamp .x { font: 700 56px/1 var(--serif); color: #C85042; }
.archive .body-area { padding: 22px 28px 24px; }
.archive .verdict {
margin-top: 12px; padding-top: 10px;
border-top: 1px dashed var(--rule);
font: italic 600 19px/1.4 var(--serif);
color: var(--red);
}
/* cross · 转场爆点(留白偏大让 mega 撑场) */
.cross {
padding: 64px 64px 60px;
border-top: 1px solid var(--rule);
border-bottom: 1px solid var(--rule);
margin-top: 30px;
}
.cross .mega {
font: 900 180px/0.88 var(--serif);
color: var(--ink-strong);
letter-spacing: -0.045em;
}
.cross .mega .em {
background: linear-gradient(180deg, transparent 60%, var(--amber-soft) 60%, var(--amber-soft) 90%, transparent 90%);
padding: 0 6px;
}
.cross .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 36px; align-items: end; margin-top: 18px; }
.cross .left, .cross .right { border-top: 2px solid var(--ink-strong); padding-top: 16px; }
.cross .right .caption { font: italic 400 16px/1.45 var(--serif); color: var(--ink-light); margin-top: 10px; padding-left: 10px; border-left: 2px solid var(--amber); }
/* hero · 顿悟特写,中等偏小 */
.hero {
padding: 52px 64px 48px;
border-top: 4px solid var(--blue-deep);
margin-top: 32px;
position: relative;
}
.hero .layout { display: grid; grid-template-columns: 7fr 5fr; gap: 32px; align-items: start; }
.hero .visual .caption { font: italic 400 16px/1.45 var(--serif); color: var(--ink-light); margin-top: 10px; padding-left: 12px; border-left: 2px solid var(--blue-deep); }
.hero .pull-quote {
font: italic 700 36px/1.22 var(--serif);
color: var(--blue-deep);
margin: 12px 0 18px;
padding: 6px 0 6px 22px;
border-left: 4px solid var(--blue-deep);
position: relative;
}
.hero .pull-quote::before {
content: '\201C'; position: absolute;
left: 4px; top: -36px;
font: 700 88px/1 var(--serif);
color: var(--blue-deep); opacity: 0.35;
}
/* closing · 终格静默,留白沉淀 */
.closing {
padding: 60px 64px 64px;
background: var(--paper);
border-top: 6px double var(--ink-strong);
margin-top: 32px;
text-align: center;
}
.closing .approach {
font: italic 400 24px/1.45 var(--serif);
color: var(--ink-light);
margin-bottom: 24px;
}
.closing .mega-name {
font: 900 144px/0.95 var(--serif);
color: var(--ink-strong);
letter-spacing: -0.028em;
margin-bottom: 14px;
}
.closing .en-name {
font: 500 36px/1 var(--sans);
color: var(--ink-light);
letter-spacing: 0.04em;
margin-bottom: 28px;
}
.closing .byline {
display: flex; justify-content: center; align-items: baseline; gap: 22px;
font: 600 14px/1.2 var(--mono);
color: var(--ink-light);
letter-spacing: 0.18em;
text-transform: uppercase;
padding: 18px 0;
margin: 0 auto 24px;
max-width: 820px;
border-top: 1px solid var(--ink-strong);
border-bottom: 1px solid var(--ink-strong);
flex-wrap: wrap;
}
.closing .byline strong { color: var(--ink-strong); font-weight: 700; letter-spacing: 0.12em; }
.closing .byline .sep { color: var(--ink-fade); font-weight: 400; }
.closing .closing-body {
font: 400 22px/1.7 var(--serif);
max-width: 760px;
margin: 0 auto 22px;
text-align: left;
}
.closing .epilogue {
font: italic 500 26px/1.45 var(--serif);
color: var(--ink-strong);
margin-top: 18px;
}
.closing .epilogue::before { content: '— '; color: var(--red); font-weight: 700; }4.2 HTML 骨架
<div class="magazine-head">
<div class="top-bar">
<div class="left">
<span class="badge">№ 01</span>
<span>[领域 · 年份]</span>
</div>
<div class="right">[ENGLISH CATEGORY / SUBCATEGORY]</div>
</div>
<h1>[不剧透标题]<br>[第二行可选]</h1>
<p class="deck">[italic 引言:暗示问题但不揭示答案]</p>
</div>
<section class="feature">
<div class="visual">[SVG 大图,max-width 540px]</div>
<div class="meta">
<div class="kicker"><span class="num">01</span><span class="rule"></span>起点 · [时空锚点]</div>
<h2 class="head-serif">[卡住的问题]</h2>
<p class="lead">[一句简洁领题]</p>
<div class="body-sans drop-cap">
<p>[具体例子,drop cap 第一段第一字]</p>
<p>[关键转折用 <em> 强调]</p>
<span class="ask">[开放设问?]</span>
</div>
</div>
</section>
<aside class="note">
<!-- 左侧 sidekick:三选一或组合(SVG 速写 / 手写公式 / 箭头评注),不能空 -->
<div class="sidekick">
<!-- 选项 A: SVG 速写 -->
<svg viewBox="0 0 280 200">[简笔画姿态 + 红笔批注]</svg>
<!-- 选项 B: 手写公式 -->
<div class="formula">
[关键词] = [看着对]<br>
<em>≠ [跑得通]</em>
</div>
<!-- 选项 C: 箭头评注 -->
<div class="arrow">→ [一句注脚]</div>
<!-- 可选:caption -->
<div class="doodle-caption">[一句小字说明]</div>
</div>
<!-- 右侧 paper:便签卡(原 note 内容迁移到 .paper 内) -->
<div class="paper">
<div class="kicker"><span class="num">02</span>第一次尝试</div>
<h3 class="head-sans">[尝试的动作名]</h3>
<div class="body-serif">
<p>[思路 1]</p>
<p>失败的关键词用 <span class="strike">删除线</span></p>
</div>
<div class="footnote"><span class="mark">¹</span><span>[失败的根本原因]</span></div>
<div class="scribble">[一句红笔批注]</div>
</div>
</aside>
<section class="archive">
<div class="stamp">
<div class="label">EX-02</div>
<div class="x">✕</div>
<div class="case">Failed</div>
</div>
<div class="body-area">
<div class="kicker"><span>第二次尝试</span></div>
<h3 class="head-sans">[尝试的动作名]</h3>
<div class="visual">[SVG 失败示意,max-width 720px]</div>
<div class="body-serif"><p>[思路 + 失败]</p></div>
<div class="verdict">[结案语]</div>
</div>
</section>
<section class="cross">
<!-- mega 字由内容决定(动作词/视角词/单字爆点),禁止默认套「等等——」 -->
<h2 class="mega"><span class="em">[转折爆点 1-3 字]</span>[可选后缀如 ——、!]</h2>
<div class="grid">
<div class="left">
<div class="kicker" style="color: var(--amber);"><span class="num" style="background: var(--amber);">04</span>转折</div>
<div class="body-serif">
<p>[反向陈述]</p>
</div>
<span class="ask" style="color: var(--amber);">[转折设问?]</span>
</div>
<div class="right">
<div class="visual">[SVG 反向姿态示意,max-width 460px]</div>
<p class="caption">[caption]</p>
</div>
</div>
</section>
<section class="hero">
<div class="layout">
<div class="visual">
[SVG 大幅 hero 图,宽度自适应]
<p class="caption">[caption]</p>
</div>
<div class="text">
<div class="kicker" style="color: var(--blue-deep);"><span class="num" style="background: var(--blue-deep);">05</span>顿悟</div>
<h2 class="head-serif" style="color: var(--blue-deep); font-size: 50px;">[姿态名,不出现概念名]</h2>
<div class="pull-quote">[核心句子]</div>
<div class="body-serif drop-cap">
<p>[洞察的具体表述]</p>
</div>
</div>
</div>
</section>
<section class="closing">
<p class="approach">[这种 X 的研究对象,叫——]</p>
<h1 class="mega-name">[中文概念名]</h1>
<div class="en-name">[English Name]</div>
<div class="byline">
<span><strong>[人名]</strong></span>
<span class="sep">·</span>
<span>[年份]</span>
<span class="sep">·</span>
<span>[机构]</span>
<span class="sep">·</span>
<span>[文献]</span>
</div>
<div class="closing-body">
<p>[它打开了什么]</p>
<p>[它换了什么眼睛]</p>
</div>
<p class="epilogue">[诗意余韵,不元自指]</p>
</section>写入:/tmp/ljg_cast_sketchnote_{name}.html
---
步骤 5:截图(单阶段)
新风格不再使用橙色弯曲箭头。叙事流通过 page rules + 编号传达,单阶段渲染:
node assets/capture.js /tmp/ljg_cast_sketchnote_{name}.html ~/Downloads/{name}.png 1080 1500 fullpagefullpage 让 Playwright 自动适应内容总高度。
旧版的 measure.js 两阶段流程(先空跑测高再写 SVG 箭头)已废弃——本模式不需要。
---
步骤 6:自检(逐项)
公理项(任何一条不过 → 重做)
- [ ] 公理 1:具体物理约束(不是"领域困境"等抽象腔)
- [ ] 公理 2:至少一次失败,且 station 2 与 3 形态不同(note + archive)
- [ ] 公理 3:标题不剧透 + 命名站点最后
- [ ] 公理 4:现在视角,无"100 年后"等上帝视角
- [ ] 公理 5:文字克制,无元自指(不出现"你刚才发明了它"等点题语)
- [ ] 公理 6:中文母语表达——整卡走一遍:每句默念听是否像中文人说话;扫"是...的"/"在...的过程中"/"对于...而言"/"进行+名词"等翻译腔模板;超 3 逗号长句砍短
- [ ] cross mega 字是叙事自然产生的(不是默认套"等等——"——读者读完应能感到这一刻的视角变化)
视觉项(杂志骨架)
- [ ] 6 个站点,每个用不同 layout 模具:feature / note / archive / cross / hero / closing
- [ ] 4 字族同时使用:Serif + Sans + Mono + Hand
- [ ] 必备装饰:kicker(含 mono num 方块)+ drop cap + byline + stamp
- [ ] Station 2 (note):双栏(左 sidekick + 右 paper),左侧有 SVG/公式/箭头之一(不空),右侧有红笔删除线 + scribble + footnote
- [ ] Station 3 (archive):黑色印章 ✕ + verdict 红色 italic
- [ ] Station 4 (cross):「等等——」200px Serif + amber 高亮
- [ ] Station 5 (hero):pull-quote 蓝色 + 浮动大引号
- [ ] Station 6 (closing):mega-name ≥ 130px + byline 上下细线 + epilogue
- [ ] 漫画分镜节奏:feature 中等留白 → note 紧 → archive 紧 → cross 爆(mega 撑场)→ hero 中偏小 → closing 大(沉淀)。6 节 margin-top 不应都相同
- [ ] 颜色 ≤ 4 主色(红 + 蓝 + amber + 中性)
- [ ] 无纯黑 #000,标题用 #0F0F0F
技术项
- [ ] 单阶段渲染(不用 measure.js)
- [ ] PNG 高度通常 4500-6500(杂志风允许更高,节奏需要)
- [ ] 字体齐全:Noto Serif SC + Noto Sans SC + JetBrains Mono + Caveat
- [ ] 中文显示优雅(无方块字)
---
与其他模具的边界
- 跟
-i(信息图):信息图是数据可视化,本模式是概念叙事 - 跟
-w(白板):白板是推理过程纵向展开,本模式是问题→失败→转折→顿悟→命名的探索路径 - 跟
-c(漫画):漫画追求黑白动态分镜,本模式追求编辑设计 + 叙事张力
不知道用哪个时,问:「读者读完,是带走一个视角,一段推理,还是一段期刊专题式的体验?」
期刊专题式 → 本模式。
---
Known Pitfalls
- [2026-04-29] SVG.relations viewBox 必须对齐实测页面高度——凭估算会导致箭头与 HTML 标签错位(误差 30-50%)。(历史教训,新版已废弃箭头流程)
- [2026-05-01] flex 容器默认让 .station 撑满宽度——必须给所有 .station 变体显式 max-width,否则 align-self 失效。(历史教训,新版用 grid + margin auto)
- [2026-05-01] 「公理 1:真问题」最容易被偷懒——容易写成"X 领域有个问题"。检查方法:删掉一切抽象,只留具体物理约束("开了就炸"、"开了就死")。
- [2026-05-01] 「公理 3:命名在后」最容易被剧透——设计 layout 时,命名站点放最下面(路径终点),且标题不能出现概念名。
- [2026-05-01] 「公理 5:文字克制」最容易触雷——AI 在 closing 段会自动写成"你以为你刚才学到了一个概念。其实你刚才不得不发明了它"。这种元自指破坏体验,禁止。要余韵就写诗意句("于是,你看见了山"),不写元论。 (from: 20260501-180500_cast-potential-function-sketchnote)
- [2026-05-01] 旧版"重新发明 / 重新分娩 / 不得不发明它"等元自指 framing 已彻底淘汰——文档和输出文字都不再用此类自指话术。 (from: 20260501-180500_cast-potential-function-sketchnote)
- [2026-05-01] 杂志风排版需要四字族同时在场(Serif/Sans/Mono/Hand)。如果输出全是 Sans,回到 AI 单一字族的均质感。Mono 用于 kicker / num / byline / stamp,Hand 用于 ask / scribble / caption,缺一不可。 (from: 20260501-180500_cast-potential-function-sketchnote)
- [2026-05-01] 两个失败站点必须形态不同——station 2 用 note(便签批注),station 3 用 archive(档案标签)。两个都用 note 或都用 archive,节奏不出来。 (from: 20260501-180500_cast-potential-function-sketchnote)
- [2026-05-01] Drop cap 用
::first-letter选第一段第一字。如果 pull quote 在 body 之前,drop cap 不会作用于 pull quote——只作用于其后第一段。这是预期行为。 (from: 20260501-180500_cast-potential-function-sketchnote) - [2026-05-01] S1 feature visual SVG 内若把红色批注(如"外面什么也没动")紧贴主体物(玻璃罩边缘),易造成视觉拥挤。批注应该外移到 SVG 之外的 caption 位置,或保留为 SVG 内极小注解,让主体 visual 保持纯净。 (from: 20260501-181811_cast-agentic-sketchnote)
- [2026-05-01] 公理 6(中文母语表达)最易触雷——AI 默认写"输入连续,输出不连续"这种英文直译腔。要改成"慢慢加热,到 100 度突然就开了"。检查方法:每句默念听是否像中文人说话;名词化抽象词("共相""现象学""碎片化")一律替换成具体动词或具体物件。 (from: refactor-ljg-card-v-mode 2026-05-01)
- [2026-05-01] cross 模具 mega 字默认套"等等——"是僵硬的剧本套路。mega 必须紧扣具体转折——可以是动作词("翻过来")、视角词("看地形"/"别盯球")、单字爆点("翻"/"山"),或保留"等等"如果它真是这个转折的自然语气。检查:换成别的转折词如果也成立,说明它没紧扣,重写。 (from: refactor-ljg-card-v-mode 2026-05-01)
- [2026-05-01] 留白不能均匀分配。借鉴漫画分镜:feature 中等留白(开篇广角)→ note + archive 紧凑(铺垫快节奏)→ cross 留白偏大(爆点撑场)→ hero 中偏小(顿悟特写)→ closing 留白大(终格沉淀)。如果 6 节 margin-top 都在 60-80px,那是画廊陈列不是漫画分镜,重排。 (from: refactor-ljg-card-v-mode 2026-05-01)
- [2026-05-02] note 模具旧版是单栏 540px 右浮,左侧 ~480px 死空白显得空洞。新版改双栏 grid(左 sidekick 涂鸦区 + 右 540px 便签纸)。原 .note 直接挂的 strike/scribble/footnote 全部迁到
.note .paper内。如果生成 HTML 用了旧的扁平结构(直接.note > .scribble),scribble 定位会错。 (from: ljg-card-v-mode-note-redesign 2026-05-02) - [2026-05-02] sidekick 不能空——空了就回到旧版死空白的问题。三种填法(SVG/formula/arrow)至少选一。但也不能塞满——sidekick 是注脚,便签是主角,密度低于便签。 (from: ljg-card-v-mode-note-redesign 2026-05-02)
- [2026-05-02] 公理 6 实操经验:AI 在 lead/closing-body 这两个抒情段最容易冒翻译腔——"这是一个值得我们..."、"在...背景下..."、"对...而言..."。生成 HTML 后,先扫这两段的中文,命中模板词立即重写。其他段(kicker/byline/stamp)反而风险低。 (from: ljg-card-v-mode-note-redesign 2026-05-02)
模具:白板(-w)
核心信条
推理过程的可见化——思路展开的痕迹,以日系余白为呼吸。
神:概念用箭头串成链,关键词被标出,旁边有简笔图标。推进但不急——每一行是一步推理,每个留白是一次呼吸。不是排好版的结论,是思考展开的过程。
形随神动,不绑定任何特定载体(不是黑板,不是白板,不是纸)。漆面般的暖黑底只是让内容浮出来的最安静的背景。
审美三柱:
- 「余白」——空间不是剩余,是主角。内容四周、段落之间的留白是设计的一部分
- 「枯」——色彩极度克制,靠亮度差和微温差说话。全局暖灰调,一抹微朱点睛
- 「素」——无装饰。无边框、无噪点纹理、无拟物质感。干净到只剩内容和空气
步骤 1:读取模板
Read assets/whiteboard_template.html
模板提供:
- 书写字体加载(Permanent Marker + Kalam)
- CSS 变量(
--bg,--board,--ink,--red,--yellow,--blue,--green,--orange,--marker-bg) - 黑板底色 + 粉笔灰质感
- 木质画框(
.board-frame) - SVG 箭头 marker 定义(红
arrow-r/ 白arrow-w/ 蓝arrow-b/ 绿arrow-g/ 黄arrow-y) .colophon署名栏{{CUSTOM_CSS}}和{{CONTENT_HTML}}插槽
步骤 2:理解内容,选择风格
2.1 提取结构
从内容中提取:
- 核心论点:一句话总结
- 推理链:论点怎么一步步推出来的?识别 A → B → C 结构
- 3-8 个关键概念:可作为链条节点的概念
- 分支点:推理在哪里分岔、汇合、或转折
- 可画的概念:哪些概念能用简笔画快速表达
2.2 选择风格路线
| 风格 | 视觉特征 | 触发信号 | 主色 |
|---|---|---|---|
| 逻辑链(默认) | 横向推理链(→ 连接)+ 纵向层级 + 黄色关键词 + 内嵌简笔画 | 有因果/推理/论证/阐释结构 | --red 箭头 + --yellow 高亮 |
| 脑暴墙 | 核心词居中 + 放射状分支 + 色块便签 + 关键词散落 | 发散型/多观点/创意/头脑风暴 | --yellow 主导 |
| 时间线 | 纵向时间轴 + 节点 + 旁注 + 对比色 | 时间/阶段/进程/回顾类 | --green 主导 |
| 矩阵分析 | 2x2 或多格矩阵 + 象限标签 + 要素散布 | 分类/对比/评估/决策框架 | --blue 主导 |
选择原则:
- 默认用「逻辑链」——最能体现黑板推演感
- 内容有多个并列观点 → 脑暴墙
- 内容有时间/阶段维度 → 时间线
- 内容涉及分类/象限 → 矩阵分析
2.3 色调——和紙奶白 + 朱砂
日式极简。奶白底如手漉和紙,墨色文字如书道落笔,朱砂红如方印点睛。用结构和字重说话,不用颜色喊。
| 变量 | 色值 | 角色 |
|---|---|---|
--board | #F7F3EC 和紙 | 奶白底,温暖的手漉和紙 |
--ink | #2C2826 墨 | 正文,温暖的近黑色如墨汁 |
--ink-light | #8A8478 灰墨 | 标注、旁白 |
--yellow | #7A6B4E 焙茶 | 高亮文字——焙茶色,在奶白底上沉稳可读 |
--red | #A09888 石 | 箭头——安静的石灰色,只引路 |
--shu | #C03C28 朱砂 | 点睛——真正的朱砂红,每张板 ≤ 3 处 |
画框:rgba(180,170,155,0.15) 极淡暖灰线,几乎隐入背景。
原则:
- 奶白底上靠字重和焙茶色建立层级
- 墨色(ink)在和紙(board)上的对比是基础张力
- 圈标、下划线统一用
--yellow(焙茶色) - 箭头用
--red(石灰),安静引路,不抢戏 --shu(朱砂)仅用于:结论框边线、核心论点圈标、署名旁的印章。每张板不超过 3 处- 朱砂是唯一的高饱和色,靠稀缺性发挥冲击力
内容驱动色温偏移
默认保持侘寂灰调。根据内容主题,允许极微妙的色温偏移——不加新色相,只调底色和高亮的冷暖倾向。
检测:扫描内容关键词,匹配主导类型。跨类时取篇幅最大的主题。
| 类型 | 触发词 | --board | --yellow | 光暈色调 |
|---|---|---|---|---|
| 默认(和紙) | 无匹配 | #F7F3EC | #7A6B4E | 暖朱 |
| 技术 | AI、算法、模型、代码、架构、系统、API、数据、工程、网络 | #F3F4F7 | #5A6878 | 冷蓝 |
| 人文 | 哲学、认知、意义、伦理、存在、美学、叙事、历史、文学、心理 | #F7F0E5 | #8A6A3E | 深暖 |
| 商业 | 投资、商业、增长、市场、估值、融资、战略、竞争、利润、ROI | #F4F5F0 | #5A7054 | 中性绿 |
实现:匹配非默认类型时,在 {{CUSTOM_CSS}} 顶部添加变量覆盖 + 表面光暈覆盖。默认类型不覆盖。
技术:
:root { --board: #F3F4F7; --yellow: #5A6878; }
.board > .surface { background: radial-gradient(ellipse at 25% 20%, rgba(120,140,170,0.05) 0%, transparent 50%), radial-gradient(ellipse at 75% 55%, rgba(150,165,190,0.03) 0%, transparent 45%); }人文:
:root { --board: #F7F0E5; --yellow: #8A6A3E; }
.board > .surface { background: radial-gradient(ellipse at 25% 20%, rgba(190,160,120,0.05) 0%, transparent 50%), radial-gradient(ellipse at 75% 55%, rgba(210,180,140,0.03) 0%, transparent 45%); }商业:
:root { --board: #F4F5F0; --yellow: #5A7054; }
.board > .surface { background: radial-gradient(ellipse at 25% 20%, rgba(130,160,130,0.05) 0%, transparent 50%), radial-gradient(ellipse at 75% 55%, rgba(160,185,155,0.03) 0%, transparent 45%); }原则:
- 只覆盖
--board、--yellow和表面光暈,其他变量一律不动 - 偏移极其微妙——不并排对比看不出差异,但整体氛围不同
--ink、--red、--shu不随内容变化——微朱仍然是唯一的色彩锚点
2.4 标题设计
标题是整张黑板的第一眼。
必须做到:
- 标题是一句完整的判断/结论,不是一个词。如果原文标题只是概念名,从内容中提炼出核心论断作为主标题,概念名作为副标题
- 主标题中 1-2 个关键词用
--yellow标出,其余白色 - 主标题下方用粉笔横线收束(SVG wavy path,opacity 0.3)
标题结构:
<div class="board-title">
<div class="board-title-sub">副标题或引导语(较小,ink-light)</div>
<h1 class="board-title-main">白色文字<span class="y">黄色关键词</span>白色文字</h1>
<svg class="title-line" width="600" height="8">
<path d="M0,4 Q150,0 300,4 T600,4" stroke="var(--yellow)" fill="none" stroke-width="2" opacity="0.3"/>
</svg>
</div>标题 CSS 参考:
.board-title { text-align: center; padding: 56px 48px 16px; }
.board-title-sub { font: 500 34px/1.4 var(--hand); color: var(--ink-light); margin-bottom: 4px; }
.board-title-main { font: 700 64px/1.2 var(--marker); color: var(--ink); margin-bottom: 8px; }
.board-title-main .y { color: var(--yellow); }
.title-line { display: block; margin: 0 auto; }步骤 3:设计画面
3.1 黑板元素工具箱
所有视觉元素用 CSS + SVG 实现,不用外部图片。
文字层级
| 层级 | 字体 | 字号 | 颜色 | 用途 |
|---|---|---|---|---|
| 主标题 | --marker | 64-80px | --ink 白 | 黑板顶部大标题,关键词嵌黄色 |
| 链条文字 | --hand 700 | 34-40px | --ink 白 | 逻辑链中的概念和说明 |
| 关键词 | --hand 700 | 34-42px | --yellow | 链条中需要强调的概念 |
| 标注 | --hand 400 | 24-28px | --ink-light | 旁注、补充、小字 |
| 大号数字 | --marker | 72-120px | --yellow 或 --red | 数据亮点 |
中文处理:Permanent Marker 和 Kalam 对中文无效,回退到 PingFang SC。中文文字通过颜色(黄/红)和装饰(下划线、圈标)维持粉笔感。
粉笔效果(CSS)
/* 红色链条箭头 — 逻辑推进的视觉线索 */
.chain-arrow {
color: var(--red);
font: 700 34px var(--hand);
margin: 0 6px;
display: inline;
}
/* 黄色粉笔高亮 */
.chalk-yellow {
color: var(--yellow);
font-weight: 700;
}
/* 粉笔圈标 */
.chalk-circled {
border: 2.5px solid var(--yellow);
border-radius: 45% 55% 50% 48%;
padding: 2px 14px;
display: inline-block;
}
/* 红色粉笔下划线 */
.chalk-underline {
border-bottom: 3px solid var(--red);
padding-bottom: 2px;
}
/* 粉笔方框(重要结论) */
.chalk-box {
border: 2.5px solid var(--ink);
border-radius: 3px;
padding: 14px 18px;
}
.chalk-box-red { border-color: var(--red); }
.chalk-box-yellow { border-color: var(--yellow); }
.chalk-box-shu { border-color: var(--shu); }
/* 虚线框 */
.chalk-dashed {
border: 2px dashed var(--ink-light);
border-radius: 3px;
padding: 14px 18px;
}
/* 编号标记 */
.chalk-num {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px; height: 36px;
border-radius: 50%;
border: 2.5px solid var(--red);
color: var(--red);
font: 700 22px var(--hand);
margin-right: 8px;
}
/* 问号/感叹号标记 */
.chalk-question {
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px; height: 44px;
border: 2.5px solid var(--yellow);
border-radius: 50%;
color: var(--yellow);
font: 700 28px var(--hand);
}
/* 微朱点睛 — 极少量使用,每张板 ≤ 3 处 */
.shu-circle {
border: 2.5px solid var(--shu);
border-radius: 45% 55% 50% 48%;
padding: 2px 14px;
display: inline-block;
}
/* 朱印 — 结论旁的方印,侘寂点睛 */
.shu-stamp {
display: inline-block;
border: 2px solid var(--shu);
padding: 4px 10px;
font: 700 18px var(--hand);
color: var(--shu);
transform: rotate(-3deg);
opacity: 0.7;
}简笔粉笔画(SVG inline)
用简单 SVG path 画概念图标。stroke 用 var(--ink) 或 var(--yellow),stroke-width: 2-3px,只用 stroke 不用 fill(粉笔线条风格)。每个图标 3-5 个 path。
常用:人(圆头+线身+四肢)、问号、趋势图(折线)、房子(三角+方)、动物(极简轮廓)、灯泡、闪电。
粉笔质感:线条不要完美平直,path 可以有微小抖动。
3.2 布局原则
黑板是链式的——主体是横向逻辑链,纵向分层递进。
核心规则:
- 链式流动:每行一条推理链,概念用红色 → 连接,从左到右
- 纵向层级:每层是推理的下一步,上下层用缩进或间距关联
- 内嵌图标:简笔画和文字混排在同一行,不单独占区
- 黄色高亮:重要概念用黄色,在白色文字中一目了然
- 红色驱动:→ 箭头统一红色,是逻辑推进的视觉节拍
- 疏密有致:核心推理链紧凑(行间距 1.8 倍),话题转换处留出呼吸(間)
- 留白即内容:不追求写满。关键转折前后留大间距,让读者的思维有落脚处
- 自然不对齐:链条不需要严格左对齐,像真正手写时的自然缩进
间距层级(区域分隔)
用间距说话,不用线说话。 黑板上没有横线——只有写得紧和写得松。
| 层级 | 间距 | 用途 | CSS |
|---|---|---|---|
| 零距 | 0-4px | 同一条链的续行 | .cl + .cl { margin-top: 2px; } |
| 小距 | 14-20px | 同一话题内的不同链 | margin-top: 16px |
| 中距 | 32-44px | 不同话题之间(間) | margin-top: 36px |
| 大距 | 52-72px | 大转折/新章节(大間) | margin-top: 60px,可加粉笔波浪线 |
禁止:
border-top直线分隔——黑板上不画直线分区- ↓ 箭头泛滥——只在明确的"因此/所以"递进时用,每张黑板 ≤ 3 个
- 等间距排布——间距不等才像手写
粉笔波浪线(仅用于大距分隔,可选):
<svg width="800" height="8" style="display:block;margin:0 auto;opacity:0.15;">
<path d="M0,4 Q100,0 200,4 T400,4 T600,4 T800,4" stroke="var(--ink)" fill="none" stroke-width="1.5"/>
</svg>3.3 画面构成(按风格)
逻辑链(默认)
[大标题 — 白色, 居中, 关键词嵌黄色]
红→ 概念A → 不是因为X → 也不是Y → 而是Z → 结论1
↓
红→ 展开结论1 → [简笔画] → 补充说明 → 推出概念B
↓
概念B → 举例... → 但有一个问题 → [?]
↓
红→ 解答... → 因此 → [最终结论 — 红框]- 每行一条推理链。行与行之间用 ↓ 连接
- 红色 → 开头的行是主干推理,无红色 → 的行是补充
- 链条自然流动,像在黑板上从左往右写
- 适当穿插简笔画打破纯文字的单调
脑暴墙
[色块1] [色块2]
\ /
[色块3] → [核心词] ← [色块4]
/ \
[色块5] [色块6]
底部:关键结论,红色下划线时间线
[标题]
●──────●──────●──────●──────●
阶段1 阶段2 阶段3 阶段4 阶段5
│ │ │ │ │
注释 注释 注释 注释 注释矩阵分析
[标题]
│ 维度A高
────────┼────────
象限1 │ 象限2
│
────────┼────────
象限3 │ 象限4
│ 维度A低
维度B低 维度B高步骤 4:写 CSS + HTML
所有 CSS 写入 {{CUSTOM_CSS}}。所有 HTML 写入 {{CONTENT_HTML}}。
CSS 从零写——class 名反映内容语义(.premise-chain、.conclusion-box),不用通用名。
色调变量:默认侘寂色调直接使用模板值。如内容匹配技术/人文/商业类型(见「内容驱动色温偏移」),在 CUSTOM_CSS 顶部覆盖 --board、--yellow 和 .board > .surface。
逻辑链布局技巧:
- 每条链用
display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 6px;实现自然换行 - 箭头用
<span class="chain-arrow">→</span>内嵌文字流 - 黄色关键词用
<span class="chalk-yellow">关键词</span> - 简笔画 SVG 用
display: inline-block; vertical-align: middle;嵌入行内 - 行间距不等——同一论点的行紧凑(
margin-top: 12px),新话题换大间距(margin-top: 28px) - 红色 → 开头的行可加
padding-left缩进,形成层次
替换变量:
| 变量 | 内容 |
|---|---|
{{CUSTOM_CSS}} | 全部自定义 CSS |
{{CONTENT_HTML}} | 全部内容 HTML |
{{SOURCE_LINE}} | 内容来源(可选):<span class="info-source">来源文字</span>,无来源时空字符串 |
写入:/tmp/ljg_cast_whiteboard_{name}.html
步骤 5:自检
- [ ] 底色 + 暖灰文字,看着舒适不刺眼?
- [ ] 如内容匹配技术/人文/商业类型,色温偏移(--board、--yellow、光暈)已应用?
- [ ] 灰调为主,微朱(--shu)≤ 3 处?
- [ ] 标题是完整判断句?高亮词用生成色(--yellow)?
- [ ] 标题够大够粗(≥ 64px)?
- [ ] 余白充足——四周留白 ≥ 72px,段间留白有呼吸感?
- [ ] 链条文字 ≥ 34px?标注 ≥ 24px?
- [ ] 区域分隔用间距层级,无 border-top 直线?
- [ ] 至少 2 个简笔图标(SVG),线条用 ink 色?
- [ ] 无拟物装饰(无噪点纹理、无木框、无粉笔质感)?
- [ ] 整体干净、安静、暖?
步骤 6:截图
node assets/capture.js /tmp/ljg_cast_whiteboard_{name}.html ~/Downloads/{name}.png 1080 800 fullpageRelated skills
FAQ
Which mold should I pick?
Use -l for long reading cards, -i for infographics, -m for multi-card splits, -v for editorial sketchnotes, -c for manga comics, -w for whiteboards, and -b for Xiaohongshu big-font attachments.
Where do PNG files save?
Rendered PNGs land in ~/Downloads/ with filenames derived from the content title or core idea, trimmed to 20 characters.
What must I read before rendering?
Read references/taste.md plus the mode file for your flag before executing any mold to avoid AI-generated visual slop.
Is Ljg Card safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.