
Ljg Cards
- 11 installs
- 28 repo stars
- Updated February 26, 2026
- lijigang/ljg-cards
Helps with ai & agent building tasks.
About
ljg-cards is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- ljg-cards
- AI & Agent Building
- AI-coding skill
Ljg Cards by the numbers
- 11 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #11,769 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lijigang/ljg-cards --skill ljg-cardsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 28 |
| Last updated | February 26, 2026 |
| Repository | lijigang/ljg-cards ↗ |
What it does
Helps with ai & agent building tasks.
Files
约束
本 skill 输出为视觉文件(PNG),不适用 L0 中的 Org-mode 和 Denote 规范。输出为图片渲染,不受 ASCII-only 限制。
Usage
<example> User: 把这段文字做成卡片:[一段文字] Assistant: [Calls ljg-cards, generates a single long PNG card to ~/Downloads/] </example>
<example> User: /ljg-cards -m [一段文字] Assistant: [Calls ljg-cards in multi mode, generates multiple 1080x1440 PNG cards to ~/Downloads/] </example>
参数
| 参数 | 说明 |
|---|---|
| (无) | 默认模式:所有内容渲染为一张宽 1080px 的长图,高度自适应 |
-m | 多卡模式:自动切分为多张 1080x1440 卡片 |
Instructions
为了执行本项技能,请严格按照以下步骤操作:
首先判断模式:检查用户输入中是否包含 -m 参数。
- 有
-m→ 进入多卡模式(跳到「多卡模式」章节) - 无
-m→ 进入默认长图模式(继续下方步骤)
步骤 1:读取资源
读取以下文件内容到内存:
~/.claude/skills/ljg-cards/assets/card_template_long.html- 确认截图脚本路径:
~/.claude/skills/ljg-cards/assets/capture.js
步骤 2:内容预处理
- 识别标题行(
#/##/###开头,或独立短行) - 识别引用块(
>开头) - 识别加粗(
**text**) - 识别金句:独立成段的短句(通常 < 25 字),承载核心洞察,用
.highlight渲染 - 按空行分割为段落列表
- 不做切分:所有内容放在一张卡内
步骤 3:格式化为 HTML
将内容转为 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>步骤 4:渲染模板
读取 card_template_long.html 并替换模板变量:
| 变量 | 规则 |
|---|---|
{{TITLE_BLOCK}} | 有标题时:<div class="title-area"><h1>标题</h1></div>;无标题时:空字符串 |
{{BODY_HTML}} | 步骤 3 生成的全部 HTML |
{{SOURCE}} | 来源/作者信息(用户提供则填入,否则 李继刚) |
注意:长图模板无 {{HEADER_BLOCK}}、无 {{PAGE_INFO}}。logo 图片路径已硬编码在模板中,无需替换。
文件命名:从内容中提取标题或核心思想作为文件名前缀 {name}:
- 优先用文章标题(h1)
- 无标题则取首段核心关键词(2-6 个中文字或英文单词)
- 文件名规则:中文直接用,英文用连字符连接,去除标点和特殊字符,长度不超过 20 字符
写入:/tmp/ljg_card_long_{name}.html
步骤 5:截图生成
使用 fullpage 模式截图,高度由内容自动撑开:
node ~/.claude/skills/ljg-cards/assets/capture.js /tmp/ljg_card_long_{name}.html ~/Downloads/{name}.png 1080 800 fullpage依赖说明:playwright 已安装在 ~/.claude/skills/ljg-cards/node_modules/。如报错:
cd ~/.claude/skills/ljg-cards && npm install playwright && npx playwright install chromium步骤 6:交付
1. 报告文件路径 2. 执行 open ~/Downloads/
---
多卡模式(-m 参数)
当用户传入 -m 参数时,执行以下流程(替代上方默认长图流程):
M-步骤 1:读取资源
读取以下文件内容到内存:
~/.claude/skills/ljg-cards/assets/card_template.html- 确认截图脚本路径:
~/.claude/skills/ljg-cards/assets/capture.js
M-步骤 2:内容分析与切分
2.1 预处理
与默认模式步骤 2 相同:
- 识别标题行、引用块、加粗、金句
- 按空行分割为段落列表
2.2 计算视觉重量
模板直接在 1080x1440 全分辨率渲染,正文 36px,行高 1.7。手机端缩放比 ≈ 0.36,实际显示约 13pt。
- 普通段落:字符数 × 1.4
- 标题行(h1 首卡 84px):字符数 × 6.0
- 金句(
.highlight40px + 左边框 + 上下留白):字符数 × 3.0 .item条目组(label + 正文):字符数 × 1.8- 引用块:字符数 × 1.7
- 分割线(divider):固定 60 权重
- 代码块:字符数 × 2.2
- Running title(续页头部):固定 70 权重
2.3 贪心切分
- 阈值:每卡约 380 字符等价视觉重量
- 逐段累加,超过阈值时在当前段之前切分
- 切分规则:
- 绝不在句子中间切
- 优先在段落/条目/章节边界切
- 标题不落单(必须跟至少一个内容元素在同一卡)
- 超长单段在句号处强制切
- 一个章节(h2 + 3 items)通常刚好一卡
2.4 特殊情况
- 只有一张卡:不显示页码
- 多张卡:显示
1 / N格式页码
M-步骤 3:格式化为 HTML
与默认模式步骤 3 完全相同的结构映射规则。
M-步骤 4:渲染模板
对每张卡片,读取 card_template.html 并替换模板变量:
| 变量 | 规则 |
|---|---|
{{HEADER_BLOCK}} | 续页卡:<div class="header"><span class="running-title">文章标题</span></div>;首卡或单卡:空字符串 |
{{TITLE_BLOCK}} | 首卡有标题时:<div class="title-area"><h1>标题</h1></div>;续页卡或无标题时:空字符串 |
{{BODY_HTML}} | M-步骤 3 生成的 HTML |
{{SOURCE}} | 来源/作者信息(用户提供则填入,否则 李继刚) |
{{PAGE_INFO}} | 多卡时 1 / 3,单卡时空字符串 |
注意:logo 图片路径已硬编码在模板中,无需替换。
文件命名:同默认模式规则,从内容提取标题或核心思想作为 {name}。
写入:/tmp/ljg_card_{name}_{N}.html
M-步骤 5:截图生成
直接以 1080x1440 全分辨率截图:
node ~/.claude/skills/ljg-cards/assets/capture.js /tmp/ljg_card_{name}_{N}.html ~/Downloads/{name}_{N}.png 1080 1440多张卡片可并行截图。
依赖说明:同默认模式。
M-步骤 6:交付
1. 报告卡片数量 + 文件路径 2. 执行 open ~/Downloads/ 3. 多卡时给出每张摘要(前 30 字)
#!/usr/bin/env node
const path = require('path');
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);
}
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://' + path.resolve(htmlPath);
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>
:root {
--text: #1D1D1F;
--text-mid: #6E6E73;
--text-dim: #ACACB0;
--accent: #1D1D1F;
--rule: #E5E5EA;
--font: 'KingHwa_OldSong', 'PingFang SC', system-ui, sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 1080px;
background: #FFFFFF;
}
.card {
width: 1080px;
background: #FFFFFF;
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 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;
}
/* ── 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;
}
</style>
</head>
<body>
<div class="card">
{{TITLE_BLOCK}}
<div class="content">
{{BODY_HTML}}
</div>
<div class="footer">
<div class="author">
<img class="avatar" src="file:///Users/lijigang/.claude/skills/ljg-cards/assets/logo.png" alt="">
<span class="author-name">{{SOURCE}}</span>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
:root {
--text: #1D1D1F;
--text-mid: #6E6E73;
--text-dim: #ACACB0;
--accent: #1D1D1F;
--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: #FFFFFF;
}
.card {
width: 1080px;
height: 1440px;
background: #FFFFFF;
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;
}
/* ── 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="file:///Users/lijigang/.claude/skills/ljg-cards/assets/logo.png" alt="">
<span class="author-name">{{SOURCE}}</span>
</div>
<span class="page-info">{{PAGE_INFO}}</span>
</div>
</div>
</body>
</html>