
Qiaomu Info Card Designer
- 221 installs
- 264 repo stars
- Updated April 5, 2026
- joeseesun/info-card-designer
Design shareable info cards for social posts, newsletters, and landing snippets with consistent typography, layout grids, and brand colors.
About
qiaomu-info-card-designer helps agents lay out bite-sized information cards with hierarchy, color, and type scales tuned for social and mobile sharing—turning copy bullets into on-brand graphics for growth campaigns.
- Grid-based card layouts
- Typography hierarchy presets
- Brand palette enforcement
- Social aspect-ratio variants
- Export-ready visual specs
Qiaomu Info Card Designer by the numbers
- 221 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #919 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joeseesun/info-card-designer --skill qiaomu-info-card-designerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 221 |
|---|---|
| repo stars | ★ 264 |
| Last updated | April 5, 2026 |
| Repository | joeseesun/info-card-designer ↗ |
What it does
Design shareable info cards for social posts, newsletters, and landing snippets with consistent typography, layout grids, and brand colors.
Files
Info Card Designer
将任意内容转化为杂志质感信息卡,自动截图 + 超长分割,适配 X/Twitter、微信、小红书分享。
工作流
Step 0:获取内容(如果输入是 URL)
如果用户给的是 URL 而非纯文本,先抓取内容再进入 Step 1。
路由规则:
| URL 类型 | 抓取方式 |
|---|---|
arxiv.org/abs/ | 提取论文 ID,优先抓 HTML 版 https://arxiv.org/html/{id}v1,失败则抓 PDF https://arxiv.org/pdf/{id}.pdf |
x.com / twitter.com | curl -sL "https://r.jina.ai/{url}" |
mp.weixin.qq.com | python3 ~/.claude/skills/markdown-proxy/scripts/fetch_weixin.py "{url}" (如存在) |
| 其他网页 | curl -sL "https://r.jina.ai/{url}",失败则 curl -sL "https://defuddle.md/{url}" |
arXiv 专用逻辑(抓全文,不是摘要):
# 从 https://arxiv.org/abs/2603.25694 提取 ID
paper_id=$(echo "$url" | grep -oP 'arxiv.org/abs/\K[\d.]+')
# 优先抓 HTML 版(内容最完整)
html_url="https://arxiv.org/html/${paper_id}v1"
curl -sL "$html_url" 2>/dev/null || curl -sL "https://r.jina.ai/https://arxiv.org/pdf/${paper_id}.pdf"通用代码(覆盖 90% 场景):
curl -sL "https://r.jina.ai/{url}" 2>/dev/null原则:skill 自己能抓就自己抓,不依赖外部 skill。r.jina.ai 免费、无需 API key、支持 X/Twitter/普通网页。arXiv 论文优先抓 HTML 版(比 PDF 更易提取),只有公众号等特殊页面才回退到专用脚本。
Step 1:提炼核心信息(最重要)
目标:让读者只看图片就能理解文章最核心、最重要的概念和信息,不需要点进原文。
提炼原则: 1. 找核心论点:文章最反直觉、最颠覆认知的 1 个观点,作为主标题或核心金句 2. 找关键数据:文中的具体数字(百分比、倍数、年份、金额),数字比文字更有冲击力 3. 找因果链:A 导致 B,B 导致 C → 每个环节就是一个要点 4. 砍到 4-6 个要点:不是所有内容都值得放,只放"删了会损失信息"的 5. 每个要点 ≤ 2 句话:第一句给事实/数据,第二句给洞察/结论
主标题写法(卡片成败关键):
- ✅ 必须是结论性的:直接亮出文章最反直觉的发现/结论,读者看到就被勾住
- ✅ 用数字驱动:"把品牌面积放大100倍"、"500年越做越小,现在越做越大"
- ✅ 用动词驱动:"买表先排队,卖表被追踪"、"雅痞拯救了机械表"
- ❌ 不能是描述性的:"在中国AI生态待了两周后"、"关于品牌的思考"(像日记标题,没冲击力)
- ❌ 避免名词性标题:"表壳即品牌的进化"、"人为制造稀缺"(太抽象)
检验标准:如果读者只看到主标题,就想知道"为什么?"——说明标题是对的。如果反应是"哦"——说明标题是错的。
条目标题写法:
- ✅ 有具体数字的反直觉发现:"200家机器人公司,几乎全部没有收入"
- ✅ 直接给结论:"VC只投好简历,历史证明这是错的"
- ❌ 平铺直叙:"软件差距在扩大"、"估值泡沫"
要点之间的逻辑:
- 要点不是随机罗列,要有叙事弧线
- 推荐顺序:发现问题 → 分析原因 → 关键证据 → 意外/反转
- 让读者看完有"哦原来如此"的感觉
金句选取:
- 从原文中找最有冲击力的 1 句话放引用块
- 如果原文没有现成金句,可以用原文事实重新组织一句(不改事实,改表达)
数据准确性(强制):
- 引用数字时必须忠实于原文的表述方式
- ARR(年化收入)和单月收入是不同概念,不能混用
- 不能为了冲击力而改变数据含义
- 原文说"reportedly"/"approximately"等不确定表述,卡片中要保留"据报道"/"约"等修饰
内容原则(强制):
- 卡片内容必须 100% 来自用户提供的原文/URL/文本,严禁自行编造或使用占位符
- 标题、描述、来源、金句均须与原文一致,Hook 改写只改表达方式,不改事实
- 用户说"保持原文"或"不改描述"时关闭 Hook 模式
可图化评估(Step 1 末尾自动执行,不询问用户):
分析完内容后,判断是否值得加图:
| 内容特征 | 图表类型 |
|---|---|
| 明确的因果链(A 导致 B,B 导致 C) | Mermaid flowchart |
| 有明确顺序的步骤流程(3 步以上) | Mermaid flowchart |
| 概念间有包含/对立/互补关系 | Mermaid graph |
| 有形象概念但 Mermaid 表达不了 | 内联 SVG |
| 纯观点/金句/哲学/数字列表 | 不加图 |
原则:图是配角,只在图比文字能多传递 30% 以上信息时才加。宁可不加,不要为了加图而加图。
Step 2:分析布局
- 低密度(1 个核心观点)→ 大字符主义布局(模板 A)
- 中密度(2-4 要点)→ 标准单栏布局(模板 B)
- 高密度(5+ 要点)→ 单栏列表布局(模板 D,推荐);多栏网格(模板 C)仅在用户明确要求或桌面端展示时使用
Step 2.5:询问设计风格(必须执行,生成前先问)
每次生成前必须询问用户选择风格,不得跳过。
用 AskUserQuestion 展示三个选项:
问题:选择卡片设计风格?
选项 A — 经典风格(默认)
功能清晰,稳定可用。仓耳今楷统一字体,安全配色,标准间距。
适合:日常分享、系列卡片、不确定时的默认选择。
选项 B — 杂志风格
在经典基础上精致化。奇偶交替底色、更细腻的间距节奏、克制的装饰引号。
适合:正式发布、社交媒体传播。
选项 C — 艺术风格(实验)
极端字号对比(标题 3x 正文)、楷书×无衬线混排、一个"英雄视觉元素"
(超大数字/单字/全宽色带)、更有气质的非常规配色。
适合:追求视觉冲击、不介意风格激进。根据用户选择执行:
- 选 A → 按 design-spec.md 经典风格生成
- 选 B → 按 design-spec.md 杂志风格生成
- 选 C → 按下方艺术风格规范生成
艺术风格(C)专用规范:
- 主标题字号 ≥ 72px,正文描述 ≤ 18px,二者比例 ≥ 3:1
- 字体三层混排:仓耳今楷(标题/条目标题)× NotoSerifSC(正文描述)× 系统无衬线(编号/标签)
- 英雄元素 = 大标题本身:72px+ 的楷书标题即是视觉冲击,不需要叠加幽灵字、背景大字等装饰——那是设计院校习作,实用分享卡会显得做作
- 配色选非常规色(避开靛蓝/琥珀/墨绿等常见选择),背景加微渐变或色温偏移
- 间距要有节奏:同组内 8-12px,跨组大留白 48-64px
- 装饰线预算清零:不用 border-left 竖线,靠色块和间距区分层次
- 深色系对比度规范(⚠️ 冷色系尤其注意):
- section-label 最小 13px,不用 10px(10px 在手机上几乎不可见)
- 编号
--num颜色必须与深色背景对比度 ≥ 3:1(44px 大字标准),冷色系 accent(蓝/绿/紫)天然亮度低,直接用往往不达标,需要提亮版本 - 禁止 opacity 叠加代替颜色:
color: var(--accent); opacity: 0.22实际合并后颜色极暗,肉眼和截图都难辨。一律用固定色值(如color: #8C6830) - hex 颜色必须恰好 6 位:
#6040888是 7 位,无效,浏览器静默回退为黑色
Step 3:生成 HTML
读取 references/design-spec.md 获取完整设计规范(字号、配色、布局模板、CSS 变量等),所有视觉参数以 design-spec.md 为准。
图表区生成规范(如 Step 1 判断需要加图):
图表位置:主标题/副标题下方,要点列表上方,作为内容结构的视觉总览。
Mermaid 流程图
<!-- 在 <head> 引入 -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'base',
themeVariables: {
primaryColor: 'var(--accent-hex)', /* 用卡片 accent 色,写死 hex 值 */
primaryTextColor: 'var(--text-hex)',
primaryBorderColor: 'var(--num-hex)',
lineColor: 'var(--num-hex)',
background: 'transparent',
edgeLabelBackground: 'transparent',
fontSize: '15px'
},
flowchart: { curve: 'basis', padding: 16 }
});
</script>
<!-- 在 HTML 中使用 -->
<div class="diagram-block">
<pre class="mermaid">
flowchart LR
A[摄入糖] --> B[血糖升高] --> C[胰岛素分泌] --> D[压制生长激素]
</pre>
</div>Mermaid CSS(图表容器):
.diagram-block {
background: rgba(255,255,255,0.04); /* 深色卡片用微亮底 */
border-radius: 10px;
padding: 20px 16px;
margin-bottom: 36px;
overflow: hidden;
}
.diagram-block .mermaid { display: flex; justify-content: center; }
.diagram-block svg { max-width: 100%; height: auto; }Mermaid 注意事项:
themeVariables中必须写固定 hex 值,不能写var(--xxx)(Mermaid 不解析 CSS 变量)- 浅色卡片用
theme: 'neutral',深色卡片用theme: 'base'+ 自定义 themeVariables - 节点文字保持简短(≤ 6 字),否则截图时节点拉太宽
内联 SVG
当内容更适合形象图示而非流程图时,用内联 SVG:
<div class="diagram-block">
<svg viewBox="0 0 500 120" xmlns="http://www.w3.org/2000/svg"
style="width:100%;height:auto;display:block;">
<!-- 用卡片 accent 色填充,与整体配色统一 -->
<rect x="10" y="40" width="120" height="44" rx="8"
fill="rgba(196,151,58,0.15)" stroke="#8C6830" stroke-width="1.5"/>
<text x="70" y="67" text-anchor="middle"
font-family="-apple-system,sans-serif" font-size="15" fill="#EAD9B5">概念A</text>
<!-- 箭头 -->
<line x1="132" y1="62" x2="178" y2="62" stroke="#8C6830" stroke-width="2"/>
<polygon points="178,56 190,62 178,68" fill="#8C6830"/>
<!-- 更多节点... -->
</svg>
</div>SVG 原则:
- viewBox 固定宽 500,高度按内容决定(通常 80-160)
- 颜色用卡片的 accent/text/muted 固定值,不用 CSS 变量(SVG 属性不继承)
- 字体用系统无衬线(
-apple-system,sans-serif),确保截图渲染稳定 - 保持极简:只用矩形、箭头、文字,不追求复杂图形
硬性约束:
- 卡片宽度:默认 600px,仅在用户明确要求时使用 480 / 900
<meta name="viewport" content="width=[指定宽度]">防缩放- 背景色
#f5f3ed - 字号用
clamp()写法(见 design-spec.md 字号规范),确保多宽度等比缩放 - 手机可读性底线:正文 ≥ 15px,辅助文字 ≥ 12px,任何可读内容不低于 10px
保存路径:/tmp/info-card-[关键词].html
Step 4:截图(必须执行)
使用 Playwright Python 截图(不用 Chrome DevTools MCP,避免端口冲突):
无图表时(标准等待):
from playwright.sync_api import sync_playwright
import os
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 600, "height": 900}, device_scale_factor=2)
page.goto("file:///tmp/info-card-xxx.html")
page.wait_for_timeout(2000) # 等待字体加载
page.screenshot(path="/tmp/info-card-xxx.png", full_page=True)
browser.close()含 Mermaid 图表时(需等待 SVG 渲染完成):
from playwright.sync_api import sync_playwright
import os
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 600, "height": 900}, device_scale_factor=2)
page.goto("file:///tmp/info-card-xxx.html")
page.wait_for_timeout(1500) # 等待字体 + Mermaid JS 加载
# 等待 Mermaid 渲染完毕(SVG 出现)
try:
page.wait_for_function(
"() => document.querySelectorAll('.mermaid svg').length > 0",
timeout=8000
)
except:
pass # 超时降级:继续截图,图表可能未渲染
page.wait_for_timeout(500) # 额外稳定等待
page.screenshot(path="/tmp/info-card-xxx.png", full_page=True)
browser.close()from PIL import Image img = Image.open("/tmp/info-card-xxx.png") print(f"Dimensions: {img.size[0]}x{img.size[1]}") print(f"File size: {os.path.getsize('/tmp/info-card-xxx.png') / 1024:.0f}KB")
**宽度与倍率对应**:
| 卡片宽度 | devicePixelRatio | 输出 PNG 宽度 |
|---------|-----------------|-------------|
| 480px | **3x** | 1440px |
| 600px | **2x** | 1200px |
| 900px | **2x** | 1800px |
> **为什么不用 Chrome DevTools MCP**:经常因为浏览器端口占用、extension 冲突导致连接失败。Playwright Python 是独立 headless 浏览器,零依赖冲突,支持 `full_page=True` 全页截图。
### Step 5:超长分割(默认不执行)
> **默认不分割**。截图后直接输出完整长图,不管高度多少。
> 只有当用户明确要求"切分"、"分割"、"拆成多张"时,才执行分割。
**用户要求分割时**:
python3 ~/.claude/skills/qiaomu-info-card-designer/scripts/split_card.py [图片路径] 1200
分割后输出 `card-1.png`, `card-2.png` ... 等文件。
### Step 6:整理并输出
> **截图完成后立即自动 deploy,不询问用户,不等待确认。**
**保存路径规则**:~/Downloads/info-cards/[YYYYMMDD]-[来源]-[主题关键词]/ ├── card.html # 源文件 └── card.png # 截图,直接可用于发布
示例:`~/Downloads/info-cards/20260405-life-wisdom/card1.png`
> 文件夹名格式:`日期-来源-主题`,全小写英文+连字符
**deploy 命令**:mkdir -p ~/Downloads/info-cards/{slug} cp /tmp/{card}.html /tmp/{card}.png ~/Downloads/info-cards/{slug}/ open ~/Downloads/info-cards/{slug}/ # 自动打开 Finder 文件夹
告知用户:输出目录路径 + 共几张图片
---
> **所有视觉参数(字号、配色、布局模板)的唯一真相源是 `references/design-spec.md`。**
MIT License
Copyright (c) 2026 joeseesun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
qiaomu-info-card-designer
Turn any text or URL into a magazine-quality info card image — auto screenshot included.
将任意文本或链接一键转化为杂志质感信息卡片,自动截图输出图片,适合分享到 X、小红书、微信。
[English](#english) | [中文](#中文)
---
<a name="english"></a>
English
What it does
Give Claude a URL, paste text, or describe a topic — this skill designs a magazine-style HTML info card and automatically screenshots it to PNG ready to post on X (Twitter), Xiaohongshu, or WeChat.
Key features:
- Swiss International Style + editorial magazine aesthetic
- Big titles, punchy hook-style summaries (10–20 chars each)
- Supports 480 / 600 / 900px width — just say "make it 480px wide"
- Auto-splits cards taller than 1200px into multiple images
- vw-based font sizing: fonts scale proportionally with card width
- Uses TsangerJinKai (仓耳今楷) local font for consistent rendering
Prerequisites
- [ ] Playwright Python (used for headless screenshot)
- Install:
pip3 install playwright && python3 -m playwright install chromium - Verify:
python3 -c "from playwright.sync_api import sync_playwright; print('ok')" - [ ] Python 3 + Pillow + NumPy (for image splitting)
- Install:
pip3 install Pillow numpy - Verify:
python3 -c "from PIL import Image; import numpy; print('ok')"
Installation
npx skills add joeseesun/info-card-designerHow to trigger
Say any of these to Claude:
- "生成信息卡" / "做张信息卡" / "信息卡片"
- "把这段内容做成卡片"
- "make info card" / "generate card"
Usage examples
用户: 把这个链接做成信息卡 https://wise.readwise.io/issues/wisereads-vol-134/
用户: 把这段内容做成 480 宽的信息卡
用户: generate a card from this text, keep the original descriptionsWidth options
| Width | Best for | Output (2x DPR) |
|---|---|---|
| 480px | Mobile-first, compact | 960px (3x → 1440px) |
| 600px | Default, balanced (recommended) | 1200px |
| 900px | Desktop / large-screen sharing | 1800px |
Troubleshooting
| Problem | Solution |
|---|---|
| Screenshot fails | Make sure Playwright is installed: pip3 install playwright && python3 -m playwright install chromium |
No module named playwright | Run pip3 install playwright then python3 -m playwright install chromium |
No module named PIL | Run pip3 install Pillow numpy |
| Card content is wrong / made up | The skill is instructed to use only your source content — re-trigger with the URL or paste the text directly |
| Card too tall (1 image) | Splitting is automatic when height > 1200px; check that Pillow + NumPy are installed |
Credits
Design inspiration from @shao__meng's original post on X: https://x.com/shao__meng/status/2027368489439334504
---
<a name="中文"></a>
中文
功能说明
给 Claude 一个 URL、粘贴一段文字,或描述一个主题——这个 skill 会自动设计成杂志质感 HTML 信息卡,并自动截图输出 PNG,可直接发到 X、小红书、微信。
核心特点:
- 瑞士国际主义 + 杂志编辑风格
- 大标题、强视觉张力
- Hook 模式(默认开启):每条描述改写为 10-20 字钩子句,有冲击感
- 支持 480 / 600 / 900px 宽度,说"生成 480 宽的卡片"即可切换
- 超长卡片(>1200px)自动分割成多张图
- 使用仓耳今楷(TsangerJinKai)本地字体,截图渲染 100% 一致
前置条件
- [ ] Playwright Python(用于无头截图)
- 安装:
pip3 install playwright && python3 -m playwright install chromium - 验证:
python3 -c "from playwright.sync_api import sync_playwright; print('ok')" - [ ] Python 3 + Pillow + NumPy(用于超长图片分割)
- 安装:
pip3 install Pillow numpy - 验证:
python3 -c "from PIL import Image; import numpy; print('ok')"
安装
npx skills add joeseesun/info-card-designer触发方式
对 Claude 说:
- "生成信息卡"
- "做张信息卡"
- "把这段内容做成卡片"
- "信息卡片"
- "make info card" / "generate card"
使用示例
把这个链接做成信息卡:https://wise.readwise.io/issues/wisereads-vol-134/
把这段文字做成 480 宽的卡片
生成信息卡,保持原文描述Hook 模式开关
| 说法 | 效果 |
|---|---|
| 默认 | 自动将描述改写为 10-20 字钩子句 |
| "保持原文" / "不改描述" | 使用原始文字,不改写 |
常见问题
| 问题 | 解决方法 |
|---|---|
| 截图失败 | 安装 Playwright:pip3 install playwright && python3 -m playwright install chromium |
No module named PIL | 运行 pip3 install Pillow numpy |
| 内容不对 / 有编造 | 提供原文链接或直接粘贴文本,重新触发 |
| 超长卡没有分割 | 确认 Pillow + NumPy 已安装,高度 >1200px 才触发分割 |
致谢
设计风格灵感来自 @shao__meng 在 X 上的这篇分享: https://x.com/shao__meng/status/2027368489439334504
信息卡设计规范
字体引入
标题字体:仓耳今楷02(本地 TTF) 正文字体:仓耳今楷02(同一字体,统一视觉风格)
<style>
@font-face {
font-family: 'TsangerJinKai';
src: url('file:///Users/joe/.claude/skills/qiaomu-info-card-designer/assets/TsangerJinKai02-W04.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: block;
}
</style>字体栈定义(写在 CSS 变量中):
:root {
--font-title: 'TsangerJinKai', serif;
--font-body: 'TsangerJinKai', serif;
--font-label: 'TsangerJinKai', sans-serif;
--font-mono: 'SF Mono', 'Menlo', monospace;
}为什么用本地字体:Google Fonts 在 file:// 协议下可能加载失败或延迟,导致截图时字体 fallback 为系统默认。本地 @font-face 保证 100% 命中,截图零等待。字号规范(手机阅读优先)
核心原则:卡片最终以图片形式在手机上全屏阅读。600px CSS × 2x 截图 = 1200px 图片,iPhone 3x 屏近 1:1 显示。换算公式:CSS px × 2 ÷ 3 ≈ 手机上的 pt。正文必须 ≥ 13pt(20px CSS),标签必须 ≥ 10pt(16px CSS)。
| 层级 | 600px 固定值 | 手机 pt | 说明 |
|---|---|---|---|
| 主标题 | 56px | ~37pt | 视觉冲击,楷体不加粗 |
| 条目标题 h3 | 28px | ~18pt | 视觉锚点,扫读入口 |
| 正文 | 20px | ~13pt | 舒适阅读底线 |
| 副标题/金句 | 22px | ~14pt | 略大于正文 |
| 标签/speaker/dim | 16px | ~10pt | 清晰可辨 |
| 页脚 | 15px | ~10pt | 最小可读 |
| 金句出处 | 15px, color #777 | ~10pt | 不用 #999 |
基础卡片 CSS
:root {
--color-bg: #f5f3ed;
--color-text: #1a1a1a;
--color-accent: #2c3e8c; /* 根据主题替换 */
--color-muted: #555;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
margin: 0;
background: var(--color-bg);
}
.card {
width: 600px;
background: var(--color-bg);
padding: 38px;
display: flex;
flex-direction: column;
gap: 24px;
position: relative;
overflow: hidden;
}
/* 噪点纹理 */
.card::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 0;
}
.card > * { position: relative; z-index: 1; }
/* 标题 — 仓耳今楷,楷书质感 */
.main-title {
font-family: var(--font-title);
font-size: 56px;
font-weight: normal; /* 楷书字体自带笔画粗细变化,不需要 bold */
line-height: 1.15;
color: #0a0a0a;
letter-spacing: 0.02em; /* 楷书适合微正间距 */
}
/* 正文 — 仓耳今楷,统一风格 */
.content-body {
font-family: var(--font-body);
font-size: 20px;
line-height: 1.65;
color: #1a1a1a;
}
/* Accent 装饰线 */
.accent-bar {
height: 6px;
background: var(--color-accent);
width: 80px;
}
.accent-bar-full {
height: 4px;
background: var(--color-accent);
width: 100%;
}
/* 标签 — 可选元素,仅在系列卡片需要编号或用户要求时添加 */
.label {
font-family: var(--font-label);
font-size: 16px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--color-accent);
}
/* 背景色块 */
.bg-block {
background: rgba(0,0,0,0.03);
padding: 18px 20px;
border-left: 4px solid var(--color-accent);
}
/* 数字大字 — 系统字体 */
.stat-number {
font-family: var(--font-label);
font-size: 120px;
font-weight: 700;
line-height: 1.0;
color: var(--color-accent);
letter-spacing: -0.05em;
}
/* 多栏布局 */
.grid-2col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px 40px;
}
.grid-3col {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px 30px;
}
.col-divider {
border-left: 2px solid rgba(0,0,0,0.12);
padding-left: 30px;
}
/* 页脚/来源 */
.footer {
font-size: 15px;
color: #888;
letter-spacing: 0.05em;
border-top: 1px solid rgba(0,0,0,0.1);
padding-top: 15px;
margin-top: 10px;
}布局速查(密度 → 模板选择)
| 密度 | 内容量 | 推荐模板 | 主标题字号 | 条目标题 | 正文字号 |
|---|---|---|---|---|---|
| 低密度 | 1 个核心观点 | 模板 A 大字符主义 | 72-96px | — | 22px |
| 中密度 | 2-4 要点 | 模板 B 标准单栏 | 56-72px | 26-30px | 20-22px |
| 高密度 | 5+ 要点 | 模板 D 单栏列表(推荐) | 48-56px | 24-28px | 18-20px |
| 高密度+桌面 | 5+ 要点,用户要求多栏 | 模板 C 多栏网格(可选) | 48px | 24-28px | 18-20px |
字体:全部使用 TsangerJinKai(仓耳今楷,本地 TTF),不使用系统字体。默认宽度:600px,所有模板统一。
布局模板
模板 A:大字符主义(低密度内容)
适用:单一观点/数据/金句
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=600">
<style>
@font-face {
font-family: 'TsangerJinKai';
src: url('file:///Users/joe/.claude/skills/qiaomu-info-card-designer/assets/TsangerJinKai02-W04.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: block;
}
/* 基础CSS + 本模板样式 */
</style>
</head>
<body>
<div class="card" style="min-height: 900px; justify-content: space-between;">
<!-- 顶部标签(可选,仅系列卡片编号或用户要求时添加) -->
<!-- <div class="label">主题标签</div> -->
<!-- 核心内容区 -->
<div>
<div class="accent-bar" style="margin-bottom: 20px;"></div>
<h1 class="main-title" style="font-size: 96px;">核心<br>观点</h1>
</div>
<!-- 补充说明 -->
<div class="content-body" style="max-width: 600px;">
一句补充说明文字,字号 18-20px,清晰可读。
</div>
<!-- 页脚 -->
<div class="footer">来源 / 出处 · 日期</div>
</div>
</body>
</html>模板 B:标准单栏(中密度内容)
适用:2-4 个要点、文章摘要
<div class="card">
<!-- 顶部标签(可选,仅系列卡片编号或用户要求时添加)
<div style="display: flex; align-items: center; gap: 16px;">
<div class="label">分类 · 1/N</div>
<div class="accent-bar"></div>
</div>
-->
<!-- 主标题 -->
<div>
<h1 class="main-title" style="font-size: 56px;">文章主标题</h1>
<p class="content-body" style="margin-top: 12px; color: #555;">副标题或一句话摘要</p>
</div>
<!-- 分割线 -->
<div class="accent-bar-full"></div>
<!-- 正文要点 -->
<div class="content-body">
<p>第一个要点,正文 18-19px,行高 1.6。</p>
<div class="bg-block" style="margin: 16px 0;">
<p>重点引用或数据,放在色块中突出。</p>
</div>
<p>第二个要点,继续描述。</p>
</div>
<!-- 页脚 -->
<div class="footer">@vista8 · 向阳乔木推荐看</div>
</div>模板 C:多栏网格(高密度内容,可选/桌面端)
⚠️ 多栏在手机上每栏太窄,仅在用户明确要求或桌面端展示时使用。默认高密度内容推荐模板 D。
适用:5+ 要点、对比、列表(桌面端展示)
<div class="card">
<!-- 顶部(标签可选,仅系列卡片编号或用户要求时添加) -->
<div>
<!-- <div class="label">专题 · 1/N</div> -->
<h1 class="main-title" style="font-size: 48px; margin-top: 8px;">标题</h1>
<div class="accent-bar-full" style="margin-top: 16px;"></div>
</div>
<!-- 2栏内容 -->
<div class="grid-2col">
<div>
<div class="label" style="margin-bottom: 8px;">Part 01</div>
<h2 style="font-size: 24px; font-weight: 700; margin-bottom: 10px;">子标题</h2>
<p class="content-body">内容文字,18px,清晰可读。</p>
</div>
<div class="col-divider">
<div class="label" style="margin-bottom: 8px;">Part 02</div>
<h2 style="font-size: 24px; font-weight: 700; margin-bottom: 10px;">子标题</h2>
<p class="content-body">内容文字,18px,清晰可读。</p>
</div>
</div>
<!-- 页脚 -->
<div class="footer">@vista8 · 向阳乔木推荐看</div>
</div>模板 D:单栏列表(高密度内容,推荐)
适用:5+ 要点、文章要点列表、手机端高密度展示
✅ 高密度内容的默认选择,手机上每个要点都能完整展示,不会被挤压。
<div class="card">
<!-- 顶部标签(可选,仅系列卡片编号或用户要求时添加)
<div style="display: flex; align-items: center; gap: 16px;">
<div class="label">专题 · 1/N</div>
<div class="accent-bar"></div>
</div>
-->
<!-- 主标题 -->
<div>
<h1 class="main-title" style="font-size: 52px;">标题</h1>
<div class="accent-bar-full" style="margin-top: 16px;"></div>
</div>
<!-- 要点列表(单栏,每条独立) -->
<div style="display: flex; flex-direction: column; gap: 24px;">
<!-- 要点 1 -->
<div style="border-left: 4px solid var(--color-accent); padding-left: 20px;">
<h3 style="font-family: var(--font-title); font-size: 22px; font-weight: normal; margin-bottom: 6px; color: #0a0a0a;">条目标题</h3>
<p class="content-body" style="font-size: 16px;">要点描述,1-2 句话,手机上清晰可读。</p>
</div>
<!-- 要点 2 -->
<div style="border-left: 4px solid var(--color-accent); padding-left: 20px;">
<h3 style="font-family: var(--font-title); font-size: 22px; font-weight: normal; margin-bottom: 6px; color: #0a0a0a;">条目标题</h3>
<p class="content-body" style="font-size: 16px;">要点描述。</p>
</div>
<!-- 更多要点... -->
</div>
<!-- 金句引用(可选) -->
<div class="bg-block">
<p style="font-family: var(--font-body); font-size: 18px; font-style: italic; line-height: 1.6;">"原文中最有冲击力的一句话"</p>
</div>
<!-- 页脚 -->
<div class="footer">@vista8 · 向阳乔木推荐看</div>
</div>卡片宽度方案
手机优先(默认)— 600px
80%+ 用户在手机上看图。600px × 2x = 1200px,iPhone 3x 屏近 1:1 显示,正文 ~13pt 舒适可读。
| 属性 | 值 |
|---|---|
| CSS 宽度 | 600px |
| viewport | <meta name="viewport" content="width=600"> |
| 截图 | device_scale_factor=2,fullPage=true |
| 输出图片 | 1200px 宽 |
| 适用平台 | 微信、X、小红书、即刻、朋友圈 |
桌面/平板 — 800px
用户提到「桌面」「博客配图」「PPT」「iPad」「宽一点」时自动切换,无需 AskUserQuestion。
| 属性 | 值 |
|---|---|
| CSS 宽度 | 800px |
| viewport | <meta name="viewport" content="width=800"> |
| 截图 | device_scale_factor=2,fullPage=true |
| 输出图片 | 1600px 宽 |
| 适用平台 | 博客、知识库、PPT、iPad 阅读 |
800px 字号适配(相对 600px 等比放大):
| 元素 | 600px 值 | 800px 值 |
|---|---|---|
| 主标题 | 56px | 64px |
| 副标题 | 22px | 26px |
| 条目标题 h3 | 28px | 32px |
| 正文 | 20px | 22px |
| 标签/speaker | 16px | 18px |
| 页脚 | 15px | 16px |
| 卡片 padding | 38px | 48px |
| 卡片 gap | 24px | 28px |
切换触发词:「桌面」「电脑看」「博客」「PPT」「iPad」「宽一点」「800」→ 自动用 800px,不需要问用户。
通用规则
1. 截图用 fullPage=true:捕获完整卡片,不裁剪 2. 字号底线:标签/页脚 不低于 15px(手机 ~10pt),正文 不低于 20px(手机 ~13pt) 3. 高密度内容用单栏列表,不用多栏网格(多栏在手机上每栏太窄) 4. 不再使用 900px:800px 是桌面端的最佳平衡(单栏行字数适中,iPad 1600px 完美)
视觉层级原则(色块优先,线条克制)
核心:区分内容块时,优先用背景色块(bg-block、浅灰底rgba(0,0,0,0.03)),不要堆叠装饰线。
| 需求 | 正确做法 | 错误做法 |
|---|---|---|
| 区分内容块 | 浅灰色块 .bg-block、留白间距 | 给每个块加 border-top / border-left |
| 突出重点 | 色块背景 + accent 左边线(仅 1 条) | 同时用顶部线 + 左边线 + 底部线 |
| 分隔大段落 | accent-bar-full(全宽,全卡最多 1 条) | 每个段落之间都加分隔线 |
| 多栏/网格分隔 | 留白 gap + 色块背景差异 | 每个格子加 border-top 彩色线 |
装饰线预算:
- 一张卡片中,
accent-bar(短装饰线)最多 1 条,accent-bar-full(全宽分隔线)最多 1 条 - 条目之间用留白
gap分隔,不用线 - 需要强调某个条目时,用
.bg-block(浅灰底 + 左边线),不要给每个条目都加左边线 - 多栏网格中,用色块底色区分,不用
border-top彩色线
移动端紧凑原则
核心:卡片最终在手机上以图片形式阅读。字号是第一优先级,间距服务于字号。
手机 pt 换算:600px CSS × 2x 截图 = 1200px 图片,iPhone 3x 屏上 CSS px × 2 ÷ 3 ≈ pt。正文 20px ≈ 13pt,标签 16px ≈ 10pt。
字号底线(绝对不能低于):
- 正文:20px(手机 ~13pt)
- 条目标题:28px(手机 ~18pt)
- 标签/speaker/dim:16px(手机 ~10pt)
- 页脚/出处:15px(手机 ~10pt)
卡片 padding:38px,让内容区更宽。
条目间距:
.points容器 gap:14px,色块背景色差已足够区分- 条目内部 padding:
16px 18px
条目标签(speaker / dim):
- 颜色用
var(--color-accent),不用#999 - 字号
16px,letter-spacing: 0.08em
段落间 gap:
.card顶层 gap:24px- 金句
.bg-block内部 padding:18px 20px
/* 移动端参考值 */
.card { padding: 38px; gap: 24px; }
.main-title { font-size: 56px; }
.subtitle { font-size: 22px; }
.point h3 { font-size: 28px; }
.point p { font-size: 20px; line-height: 1.65; }
.point .dim,
.point .speaker { font-size: 16px; color: var(--color-accent); }
.label { font-size: 16px; }
.bg-block p { font-size: 22px; }
.footer { font-size: 15px; }
.points { gap: 14px; }
.point { padding: 16px 18px; }
.bg-block { padding: 18px 20px; }图标使用规范
原则:禁止使用 emoji。所有图标统一使用 Lucide Icons(开源,MIT协议),通过 CDN 引入。
引入方式
<!-- 在 <head> 中引入 -->
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<!-- 在 HTML 中使用 -->
<i data-lucide="book-open" class="icon"></i>
<!-- 在 </body> 前初始化 -->
<script>lucide.createIcons();</script>图标样式 CSS
.icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
vertical-align: middle;
color: var(--color-accent); /* 继承 accent 颜色 */
}
/* section 标题旁图标(稍大) */
.section-icon {
width: 26px;
height: 26px;
color: var(--color-accent);
flex-shrink: 0;
}常用图标速查
| 场景 | 图标名 | 用法 |
|---|---|---|
| 教育/阅读 | book-open | 教育理念区块 |
| 家庭/关系 | users | 家庭关系区块 |
| 财富/增长 | trending-up | 财富话题 |
| 升级/转变 | arrow-right | 对比/升级列表 |
| 决策/平衡 | scale | 决策框架 |
| 确认/原则 | check-circle | 核心规则列表 |
| 合作 | handshake | 合作规则 |
| 运动/健康 | activity | 运动习惯 |
| 饮食/自然 | leaf | 饮食建议 |
| 人生方向 | compass | 哲学/方向 |
| 思考/洞见 | lightbulb | 学习方法 |
| 反复改进 | repeat-2 | 迭代/成长 |
| 警示 | alert-triangle | 注意事项 |
| 流程步骤 | arrow-right | 四步流程 |
| 金句引用 | quote | 引用块装饰 |
| 健康跑步 | footprints | 跑步习惯 |
| 时钟/时间 | clock | 时间相关 |
| 简洁/极简 | minimize-2 | 简单原则 |
注意事项
- Playwright 截图前必须等待 CDN 加载:
page.wait_for_timeout(3000)(比普通卡片多 1s) - 图标颜色默认继承
currentColor,通过父容器color属性控制 - 图标大小用 CSS 控制,不用 HTML 属性
- 不要在正文段落内行内混入图标(影响阅读节奏),只在标题/标签区块使用
⚠️ 关键 Bug:CSS 选择器必须同时覆盖 `i` 和 `svg`
Lucide 的 createIcons() 会把 <i data-lucide="..."> 替换成 <svg> 元素。替换后,原来针对 i 的 CSS 选择器就失效了。
正确写法(必须同时写 i 和 svg):
/* ✅ 正确 */
.rule-item i, .rule-item svg { width: 20px; height: 20px; color: var(--accent); }
/* ❌ 错误:替换后 i 选择器失效,图标颜色变成继承色 */
.rule-item i { color: var(--accent); }最可靠的替代方案:在图标的父容器上设置 color: var(--accent),SVG 通过 currentColor 自动继承,无需依赖选择器:
/* 最稳定的方式:父容器设色 */
.section-label { color: var(--accent); } /* 子图标自动继承 */---
配色方案
经典/杂志风格配色(A/B 风格)
| 主题 | accent 颜色 | 适用场景 |
|---|---|---|
| 靛蓝 | #2c3e8c | 知识/科技/AI |
| 深红 | #c0392b | 警示/激情/重要 |
| 墨绿 | #1a4a3a | 生活/健康/自然 |
| 深金 | #8b6914 | 财经/商业/价值 |
| 深灰 | #2c2c2c | 中性/通用/严肃 |
| 紫罗兰 | #5b2d8e | 创意/哲学/艺术 |
艺术风格配色(C 风格)
原则:避开上方的常见"安全色",选有气质的非常规色,背景加微渐变或色温偏移。
| 主题 | 背景 | accent | 适用场景 |
|---|---|---|---|
| 赭石棕 | #F7F3EE → #F2ECE4(微暖渐变) | #7A3B1E | 人文/教育/历史 |
| 苔藓绿 | #EDF2ED → #E4EDE5(微冷渐变) | #2D5016 | 自然/健康/生活 |
| 茄紫 | #F2EEF8 → #EBE5F5 | #3D1F6E | 哲学/创意/精神 |
| 古铜暗 | #0F0C07(深暗底) | #7A5C1E | 财富/权威/深度 |
| 烟灰蓝 | #EEF0F4 → #E8EBF2 | #2A3A5C | 科技/理性/冷静 |
| 胭脂红 | #F8EEEE → #F2E5E5 | #8B1A2A | 激情/警示/重要 |
艺术风格背景渐变写法:
background: linear-gradient(160deg, #F7F3EE 0%, #F2ECE4 100%);多卡片同色系渐变
原则:多张卡片组成系列时,在主色系内做色相/明度微调,保持系列感又避免单调。
做法:以主题色为基准,每张卡片在色相环上偏移 10-20°,或在明度上做 ±10% 调整。
示例(深金系列 4 张):
| 卡片 | 色名 | 色值 | 偏移 |
|---|---|---|---|
| 1/N | 深金(基准) | #8b6914 | — |
| 2/N | 赤铜 | #8a4a1a | 偏暖红 |
| 3/N | 橄榄金 | #6b5b1e | 偏绿沉稳 |
| 4/N | 琥珀 | #9c5a0a | 偏亮收尾 |
规则:
- 第 1 张用主题基准色
- 中间卡片交替偏暖/偏冷
- 最后一张可略提亮度,收尾提气
- 所有色值与
--color-bg: #f5f3ed对比度需 ≥ 4.5:1(WCAG AA)
深色卡片设计规范(艺术风格 C 专用)
深色背景卡片有独特的对比度挑战,以下规则强制执行,违反任意一条都会导致截图不可读。
三字体混排(Style C 必须)
/* 必须声明两个本地字体 */
@font-face { font-family:'TsangerJinKai'; src:url('file:///Users/joe/.claude/skills/qiaomu-info-card-designer/assets/TsangerJinKai02-W04.ttf') format('truetype'); font-weight:normal; font-style:normal; font-display:block; }
@font-face { font-family:'NotoSerifSC'; src:url('file:///Users/joe/.claude/skills/qiaomu-info-card-designer/assets/NotoSerifSC-Regular.ttf') format('truetype'); font-weight:normal; font-style:normal; font-display:block; }
:root {
--kai: 'TsangerJinKai', serif; /* 标题、条目标题 */
--song: 'NotoSerifSC', serif; /* 正文描述、金句 */
--sans: -apple-system, 'Helvetica Neue', sans-serif; /* 编号、标签、eyebrow、footer */
}字体分工:
| 层级 | 字体 | 示例元素 |
|---|---|---|
| 主标题 / 条目标题 | --kai 仓耳今楷 | .main-title, .item-title |
| 正文描述 / 引用 | --song NotoSerifSC | .item-sub, .quote-text |
| 编号 / 标签 / eyebrow / footer | --sans 系统无衬线 | .num, .eyebrow, .section-label |
深色背景对比度规则
section-label 最小字号:
/* ✅ 正确:13px 手机可读 */
.section-label { font-size: 13px; letter-spacing: 0.14em; }
/* ❌ 错误:10px 手机上几乎不可见 */
.section-label { font-size: 10px; }编号 `.num` 对比度要求:
- 编号字号 ≥ 42px(属于大字体,WCAG 大字标准:≥ 3:1)
- 冷色系 accent(蓝/绿/紫)在深色背景上天然亮度低,直接用往往不达标
- 暖色系(金/铜/橙)对比度更稳定
/* ✅ 正确:使用提亮版本的固定色值 */
.num { color: #4E6E96; } /* 蓝系,~4.8:1 on #06090F */
.num { color: #3C7850; } /* 绿系,~5:1 on #050D07 */
.num { color: #6848A0; } /* 紫系,~3.7:1 on #08050E */
.num { color: #8C6830; } /* 金系,~4.6:1 on #0D0A05 */
/* ❌ 错误:直接使用 accent 颜色(可能过暗) */
.num { color: var(--accent); } /* #6A8FBC 在深蓝背景上只有 2.8:1,不合规 */禁止 opacity 叠加
opacity 叠加在深色背景上会把颜色压成几乎不可见的黑色:
/* ❌ 错误:opacity 叠加 */
.eyebrow { color: var(--accent); opacity: 0.45; } /* 实际渲染极暗 */
.num { color: var(--accent); opacity: 0.22; } /* 几乎看不见 */
/* ✅ 正确:直接用固定色值 */
.eyebrow { color: #5A789C; } /* 不用 opacity,直接给合规的颜色 */
.num { color: #4E6E96; }hex 颜色必须恰好 6 位
/* ❌ 错误:7 位 hex,浏览器静默回退为黑色! */
color: #6040888; /* 7 位,无效 */
/* ✅ 正确:恰好 6 位 */
color: #604088; /* 6 位,有效 */各色系 num 颜色参考值
| 色系 | 背景 | num 颜色 | 对比度 |
|---|---|---|---|
| 古铜暗(金系) | #0D0A05 | #8C6830 | ~4.6:1 |
| 钢青(蓝系) | #06090F | #4E6E96 | ~4.8:1 |
| 苔绿(绿系) | #050D07 | #3C7850 | ~5.0:1 |
| 深紫(紫系) | #08050E | #6848A0 | ~3.7:1 |
| 暖铜(铜系) | #0E0907 | #7A5430 | ~4.5:1 |
---
顶部标签使用规则
默认不加标签。顶部标签(.label+.accent-bar)是可选元素,不是必须的。
| 场景 | 是否添加 | 示例 |
|---|---|---|
| 单张独立卡片 | ❌ 不加 | — |
| 多张系列卡片 | ✅ 加编号 | 圆桌复盘 · 2/5 |
| 用户明确要求分类标签 | ✅ 加 | AI 趋势 |
| 内容本身有明确主题分类 | 可选 | 根据内容判断 |
风格方案(生成卡片时用 AskUserQuestion 让用户选择)
方案一:经典风格(默认)
沿用上方基础 CSS,全部使用 TsangerJinKai。适合快速出图、风格统一。
方案二:杂志风格
在经典风格基础上做精致化升级,适合正式发布、社交媒体传播。
与经典风格的差异:
| 元素 | 经典风格 | 杂志风格 |
|---|---|---|
| 卡片 padding | 38px | 44px 42px |
| 卡片 gap | 24px | 28px |
正文颜色 --color-muted | #555 | #333 |
| 条目列表 | 统一灰底,gap: 14px | 奇偶交替底色,gap: 0 |
| speaker/dim 标签 | 16px | 13px, letter-spacing: 0.15em, opacity: 0.85 |
| 条目标题 h3 | 28px | 26px |
| 正文 p | 20px, line-height: 1.65 | 18px, line-height: 1.75 |
| accent-bar | 6px × 80px | 4px × 60px |
| label | 16px, letter-spacing: 0.12em | 15px, letter-spacing: 0.18em |
| 页脚 | 直线分隔 | · · · 装饰符分隔 |
| 金句引用 | .bg-block(浅灰底 + 左边线) | .pull-quote(见下方) |
杂志风格条目 CSS:
/* 条目奇偶交替 */
.points { display: flex; flex-direction: column; gap: 0; }
.point { padding: 20px 22px; }
.point:nth-child(odd) { background: rgba(0,0,0,0.035); }
.point:nth-child(even) { background: transparent; }
/* speaker 标签更精致 */
.point .speaker {
font-size: 13px; letter-spacing: 0.15em;
text-transform: uppercase; opacity: 0.85;
}
/* 页脚装饰 */
.footer-divider {
text-align: center; color: #ccc; font-size: 14px;
letter-spacing: 0.5em; margin-bottom: -8px;
}金句引用 `.pull-quote`(克制版):
原则:金句是点睛之笔,不是视觉重心。用浅底色 + 左边线 + 装饰引号,融入整体节奏,不喧宾夺主。
禁止:深色反转背景(accent色满底 + 白字)。这会抢走正文内容的注意力。
.pull-quote {
background: rgba(0,0,0,0.03);
padding: 24px 24px 20px;
border-left: 4px solid var(--color-accent);
position: relative;
}
.pull-quote::before {
content: '\201C';
font-family: Georgia, serif;
font-size: 56px;
color: var(--color-accent);
opacity: 0.15;
position: absolute;
top: -4px;
left: 12px;
line-height: 1;
}
.pull-quote p {
font-family: var(--font-body);
font-size: 20px;
line-height: 1.7;
color: #1a1a1a;
font-style: italic;
position: relative;
z-index: 1;
}
.pull-quote .attribution {
font-family: var(--font-label);
font-size: 14px;
color: #999;
margin-top: 10px;
letter-spacing: 0.08em;
}字体方案(可叠加在任一风格上)
| 方案 | 标题 | 正文 | 标签 | 适用场景 |
|---|---|---|---|---|
| 全楷体(默认) | TsangerJinKai | TsangerJinKai | TsangerJinKai | 国风、文化、通用 |
| 楷宋混排 | TsangerJinKai | NotoSerifSC | TsangerJinKai | 商业、正式、长文 |
NotoSerifSC 引入(楷宋混排时额外添加):
@font-face {
font-family: 'NotoSerifSC';
src: url('file:///Users/joe/.claude/skills/qiaomu-info-card-designer/assets/NotoSerifSC-Regular.ttf') format('truetype');
font-weight: normal; font-style: normal; font-display: block;
}
:root {
--font-body: 'NotoSerifSC', serif; /* 仅替换正文 */
}输出与部署规则
截图完成后立即自动 deploy,不询问用户,不等待确认。
工作流
1. 生成 HTML → /tmp/ 临时目录 2. Playwright 截图 → 同目录下 .png(2x,fullPage) 3. 自动部署 → 直接复制 HTML + PNG 到目标目录,无需询问
目标目录规则
| 条件 | 目标路径 |
|---|---|
| 用户指定了路径 | 用户指定的路径 |
| 默认 | ~/Downloads/info-cards/{slug}/ |
slug 命名规则:YYYYMMDD-{来源简称}-{主题关键词},全小写英文+连字符。示例:20260327-justinlin-agentic-thinking、20260405-life-wisdom
部署内容
每个目录包含:
card*.html— 源文件(可二次编辑)card*.png— 截图(直接可用于发布)
#!/usr/bin/env python3
"""
Smart split for info card images.
- Only splits if height > min_total (default 1400px)
- Finds natural break points (background-color rows) instead of hard-cutting
- Never cuts through text
Usage: python3 split_card.py <image_path> [max_slice=1200] [min_total=1400]
"""
import sys
import os
import numpy as np
from PIL import Image
def find_break_points(img_array, bg_color, max_slice=1200, min_slice=600):
"""Find rows that are entirely background color (natural gaps between content).
Returns list of y-positions to split at."""
height = img_array.shape[0]
# Find rows where all pixels match background color (with tolerance)
tolerance = 15
if len(img_array.shape) == 3:
# RGB/RGBA image
diff = np.abs(img_array[:, :, :3].astype(int) - np.array(bg_color[:3]).astype(int))
is_bg_row = np.all(diff <= tolerance, axis=(1, 2))
else:
diff = np.abs(img_array.astype(int) - bg_color[0])
is_bg_row = np.all(diff <= tolerance, axis=1)
# Find contiguous runs of background rows (gaps between content)
# A gap must be at least 8px tall to count as a natural break
min_gap = 8
gaps = []
in_gap = False
gap_start = 0
for y in range(height):
if is_bg_row[y]:
if not in_gap:
in_gap = True
gap_start = y
else:
if in_gap:
gap_len = y - gap_start
if gap_len >= min_gap:
# Use the middle of the gap as the break point
gaps.append((gap_start + gap_len // 2, gap_len))
in_gap = False
# Now pick break points that keep slices between min_slice and max_slice
break_points = []
last_break = 0
for gap_y, gap_len in gaps:
distance = gap_y - last_break
if distance >= max_slice:
# We've gone too far, find the best gap before this point
# Look back for the closest gap that's within range
best = None
for gy, gl in gaps:
d = gy - last_break
if min_slice <= d <= max_slice:
best = gy
if best:
break_points.append(best)
last_break = best
else:
# No good gap found, use this one even if it's a bit over
break_points.append(gap_y)
last_break = gap_y
elif distance >= min_slice and gap_y + min_slice >= height:
# Near the end, and we have enough for a decent slice
break_points.append(gap_y)
last_break = gap_y
return break_points
def detect_bg_color(img):
"""Detect background color by sampling corners."""
pixels = []
w, h = img.size
for x, y in [(5, 5), (w-5, 5), (5, h-5), (w-5, h-5)]:
pixels.append(img.getpixel((x, y)))
# Most common corner pixel
from collections import Counter
return Counter(pixels).most_common(1)[0][0]
def split_card(image_path, max_slice=1200, min_total=1400):
img = Image.open(image_path)
width, height = img.size
if height <= min_total:
print(f"No split needed ({height}px ≤ {min_total}px threshold)")
return [image_path]
base_dir = os.path.dirname(image_path)
base_name = os.path.splitext(os.path.basename(image_path))[0]
# Remove existing -N suffix if re-splitting
if base_name.endswith(("-1", "-2", "-3", "-4", "-5")):
base_name = base_name[:-2]
bg_color = detect_bg_color(img)
img_array = np.array(img)
break_points = find_break_points(img_array, bg_color, max_slice=max_slice)
if not break_points:
print(f"No natural break points found, keeping as single image")
return [image_path]
# Create slices
cuts = [0] + break_points + [height]
parts = []
for i in range(len(cuts) - 1):
top = cuts[i]
bottom = cuts[i + 1]
slice_h = bottom - top
# Skip tiny trailing slices (< 200px)
if slice_h < 200 and i == len(cuts) - 2:
# Merge with previous slice
if parts:
prev_path = parts[-1]
prev_top = cuts[i - 1]
cropped = img.crop((0, prev_top, width, bottom))
cropped.save(prev_path, "PNG")
print(f" Updated: {prev_path} ({bottom - prev_top}px, merged tiny tail)")
continue
cropped = img.crop((0, top, width, bottom))
out_path = os.path.join(base_dir, f"{base_name}-{i+1}.png")
cropped.save(out_path, "PNG")
parts.append(out_path)
print(f" Saved: {out_path} ({slice_h}px)")
return parts
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: split_card.py <image_path> [max_slice=1200] [min_total=1400]")
sys.exit(1)
path = sys.argv[1]
max_s = int(sys.argv[2]) if len(sys.argv) > 2 else 1200
min_t = int(sys.argv[3]) if len(sys.argv) > 3 else 1400
result = split_card(path, max_s, min_t)
print(f"\nResult: {len(result)} file(s)")
for p in result:
print(f" {p}")